This commit is contained in:
lq1405 2024-10-13 17:04:47 +08:00
commit 2183073421
96 changed files with 7949 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

363
.gitignore vendored Normal file
View File

@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

31
Dockerfile Normal file
View File

@ -0,0 +1,31 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
# ¸´ÖÆËùÓеÄÏîÄ¿Îļþ
COPY ["LMS.service/LMS.service.csproj", "LMS.service/"]
COPY ["LMS.Common/LMS.Common.csproj", "LMS.Common/"]
COPY ["LMS.DAO/LMS.DAO.csproj", "LMS.DAO/"]
COPY ["LMS.Repository/LMS.Repository.csproj", "LMS.Repository/"]
COPY ["LMS.Tools/LMS.Tools.csproj", "LMS.Tools/"]
RUN dotnet restore "LMS.service/LMS.service.csproj"
COPY . .
WORKDIR "/src/LMS.service"
RUN dotnet build "LMS.service.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "LMS.service.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "LMS.service.dll"]

View File

@ -0,0 +1,13 @@
namespace LMS.Common.Attributes
{
[AttributeUsage(AttributeTargets.Field)]
public class DescriptionAttribute : Attribute
{
public string Description { get; set; }
public DescriptionAttribute(string description)
{
Description = description;
}
}
}

View File

@ -0,0 +1,13 @@
namespace LMS.Common.Attributes
{
[AttributeUsage(AttributeTargets.Field)]
public class ResultAttribute : Attribute
{
public string Result { get; set; }
public ResultAttribute(string result)
{
Result = result;
}
}
}

View File

@ -0,0 +1,31 @@
using LMS.Common.Attributes;
namespace LMS.Common.Enums
{
public class MachineEnum
{
/// <summary>
/// 机器码使用状态
/// </summary>
public enum MachineUseStatus
{
[Description("试用")]
Trial = 0,
[Description("永久")]
Permanent = 1
}
/// <summary>
/// 机器码状态
/// </summary>
public enum MachineStatus
{
[Description("激活")]
Active = 1,
[Description("冻结")]
Frozen = 0
}
}
}

View File

@ -0,0 +1,41 @@

using LMS.Common.Attributes;
namespace LMS.Common.Enums
{
public class PermissionEnum
{
public enum PermissionType
{
[Description("用户权限")]
User = 1,
[Description("机器码权限")]
Machine = 2,
[Description("角色权限")]
Role = 3,
[Description("提示词类型")]
Prompt = 4,
[Description("权限类型权限")]
PermissionType = 5,
[Description("权限权限")]
Permission = 6,
[Description("其他权限")]
Custom = 100
}
public enum PType
{
User = 1,
Machine = 2,
Role = 3,
}
}
}

View File

@ -0,0 +1,49 @@
namespace LMS.Common.Enums
{
public class PromptEnum
{
public enum PromptType
{
/// <summary>
/// 开头提示词
/// </summary>
StartPrompt = 0,
}
public class PromptStatus
{
/// <summary>
/// 提示词状态:启用
/// </summary>
public const string Enable = "enable";
///
/// <summary>
///提示词状态:禁用
/// </summary>
public const string Disable = "disable";
}
public class PromptTypeStatus
{
/// <summary>
/// 提示词类型状态:启用
/// </summary>
public const string Enable = "enable";
///
/// <summary>
///提示词类型状态:禁用
/// </summary>
public const string Disable = "disable";
/// <summary>
/// 所有有效的提示词类型状态
/// </summary>
public static readonly HashSet<string> ValidStatuses = new HashSet<string>
{
Enable,
Disable
};
}
}
}

View File

@ -0,0 +1,198 @@
using LMS.Common.Attributes;
namespace LMS.Common.Enums
{
public class ResponseCodeEnum
{
public enum ResponseCode
{
#region
[Result("请求成功")]
[Description("请求成功")]
Success = 1,
#endregion
#region User
[Result(ResponseString.UndefinedLoginType)]
[Description(ResponseString.UndefinedLoginType)]
UndefinedLoginType = 1000,
[Result(ResponseString.UserNotFound)]
[Description("未找到对应用户名的账号")]
FindUserByNameFail = 1001,
[Result(ResponseString.UserPasswordError)]
[Description("用户密码错误")]
UserPasswordFail = 1002,
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的用户不存在")]
FindUserByIdFail = 1003,
[Result(ResponseString.UserStatusError)]
[Description("用户的状态错误,只能状态为正常的用户可以登录")]
UserStatusError = 1004,
[Result("指定邮箱的用户已经存在")]
[Description("指定邮箱的用户已经存在")]
UserEmailExist = 1005,
[Result("指定手机号的用户已经存在")]
[Description("指定手机号的用户已经存在")]
UserPhoneExist = 1006,
[Result("指定邮箱或手机号的用户已经存在")]
[Description("指定邮箱或手机号的用户已经存在")]
UserEmailOrPhoneExist = 1007,
[Result("指定的用户名已存在")]
[Description("指定的用户名已存在")]
UasrNameExist = 1008,
[Result("指定的用户昵称已存在")]
[Description("指定的用户昵称已存在")]
UserNickNameExist = 1009,
[Result(ResponseString.UserIsLockedOut)]
[Description(ResponseString.UserIsLockedOut)]
UserIsLockedOut = 1010,
[Result(ResponseString.UserLoginOut)]
[Description("用户的刷新令牌无效,可能是二次登录,或者是登录到期,请重新登录")]
RefreshTokenInvalid = 1011,
[Result("用户注册失败")]
[Description("用户注册失败")]
UserRegisterFial = 1012,
[Result("用户不是VIP用户")]
[Description("用户不是VIP用户")]
UserNotVip = 1013,
[Result("无效的邀请码")]
[Description("无效的邀请码")]
InvalidAffiliateCode = 1014,
#endregion
#region Machine
[Result(ResponseString.MachineStatusNotFoundOrStatusIsNot)]
[Description(ResponseString.MachineStatusNotFoundOrStatusIsNot)]
MachineNotFound = 2001,
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的机器码不存在")]
FindMachineByIdFail = 2002,
[Result(ResponseString.DataExist)]
[Description("指定机器码已经存在")]
MachineAlreadyExist = 2003,
#endregion
#region PermissionType
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的权限分类没有找到")]
FindPermissionTypeByIdFail = 3001,
[Result(ResponseString.DataExist)]
[Description("指定ID的权限名称和编码已经存在")]
PermissionTypeExist = 3002,
#endregion
#region
[Result(ResponseString.NotPermissionAction)]
[Description(ResponseString.NotPermissionAction)]
NotPermissionAction = 4001,
[Result(ResponseString.DataExist)]
[Description("指定用户ID和权限分类ID已存在不可新增")]
PermissionExist = 4002,
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的权限没有找到")]
FindPermissionByIdFail = 4003,
[Result("权限编码已存在,请检查")]
[Description("权限编码已存在,请检查")]
PermissionCodeExist = 4004,
#endregion
#region
[Result(ResponseString.SystemError)]
[Description(ResponseString.SystemError)]
SystemError = 5000,
[Result(ResponseString.ParameterError)]
[Description(ResponseString.ParameterError)]
ParameterError = 5001,
[Result(ResponseString.InvalidOptions)]
[Description(ResponseString.InvalidOptions)]
InvalidOptions = 5002,
#endregion
#region
[Result(ResponseString.DataExist)]
[Description("指定的提示词已经存在")]
PromptStringExist = 6001,
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的提示词没有找到")]
FindPromptStringFail = 6002,
#endregion
#region
[Result(ResponseString.DataExist)]
[Description("指定的提示词的名称或者是编码已存在")]
PromptTypeExist = 7001,
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的提示词类型没有找到")]
FindPromptTypeFail = 7002,
#endregion
#region
[Result(ResponseString.ForwardFail)]
[Description("文案转发失败")]
ForwardWordFail = 8001,
#endregion
#region
[Result(ResponseString.DataExist)]
[Description("指定的角色名称已经存在")]
RoleNameExist = 9001,
[Result(ResponseString.IdDateNotFound)]
[Description("指定ID的角色不存在")]
FindRoleByIdFail = 9002,
[Result(ResponseString.DataNotExist)]
[Description("指定编码的角色不存在")]
FindRoleByCodeFail = 9003,
[Result(ResponseString.HasRelationship)]
[Description("指定角色已经绑定了用户,无法删除")]
RoleHasUser = 9004,
#endregion
}
}
}

View File

@ -0,0 +1,42 @@
namespace LMS.Common.Enums
{
public static class ResponseString
{
#region
public const string UndefinedLoginType = "未知的登录类型";
public const string UserNotFound = "用户未找到,请先注册";
public const string UserIsLockedOut = "用户已被锁定,请联系管理员";
public const string UserPasswordError = "检查账号和密码是否正确";
public const string UserLoginOut = "用户已退出,请重新登录";
#endregion
public const string UserLogin = "检查账号和密码是否正确";
public const string UserEmailNotFound = "未找到对应用户名/邮箱的账号";
public const string UserStatusError = "用户的状态错误";
public const string MachineStatusNotFoundOrStatusIsNot = "未找到对应的机器码或者是机器码已过期,请联系开发或客服";
public const string SystemError = "系统错误";
public const string ParameterError = "参数错误";
public const string InvalidOptions = "无效的操作";
public const string IdDateNotFound = "指定ID的数据没有找到";
public const string DataExist = "数据已存在,不可新增";
public const string HasRelationship = "数据存在关联,请先删除关联";
public const string DataNotExist = "数据不存在,请检查";
public const string NotPermissionAction = "没有执行该操作的权限";
public const string ForwardFail = "转发失败";
}
}

View File

@ -0,0 +1,31 @@
using LMS.Common.Attributes;
namespace LMS.Common.Enums
{
public class UserStatus
{
public enum UserStatusEnum
{
[Description("正常")]
Normal = 1,
[Description("禁用")]
Disable = 0,
[Description("删除")]
Delete = 2,
[Description("黑名单")]
BlackList = 3,
[Description("未激活")]
NotActive = 4,
[Description("违规")]
Violation = 5,
[Description("未知")]
Unknown = 6
}
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,37 @@
using System.Security.Cryptography;
using System.Text;
namespace LMS.Common.RSAKey
{
public static class AESGenerate
{
public static string Encrypt(string plainText, byte[] key, byte[] iv)
{
using Aes aesAlg = Aes.Create();
aesAlg.Key = key;
aesAlg.IV = iv; // 使用全零的IV,实际使用时应该使用随机IV
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
byte[] encrypted = encryptor.TransformFinalBlock(Encoding.UTF8.GetBytes(plainText), 0, plainText.Length);
return Convert.ToBase64String(encrypted);
}
public static string Decrypt(string cipherText, byte[] key, byte[] iv)
{
using Aes aesAlg = Aes.Create();
aesAlg.Key = key;
aesAlg.IV = iv; // 使用全零的IV,需要与加密时使用的IV一致
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
byte[] cipher = Convert.FromBase64String(cipherText);
byte[] decrypted = decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
return Encoding.UTF8.GetString(decrypted);
}
}
}

View File

@ -0,0 +1,99 @@
using System.Security.Cryptography;
namespace LMS.Common.RSAKey
{
/// <summary>
/// 这是一个混淆器,用于对密钥进行混淆,以增加破解难度
/// </summary>
public static class ComplexKeyObfuscator
{
private const int SaltSize = 16;
/// <summary>
/// 混淆方法
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static byte[] Obfuscate(byte[] key)
{
if (key == null || key.Length == 0)
throw new ArgumentException("Key cannot be null or empty");
byte[] salt = GenerateRandomSalt();
byte[] obfuscatedKey = new byte[key.Length];
Array.Copy(key, obfuscatedKey, key.Length);
// 多层混淆操作
for (int i = 0; i < obfuscatedKey.Length; i++)
{
// 异或操作
obfuscatedKey[i] ^= salt[i % salt.Length];
obfuscatedKey[i] ^= (byte)((i * 17) % 256); // 使用质数17
obfuscatedKey[i] ^= (byte)((obfuscatedKey.Length - i) % 256);
// 位移操作
obfuscatedKey[i] = (byte)(((obfuscatedKey[i] << 3) | (obfuscatedKey[i] >> 5)) & 0xFF);
// 基于位置的置换
int newPos = (i * 7 + 5) % obfuscatedKey.Length; // 使用质数7
byte temp = obfuscatedKey[i];
obfuscatedKey[i] = obfuscatedKey[newPos];
obfuscatedKey[newPos] = temp;
}
// 将salt和混淆后的密钥合并
byte[] result = new byte[salt.Length + obfuscatedKey.Length];
Array.Copy(salt, 0, result, 0, salt.Length);
Array.Copy(obfuscatedKey, 0, result, salt.Length, obfuscatedKey.Length);
return result;
}
/// <summary>
/// 解决混淆方法
/// </summary>
/// <param name="obfuscatedData"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static byte[] Deobfuscate(byte[] obfuscatedData)
{
if (obfuscatedData == null || obfuscatedData.Length <= SaltSize)
throw new ArgumentException("Invalid obfuscated data");
byte[] salt = new byte[SaltSize];
byte[] obfuscatedKey = new byte[obfuscatedData.Length - SaltSize];
Array.Copy(obfuscatedData, 0, salt, 0, SaltSize);
Array.Copy(obfuscatedData, SaltSize, obfuscatedKey, 0, obfuscatedKey.Length);
// 反向多层混淆操作
for (int i = obfuscatedKey.Length - 1; i >= 0; i--)
{
// 反向置换
int newPos = (i * 7 + 5) % obfuscatedKey.Length;
byte temp = obfuscatedKey[i];
obfuscatedKey[i] = obfuscatedKey[newPos];
obfuscatedKey[newPos] = temp;
// 反向位移操作
obfuscatedKey[i] = (byte)(((obfuscatedKey[i] >> 3) | (obfuscatedKey[i] << 5)) & 0xFF);
// 反向异或操作
obfuscatedKey[i] ^= (byte)((obfuscatedKey.Length - i) % 256);
obfuscatedKey[i] ^= (byte)((i * 17) % 256);
obfuscatedKey[i] ^= salt[i % salt.Length];
}
return obfuscatedKey;
}
private static byte[] GenerateRandomSalt()
{
using var rng = RandomNumberGenerator.Create();
byte[] salt = new byte[SaltSize];
rng.GetBytes(salt);
return salt;
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.Common.RSAKey
{
public class RSAKeyGenerateModel
{
public string PublicKey { get; set; }
public string EncryptedPrivateKey { get; set; }
public string EncryptedKey { get; set; }
public string EncryptionIV { get; set; }
}
}

View File

@ -0,0 +1,131 @@
using LMS.Common.RSAKey;
using System.Security.Cryptography;
using System.Text;
namespace LMS.Common.RSAKey
{
public static class RsaKeyPairGenerator
{
/// <summary>
/// 初始化一个RSA密钥对
/// </summary>
/// <returns></returns>
public static RSAKeyGenerateModel InitRsaKey()
{
using RSACryptoServiceProvider rsa = new(2048);
string publicKeyPem = ExportPublicKeyToPem(rsa);
string privateKeyPem = ExportPrivateKeyToPem(rsa);
// 随机生成一个AES密钥
byte[] aesKey = GenerateRandomAesKey(32);
byte[] aesIv = GenerateRandomAesKey(16);
// 通过随机的密钥开始AES加密私钥
string encryptPrivateKey = AESGenerate.Encrypt(privateKeyPem, aesKey, aesIv);
return new RSAKeyGenerateModel
{
PublicKey = publicKeyPem,
EncryptedPrivateKey = encryptPrivateKey,
EncryptedKey = Convert.ToBase64String(ComplexKeyObfuscator.Obfuscate(aesKey)),
EncryptionIV = Convert.ToBase64String(ComplexKeyObfuscator.Obfuscate(aesIv))
};
}
private static byte[] GenerateRandomAesKey(int bits)
{
using var randomNumberGenerator = RandomNumberGenerator.Create();
var randomBytes = new byte[bits]; // 256 bits
randomNumberGenerator.GetBytes(randomBytes);
return randomBytes;
}
public static void GenerateRsaKeyPair()
{
using RSACryptoServiceProvider rsa = new(2048);
string publicKeyPem = ExportPublicKeyToPem(rsa);
string privateKeyPem = ExportPrivateKeyToPem(rsa);
Console.WriteLine("公钥 (PEM)\n" + publicKeyPem);
Console.WriteLine("私钥 (PEM)\n" + privateKeyPem);
Console.WriteLine();
string original = "Hello, RSA!";
var encrypted = Encrypt(publicKeyPem, original);
Console.WriteLine("Encrypted: " + encrypted);
Console.WriteLine();
var decrypted = Decrypt(privateKeyPem, encrypted);
Console.WriteLine("Decrypted: " + decrypted);
var signData = SignData(privateKeyPem, original);
Console.WriteLine("SignData: " + signData);
var isVerify = VerifySignData(publicKeyPem, original, signData);
Console.WriteLine("VerifySign: " + isVerify);
}
private static string ExportPublicKeyToPem(RSA rsa)
{
var publicKey = rsa.ExportSubjectPublicKeyInfo();
var base64 = Convert.ToBase64String(publicKey);
return $"-----BEGIN PUBLIC KEY-----\n{InsertNewLines(base64)}\n-----END PUBLIC KEY-----";
}
private static string ExportPrivateKeyToPem(RSA rsa)
{
var privateKey = rsa.ExportPkcs8PrivateKey();
var base64 = Convert.ToBase64String(privateKey);
return $"-----BEGIN PRIVATE KEY-----\n{InsertNewLines(base64)}\n-----END PRIVATE KEY-----";
}
private static string InsertNewLines(string input)
{
return string.Join("\n", Enumerable.Range(0, input.Length / 64 + 1)
.Select(i => input.Substring(i * 64, Math.Min(64, input.Length - i * 64))));
}
public static string Encrypt(string publicKeyPem, string data)
{
using (var rsa = RSA.Create())
{
rsa.ImportFromPem(publicKeyPem);
byte[] encryptedData = rsa.Encrypt(Encoding.UTF8.GetBytes(data), RSAEncryptionPadding.OaepSHA256);
return Convert.ToBase64String(encryptedData);
}
}
public static string Decrypt(string privateKeyPem, string data)
{
using (var rsa = RSA.Create())
{
rsa.ImportFromPem(privateKeyPem);
byte[] decryptedData = rsa.Decrypt(Convert.FromBase64String(data), RSAEncryptionPadding.OaepSHA256);
return Encoding.UTF8.GetString(decryptedData);
}
}
public static string SignData(string privateKeyPem, string data)
{
using (var rsa = RSA.Create())
{
rsa.ImportFromPem(privateKeyPem);
var inputBytes = Encoding.UTF8.GetBytes(data);
var resultBytes = rsa.SignData(inputBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(resultBytes);
}
}
public static bool VerifySignData(string publicKeyPem, string data, string sign)
{
using (var rsa = RSA.Create())
{
rsa.ImportFromPem(publicKeyPem);
var dataBytes = Encoding.UTF8.GetBytes(data);
var signBytes = Convert.FromBase64String(sign);
return rsa.VerifyData(dataBytes, signBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
}
}

View File

@ -0,0 +1,46 @@

using LMS.Repository.DB;
using LMS.Repository.Models.DB;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
namespace LMS.DAO
{
public class ApplicationDbContext : IdentityDbContext<User, Role, long>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
//public DbSet<Prompt> Prompt { get; set; }
//public DbSet<PromptType> PromptType { get; set; }
public DbSet<Permission> Permission { get; set; }
public DbSet<PermissionType> PermissionType { get; set; }
public DbSet<Machine> Machine { get; set; }
public DbSet<RefreshTokens> RefreshTokens { get; set; }
public DbSet<ApiEndpoints> ApiEndpoints { get; set; }
public DbSet<RsaKeys> RsaKeys { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApiEndpoints>()
.Property(a => a.RequiredPermissionIds)
.HasConversion(
v => string.IsNullOrEmpty(JsonSerializer.Serialize(v, (JsonSerializerOptions?)null))
? "[]" // 如果序列化结果为空,则存储空数组
: JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => string.IsNullOrEmpty(v) || v == "[]"
? new List<string>() // 如果存储的是空字符串或空数组,则返回空列表
: JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? new List<string>()
);
}
}
}

21
LMS.DAO/LMS.DAO.csproj Normal file
View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="OneOf" Version="3.0.271" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LMS.Common\LMS.Common.csproj" />
<ProjectReference Include="..\LMS.Repository\LMS.Repository.csproj" />
<ProjectReference Include="..\LMS.Tools\LMS.Tools.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.DAO.MachineDAO
{
public class MachineBasicDao(ApplicationDbContext context)
{
private readonly ApplicationDbContext _context = context;
/// <summary>
/// 检查机器码是否存在机器码不是对应的ID
/// </summary>
/// <param name="machineId"></param>
/// <returns></returns>
public async Task<bool> CheckMachineByMachineId(string? machineId)
{
if (string.IsNullOrWhiteSpace(machineId))
{
return false;
}
return await _context.Machine.AnyAsync(x => x.MachineId == machineId);
}
/// <summary>
/// 检查机器码是否存在机器码对应的ID
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
public async Task<bool> CheckMachineById(string Id)
{
if (string.IsNullOrWhiteSpace(Id))
{
return false;
}
return await _context.Machine.AnyAsync(x => x.Id == Id);
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace LMS.DAO
{
public class MyDbcontextDesignFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
//public MyDbcontextDesignFactory CreateDbContext(string[] args)
//{
// DbContextOptionsBuilder<ApplicationDbContext> optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
// string str = Environment.GetEnvironmentVariable("CONNECTION_STRING");
// optionsBuilder.UseMySql(str, ServerVersion.Parse("8.0.18-mysql"));
// ApplicationDbContext db = new ApplicationDbContext(optionsBuilder.Options);
// return db;
//}
public ApplicationDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<ApplicationDbContext> optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
string str = "server=123.129.219.240;port=14080;user=luo;password=Luoqiang1405;database=LMS_TEST";
optionsBuilder.UseMySql(str, ServerVersion.Parse("8.0.18-mysql"));
ApplicationDbContext db = new ApplicationDbContext(optionsBuilder.Options);
return db;
}
}
}

View File

@ -0,0 +1,117 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Primitives;
using OneOf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LMS.Common.Enums.PermissionEnum;
namespace LMS.DAO.PermissionDAO
{
public class PermissionBasicDao(ApplicationDbContext context)
{
private readonly ApplicationDbContext _context = context;
/// <summary>
/// 判断指定的ID的权限是不是存在
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<bool> CheckPermissionExistById(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return false;
}
return await _context.Permission.AnyAsync(x => x.Id == id);
}
/// <summary>
/// 判断权限的PermissionCode是不是存在
/// 通过判断是不是有传递ID , 判断是不是需要排除当前的数据
/// </summary>
/// <param name="permissionCode"></param>
/// <param name="id"></param>
/// <returns></returns>
public async Task<bool> CheckPermissionCodeExist(string permissionCode, string id = null)
{
if (string.IsNullOrWhiteSpace(permissionCode))
{
throw new ArgumentNullException(nameof(permissionCode));
}
if (string.IsNullOrEmpty(id))
{
return await _context.Permission.AnyAsync(x => x.PermissionCode == permissionCode);
}
else
{
return await _context.Permission.AnyAsync(x => x.PermissionCode == permissionCode && x.Id != id);
}
}
/// <summary>
/// 检查对应分类的权限是不是存在
/// </summary>
/// <param name="type"></param>
/// <param name="id"></param>
/// <param name="pid">再修改的时候必传这个值用于判断对应的关联ID是不是有其他的值有的话不能修改</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<bool> CheckPermissionByTypeAndId(PType type, OneOf<string, long?> id, string pid = null)
{
if (id.IsT0)
{
if (type != PType.Machine)
{
throw new Exception("请检查参数的对应关系");
}
if (!string.IsNullOrEmpty(pid))
{
return await _context.Permission.AnyAsync(x => x.MachineId == id.AsT0 && x.Type == type && x.Id != pid);
}
else
{
return await _context.Permission.AnyAsync(x => x.MachineId == id.AsT0 && x.Type == type);
}
}
else if (id.IsT1)
{
if (type == PType.Machine)
{
throw new Exception("请检查参数的对应关系");
}
if (id.AsT1 == null)
{
return false;
}
if (!string.IsNullOrEmpty(pid))
{
return type switch
{
PType.User => await _context.Permission.AnyAsync(x => x.UserId == id.AsT1 && x.Type == type && x.Id != pid),
PType.Role => await _context.Permission.AnyAsync(x => x.RoleId == id.AsT1 && x.Type == type && x.Id != pid),
PType.Machine => throw new Exception("请检查参数的对应关系"),
_ => throw new Exception("参数类型错误"),
};
}
else
{
return type switch
{
PType.User => await _context.Permission.AnyAsync(x => x.UserId == id.AsT1 && x.Type == type),
PType.Role => await _context.Permission.AnyAsync(x => x.RoleId == id.AsT1 && x.Type == type),
PType.Machine => throw new Exception("请检查参数的对应关系"),
_ => throw new Exception("参数类型错误"),
};
}
}
else
{
throw new Exception("参数类型错误");
}
}
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.DAO.PermissionDAO
{
public class PermissionTypeDao(ApplicationDbContext context)
{
private readonly ApplicationDbContext _context = context;
/// <summary>
/// 判断权限类型ID数组中的数据是不是都存在数据库中
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task<bool> CheckPermissionTypeIdsExist(List<string> ids)
{
foreach (var id in ids)
{
if (!await _context.PermissionType.AnyAsync(x => x.Id == id))
{
return false;
}
}
return true;
}
/// <summary>
/// 检查权限类型的Code是不是存在
/// </summary>
/// <param name="Code"></param>
/// <param name="Id"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<bool> CheckPermissionTypeCodeExist(string Code, string Id = null)
{
if (string.IsNullOrWhiteSpace(Code))
{
throw new ArgumentNullException(nameof(Code));
}
if (string.IsNullOrWhiteSpace(Id))
{
return await _context.PermissionType.AnyAsync(x => x.Code == Code);
}
else
{
return await _context.PermissionType.AnyAsync(x => x.Code == Code && x.Id != Id);
}
}
}
}

View File

@ -0,0 +1,28 @@
using LMS.Repository.Models.DB;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.DAO.RoleDAO
{
public class RoleBasicDao(ApplicationDbContext context, RoleManager<Role> roleManager)
{
private readonly ApplicationDbContext _context = context;
private readonly RoleManager<Role> _roleManager = roleManager;
/// <summary>
/// 检查角色是不是存在
/// </summary>
/// <param name="roleName"></param>
/// <returns></returns>
public async Task<bool> CheckRoleExistById(long? roleId)
{
if (roleId == null)
return false;
return await _roleManager.FindByIdAsync(roleId.ToString()) != null;
}
}
}

View File

@ -0,0 +1,29 @@
using LMS.Repository.Models.DB;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.DAO.UserDAO
{
public class UserBasicDao(UserManager<User> userManager)
{
private readonly UserManager<User> _userManager = userManager;
/// <summary>
/// 检查用户是否存在,通过用户ID
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<bool> CheckUserExistsByID(long? userId)
{
if (userId == null)
{
return false;
}
return await _userManager.FindByIdAsync(userId.ToString()) != null;
}
}
}

View File

@ -0,0 +1,22 @@
using LMS.Tools.Extensions;
namespace LMS.Repository.Models.DB
{
public class ApiEndpoints
{
public string Id { get; set; }
public string HttpMethod { get; set; }
public string Path { get; set; }
public DateTime CreateTime { get; set; } = BeijingTimeExtension.GetBeijingTime();
public List<string> RequiredPermissionIds { get; set; }
public long CreatedId { get; set; }
public long UpdatedId { get; set; }
}
}

View File

@ -0,0 +1,62 @@
using static LMS.Common.Enums.MachineEnum;
namespace LMS.Repository.Models.DB
{
public class Machine
{
/// <summary>
/// ID 主键
/// </summary>
public string Id { get; set; }
/// <summary>
/// 机器ID
/// </summary>
public string MachineId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 停用时间
/// </summary>
public DateTime? DeactivationTime { get; set; }
/// <summary>
/// 状态(是否可用)
/// </summary>
public MachineStatus Status { get; set; }
/// <summary>
/// 创建者ID
/// </summary>
public long CreateId { get; set; }
/// <summary>
/// 更新者ID
/// </summary>
public long UpdateId { get; set; }
/// <summary>
/// 使用状态(试用,永久)
/// </summary>
public MachineUseStatus UseStatus { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 所属用户ID
/// </summary>
public required long UserID { get; set; }
}
}

View File

@ -0,0 +1,82 @@
using LMS.Common.Enums;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
using static LMS.Common.Enums.PermissionEnum;
namespace LMS.Repository.Models.DB
{
/// <summary>
/// 所有的权限集合
/// </summary>
public class Permission
{
/// <summary>
/// 权限的ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 用户的ID
/// </summary>
public long? UserId { get; set; }
/// <summary>
/// 机器码的ID是ID不是机器码
/// </summary>
public string? MachineId { get; set; }
/// <summary>
/// 角色ID
/// </summary>
public long? RoleId { get; set; }
/// <summary>
/// 权限类型的ID子权限
/// </summary>
[Column(TypeName = "json")]
public string PermissionTypeIds { get; set; }
/// <summary>
/// 权限对应的Code
/// </summary>
public string PermissionCode { get; set; }
/// <summary>
/// 权限类型
/// </summary>
public PType Type { get; set; }
/// <summary>
/// 创建人ID
/// </summary>
public long CreateUserId { get; set; }
/// <summary>
/// 更新人ID
/// </summary>
public long UpdateUserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
[NotMapped]
public List<string> PermissionTypeIdsJson
{
get => JsonSerializer.Deserialize<List<string>>(PermissionTypeIds) ?? [];
set => PermissionTypeIds = JsonSerializer.Serialize(value);
}
}
}

View File

@ -0,0 +1,56 @@
using LMS.Common.Enums;
using static LMS.Common.Enums.PermissionEnum;
namespace LMS.Repository.Models.DB
{
/// <summary>
/// 存放权限的表
/// </summary>
public class PermissionType
{
/// <summary>
/// 权限ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 权限的名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 表示权限的代码
/// </summary>
public string Code { get; set; }
/// <summary>
/// 权限的分类user和machine
/// </summary>
public PermissionEnum.PermissionType Type { get; set; }
/// <summary>
/// 创建者ID
/// </summary>
public long CreateUserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新者ID
/// </summary>
public long UpdateUserId { get; set; }
/// <summary>
/// 更新权限的时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 权限的描述
/// </summary>
public string? Remark { get; set; }
}
}

View File

@ -0,0 +1,71 @@
namespace LMS.Repository.Models.DB
{
public class Prompt
{
/// <summary>
/// ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 提示词预设的名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 提示词的类型ID
/// </summary>
public string PromptTypeId { get; set; }
/// <summary>
/// 提示词的类型编码
/// </summary>
public string PromptTypeCode { get; set; }
/// <summary>
/// 提示词预设字符串
/// </summary>
public string PromptString { get; set; }
/// <summary>
/// 当前版本
/// </summary>
public int Version { get; set; }
/// <summary>
/// 状态
/// </summary>
public string Status { get; set; }
/// <summary>
/// 提示词的描述
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 创建者
/// </summary>
public string CreateUserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新者
/// </summary>
public string UpdateUserId { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
}
}

View File

@ -0,0 +1,61 @@
namespace LMS.Repository.Models.DB
{
/// <summary>
/// 提示词类型的库
/// </summary>
public class PromptType
{
/// <summary>
/// ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 提示词类型编码
/// </summary>
public string Code { get; set; }
/// <summary>
/// 提示词类型名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 提示词类型描述
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 提示词类型状态
/// </summary>
public string Status { get; set; }
/// <summary>
/// 是否删除
/// </summary>
public bool IsDeleted { get; set; }
/// <summary>
/// 创建用户ID
/// </summary>
public string CreateUserId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新者ID
/// </summary>
public string UpdateUserId { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime UpdateTime { get; set; }
}
}

View File

@ -0,0 +1,46 @@
namespace LMS.Repository.Models.DB
{
public class RefreshTokens
{
/// <summary>
/// 主键 ID
/// </summary>
public required string Id { get; set; }
/// <summary>
/// 用户ID
/// </summary>
public required long UserId { get; set; }
/// <summary>
/// Token
/// </summary>
public required string Token { get; set; }
/// <summary>
/// 到期时间
/// </summary>
public DateTime Expiration { get; set; }
/// <summary>
/// 是不是失效
/// </summary>
public Boolean Revoked { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedTime { get; set; }
/// <summary>
/// 上次校验IP
/// </summary>
public required string LastCheckIp { get; set; }
/// <summary>
/// 设备信息,浏览器信息等
/// </summary>
public required string DeviceInfo { get; set; }
}
}

18
LMS.Repository/DB/Role.cs Normal file
View File

@ -0,0 +1,18 @@
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
namespace LMS.Repository.Models.DB
{
public class Role : IdentityRole<long>
{
public long CreatedUserId { get; set; }
public long UpdatedUserId { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public string? Remark { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,57 @@
using LMS.Tools.Extensions;
namespace LMS.Repository.DB
{
public class RsaKeys
{
/// <summary>
/// ID 主键
/// </summary>
public required string Id { get; set; }
/// <summary>
/// 使用次数
/// </summary>
public int UseCount { get; set; }
/// <summary>
/// 公钥
/// </summary>
public required string PublicKey { get; set; }
/// <summary>
/// 加密后的私钥
/// </summary>
public required string EncryptedPrivateKey { get; set; }
/// <summary>
/// 加密私钥的随机字符串
/// </summary>
public required string EncryptionKey { get; set; } // 加密私钥的随机字符串
/// <summary>
/// 加密私钥的随机IV
/// </summary>
public required string EncryptionIV { get; set; } // 加密私钥的随机字符串
/// <summary>
/// Key的版本
/// </summary>
public int KeyVersion { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedTime { get; set; } = BeijingTimeExtension.GetBeijingTime();
/// <summary>
/// 上次使用的时间
/// </summary>
public DateTime? LastUsed { get; set; }
/// <summary>
/// 到期时间
/// </summary>
public DateTime ExpirationTime { get; set; }
}
}

69
LMS.Repository/DB/User.cs Normal file
View File

@ -0,0 +1,69 @@
using LMS.Repository.User;
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace LMS.Repository.Models.DB
{
public class User : IdentityUser<long>
{
[MaxLength(50)]
public string NickName { get; set; }
/// <summary>
/// 允许使用的机器码数量
/// </summary>
public long AllDeviceCount { get; set; } = 1;
/// <summary>
/// 代理分成比例
/// </summary>
public double AgentPercent { get; set; } = 0.00;
/// <summary>
/// 免费修改机器码次数
/// </summary>
public long FreeCount { get; set; } = 0;
/// <summary>
/// 用户的一些简单的操作
/// </summary>
[Column(TypeName = "json")]
public string? Options { get; set; } = "{}";
[Column(TypeName = "datetime")]
public DateTime CreatedDate { get; set; } = DateTime.Now;
[Column(TypeName = "datetime")]
public DateTime UpdatedDate { get; set; } = DateTime.Now;
[Column(TypeName = "datetime")]
public DateTime LastLoginDate { get; set; } = DateTime.Now;
public string? LastLoginIp { get; set; } = "";
public string? LastLoginDevice { get; set; } = "";
/// <summary>
/// 上级ID
/// </summary>
public long? ParentId { get; set; }
/// <summary>
/// 推广码
/// </summary>
public string AffiliateCode { get; set; } = string.Empty;
/// <summary>
/// 实际使用的Options
/// </summary>
[NotMapped]
public UserPrivateOptions OptionsJson
{
get => JsonSerializer.Deserialize<UserPrivateOptions>(Options ?? "{}") ?? new UserPrivateOptions();
set => Options = JsonSerializer.Serialize(value);
}
}
}

View File

@ -0,0 +1,11 @@
namespace LMS.Repository.Models.DB
{
public class UserRoles
{
public required string RoleId { get; set; }
public required string UserId { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.Repository.DTO
{
public class CollectionResponse<T>
{
public int Current { get; set; }
public int Total { get; set; }
public List<T> Collection { get; set; } = new List<T>();
}
}

View File

@ -0,0 +1,14 @@
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
namespace LMS.Repository.DTO
{
public class MachineDetailDto : Machine
{
public UserBaseDto? CreatedUser { get; set; }
public UserBaseDto? UpdatedUser { get; set; }
public UserBaseDto? OwnUser { get; set; }
}
}

View File

@ -0,0 +1,30 @@
using static LMS.Common.Enums.MachineEnum;
namespace LMS.Repository.DTO.MachineResponse
{
public class MachineDto
{
public class MachineStatusResponse
{
/// <summary>
/// 机器状态
/// </summary>
public MachineStatus Status { get; set; }
/// <summary>
/// 到期时间
/// </summary>
public DateTime? DeactivationTime { get; set; }
/// <summary>
/// 机器码ID
/// </summary>
public string Id { get; set; }
/// <summary>
/// 机器码
/// </summary>
public string MachineId { get; set; }
}
}
}

View File

@ -0,0 +1,21 @@
using LMS.Repository.DTO.UserDto;
namespace LMS.Repository.DTO
{
public class RoleDto
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public UserBaseDto? CreatedUser { get; set; }
public UserBaseDto? UpdeatedUser { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public string Remark { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,10 @@
namespace LMS.Repository.DTO;
public class SoftwareDao
{
public string Version { get; set; }
public string Author { get; set; }
public string Description { get; set; }
}

View File

@ -0,0 +1,44 @@
namespace LMS.Repository.DTO.UserDto;
public class UserAgentInfoDto
{
/// <summary>
/// 用户ID
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 用户名称
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 用户昵称
/// </summary>
public string NickName { get; set; }
/// <summary>
/// 用户的邀请码
/// </summary>
public string AffiliateCode { get; set; }
/// <summary>
/// 用户的代理分成比例
/// </summary>
public double AgentPercent { get; set; }
/// <summary>
/// 邀请人数
/// </summary>
public int AffiliateNumber { get; set; }
/// <summary>
/// 邀请的VIP人数
/// </summary>
public int AffiliateVIPNumber { get; set; }
/// <summary>
/// 邀请获取的金额
/// </summary>
public double AffiliateMoney { get; set; }
}

View File

@ -0,0 +1,18 @@
using LMS.Repository.User;
namespace LMS.Repository.DTO.UserDto;
public class UserBaseDto
{
public long Id { get; set; }
public string NickName { get; set; } = string.Empty;
public string UserName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string PhoneNumber { get; set; } = string.Empty;
public string Avatar { get; set; } = string.Empty;
}

View File

@ -0,0 +1,17 @@
namespace LMS.Repository.DTO.UserDto
{
public class UserCollectionDto : UserBaseDto
{
public List<string> RoleNames { get; set; } = [];
public DateTime CreatedDate { get; set; }
public DateTime LastLoginDate { get; set; }
public long ParentId { get; set; }
public string LastLoginIp { get; set; } = string.Empty;
public string LastLoginDevice { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,40 @@
using LMS.Repository.User;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace LMS.Repository.DTO.UserDto
{
public class UserDto : UserBaseDto
{
public List<string> RoleNames { get; set; } = [];
/// <summary>
/// 允许使用的机器码数量
/// </summary>
public long AllDeviceCount { get; set; } = 1;
/// <summary>
/// 代理分成比例
/// </summary>
public double AgentPercent { get; set; } = 0.00;
/// <summary>
/// 免费修改机器码次数
/// </summary>
public long FreeCount { get; set; } = 0;
public DateTime CreatedDate { get; set; }
/// <summary>
/// 邀请码
/// </summary>
public string AffiliateCode { get; set; } = string.Empty;
public long ParentId { get; set; }
/// <summary>
/// 放一些操作信息 不能频繁的修改数据库使用的json格式存放
/// </summary>
public UserPrivateOptions Options { get; set; } = new UserPrivateOptions();
}
}

View File

@ -0,0 +1,27 @@
namespace LMS.Repository.Model
{
public class OpenAI
{
public class RequestMessage
{
/// <summary>
/// 请求的角色
/// </summary>
public string role { get; set; }
/// <summary>
/// 请求的消息
/// </summary>
public string content { get; set; }
}
/// <summary>
/// 请求失败,返回错误信息
/// </summary>
public class ErrorResponse
{
public string Error { get; set; }
public string Message { get; set; }
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Prompt\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LMS.Tools\LMS.Tools.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations;
using static LMS.Common.Enums.MachineEnum;
namespace LMS.Repository.Models.Machine
{
public class MachineModel
{
/// <summary>
/// 机器ID
/// </summary>
[Required]
public string MachineId { get; set; }
/// <summary>
/// 停用时间
/// </summary>
public DateTime? DeactivationTime { get; set; }
/// <summary>
/// 使用状态(试用,永久)
/// </summary>
[Required]
public MachineUseStatus UseStatus { get; set; }
/// <summary>
/// 当前机器的状态(激活,冻结)
/// </summary>
[Required]
public MachineStatus Status { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
/// <summary>
/// 所属用户ID
/// </summary>
public long? UserId { get; set; }
}
}

View File

@ -0,0 +1,47 @@
using System.ComponentModel.DataAnnotations;
using static LMS.Common.Enums.PermissionEnum;
namespace LMS.Repository.RequestModel.Permission
{
public class PermissionModel
{
/// <summary>
/// 角色ID
/// </summary>
public long? RoleId { get; set; }
/// <summary>
/// 用户的ID
/// </summary>
public long? UserId { get; set; }
/// <summary>
/// 机器码的ID是ID不是机器码
/// </summary>
public string? MachineId { get; set; }
/// <summary>
/// 权限类型的ID子权限
/// </summary>
[Required]
public List<string> PermissionTypeIds { get; set; }
/// <summary>
/// 权限对应的Code
/// </summary>
[Required]
public string PermissionCode { get; set; }
/// <summary>
/// 权限类型
/// </summary>
[Required]
public PType Type { get; set; }
/// <summary>
/// 备注
/// </summary>
public string? Remark { get; set; }
}
}

View File

@ -0,0 +1,35 @@
using static LMS.Common.Enums.PermissionEnum;
using System.ComponentModel.DataAnnotations;
namespace LMS.Repository.Models.Promission
{
public class PermissionTypeModel
{
/// <summary>
/// 权限的名字
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 权限的分类user和machine
/// </summary>
[Required]
public PermissionType Type { get; set; }
/// <summary>
/// 表示权限的代码
/// </summary>
[Required]
public string Code { get; set; }
/// <summary>
/// 权限的描述
/// </summary>
public string? Remark { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.Repository.RSAKey
{
public class RSAKeyGenerateModel
{
public string PublicKey { get; set; }
public string EncryptedPrivateKey { get; set; }
public string EncryptedKey { get; set; }
public string EncryptionIV { get; set; }
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.Repository.Role
{
public class RoleModel
{
[Required]
public string Name { get; set; }
public string Remark { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,31 @@
namespace LMS.Repository.Models.User
{
/// <summary>
/// 用户登录的类型
/// </summary>
public enum LoginType
{
Username = 1, Email = 0,
PhoneNumber = 2,
}
/// <summary>
/// 用户登录的实体类
/// </summary>
public class LoginModel
{
public string? UserName { get; set; }
public string? Email { get; set; }
public LoginType LoginType { get; set; } = 0;
public required string Password { get; set; }
public required string TokenId { get; set; }
public bool RememberMe { get; set; } = false;
public required string DeviceInfo { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.Repository.User
{
public class PublicKeyModel
{
public required string ClientPublicKey { get; set; }
}
public class PublicKeyResponse
{
public required string Token { get; set; }
public required string PublicKey { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace LMS.Repository.Models.User
{
public class RefreshTokenModel
{
public string refreshToken { get; set; }
public required long UserId { get; set; }
public required string DeviceInfo { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace LMS.Repository.Models.User
{
public class RegisterModel
{
[Required]
public required string UserName { get; set; }
public string? Email { get; set; }
[Required]
public required string Password { get; set; }
[Required]
public required string TokenId { get; set; }
[Required]
public required string AffiliateCode { get; set; }
}
}

View File

@ -0,0 +1,14 @@
namespace LMS.Repository.User
{
public class UpdatedUserModel
{
public double? AgentPercent { get; set; }
public int? AllDeviceCount { get; set; }
public string? Email { get; set; }
public int? FreeCount { get; set; }
public string? NickName { get; set; }
public string? UserName { get; set; }
public string? PhoneNumber { get; set; }
public List<string>? RoleNames { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS.Repository.User;
public class UserPrivateOptions
{
}

View File

@ -0,0 +1,15 @@
namespace LMS.Tools.Extensions
{
public class BeijingTimeExtension
{
/// <summary>
/// 获取北京时间,将时区转换为北京时间
/// </summary>
/// <returns></returns>
public static DateTime GetBeijingTime()
{
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow,
TimeZoneInfo.FindSystemTimeZoneById("China Standard Time"));
}
}
}

View File

@ -0,0 +1,44 @@
namespace LMS.Tools.Extensions
{
public class ConvertExtension
{
/// <summary>
/// 将字符串转换为long默认或者是转换错误返回0
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static long ObjectToLong(object obj)
{
if (obj == null)
return 0;
if (obj is long longValue)
return longValue;
if (obj is int intValue)
return intValue;
if (obj is string strValue)
{
if (long.TryParse(strValue, out long result))
return result;
}
// 处理其他数值类型
if (obj is IConvertible convertible)
{
try
{
return convertible.ToInt64(System.Globalization.CultureInfo.InvariantCulture);
}
catch
{
// 转换失败返回0
return 0;
}
}
return 0; // 默认返回0
}
}
}

View File

@ -0,0 +1,74 @@
using LMS.Common.Attributes;
using static LMS.Common.Enums.PermissionEnum;
namespace LMS.Tools
{
public static class EnumExtensions
{
/// <summary>
/// 判断是否为有效的权限类型
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static bool IsValidPermissionType(Type enumType, object value)
{
if (enumType == null || !enumType.IsEnum)
{
return false;
}
if (value == null)
{
return false;
}
// 处理整数类型
if (value is int intValue)
{
return Enum.IsDefined(enumType, intValue);
}
// 处理字符串类型
if (value is string stringValue)
{
return Enum.TryParse(enumType, stringValue, true, out _);
}
// 如果不是整数或字符串,尝试转换为枚举底层类型
try
{
var underlyingType = Enum.GetUnderlyingType(enumType);
var convertedValue = Convert.ChangeType(value, underlyingType);
return Enum.IsDefined(enumType, convertedValue);
}
catch
{
return false;
}
}
/// <summary>
/// 获取对应的枚举的描述
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string GetDescription(this Enum value)
{
var field = value.GetType().GetField(value.ToString());
var attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
}
/// <summary>
/// 获取对应的枚举的结果
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string GetResult(this Enum value)
{
var field = value.GetType().GetField(value.ToString());
var attribute = Attribute.GetCustomAttribute(field, typeof(ResultAttribute)) as ResultAttribute;
return attribute == null ? value.ToString() : attribute.Result;
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LMS.Common\LMS.Common.csproj" />
</ItemGroup>
</Project>

30
LMS.service/.dockerignore Normal file
View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

63
LMS.service/.gitattributes vendored Normal file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

364
LMS.service/.gitignore vendored Normal file
View File

@ -0,0 +1,364 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/Properties/launchSettings.json

View File

@ -0,0 +1,174 @@
using LMS.Tools;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service
{
public class APIResponseModel<T>
{
public APIResponseModel()
{
Code = (int)ResponseCode.Success;
Message = string.Empty;
Data = default(T);
}
/// <summary>
/// 返回的码 01
/// </summary>
public int Code { get; set; }
/// <summary>
/// 返回的信息,成功或者失败的信息
/// </summary>
public string? Message { get; set; }
/// <summary>
/// 返回的数据,可以是任何类型
/// </summary>
public object? Data { get; set; }
/// <summary>
/// 创建返回消息
/// </summary>
/// <param name="code">返回码</param>
/// <param name="data">返回数据</param>
/// <param name="message">返回消息</param>
/// <returns></returns>
public static APIResponseModel<T> CreateResponseModel(ResponseCode code, T data)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = code.GetResult(),
Data = data
};
}
/// <summary>
/// 创建正常的返回数据
/// </summary>
/// <param name="data">返回的数据</param>
/// <param name="message">返回成功的消息</param>
/// <returns></returns>
public static APIResponseModel<T> CreateSuccessResponseModel(ResponseCode code, T data)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = code.GetResult(),
Data = data
};
}
/// <summary>
/// 创建一个返回成功的消息
/// </summary>
/// <param name="data"></param>
/// <param name="message"></param>
/// <returns></returns>
public static APIResponseModel<T> CreateSuccessResponseModel(T data, string message)
{
return new APIResponseModel<T>
{
Code = (int)ResponseCode.Success,
Message = message,
Data = data
};
}
/// <summary>
/// 创建一个返回成功的消息
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static APIResponseModel<T> CreateSuccessResponseModel(T data)
{
return new APIResponseModel<T>
{
Code = (int)ResponseCode.Success,
Message = "请求成功",
Data = data
};
}
/// <summary>
/// 创建正常的返回数据
/// </summary>
/// <param name="data">返回的数据</param>
/// <returns></returns>
public static APIResponseModel<T> CreateSuccessResponseModel(ResponseCode code)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = code.GetResult(),
Data = null
};
}
/// <summary>
/// 返回错误消息
/// </summary>
/// <param name="code">错误的码</param>
/// <param name="data">返回的数据</param>
/// <returns></returns>
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code, T data)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = code.GetResult(),
Data = data
};
}
/// <summary>
/// 返回错误消息
/// </summary>
/// <param name="code">错误的码</param>
/// <param name="data">返回的数据</param>
/// <param name="message">返回的错误消息</param>
/// <returns></returns>
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code, T data, string message)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = message,
Data = data
};
}
/// <summary>
/// 创建一个错误的返回数据,只有错误消息
/// </summary>
/// <param name="code">错误的码</param>
/// <returns></returns>
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = code.GetResult(),
Data = null
};
}
/// <summary>
/// 创建一个错误的返回数据,只有错误消息
/// </summary>
/// <param name="code">错误的码</param>
/// <param name="message">错误消息提示</param>
/// <returns></returns>
public static APIResponseModel<T> CreateErrorResponseModel(ResponseCode code, string message)
{
return new APIResponseModel<T>
{
Code = (int)code,
Message = message,
Data = null
};
}
}
}

View File

@ -0,0 +1,19 @@
namespace LMS.service.Configuration
{
public static class AddCorsConifg
{
public static void AddCorsServices(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace Lai_server.Configuration
{
public static class AuthenticationExtensions
{
public static void AddJWTAuthentication(this IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
string secKeyEnv = Environment.GetEnvironmentVariable("SecKey");
byte[] keyBytes = Encoding.UTF8.GetBytes(secKeyEnv);
var secKey = new SymmetricSecurityKey(keyBytes);
x.TokenValidationParameters = new()
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = secKey
};
});
}
}
}

View File

@ -0,0 +1,31 @@
using AutoMapper;
using LMS.Repository.DTO;
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.Machine;
using LMS.Repository.Models.Promission;
using LMS.Repository.RequestModel.Permission;
using static LMS.Repository.DTO.MachineResponse.MachineDto;
namespace Lai_server.Configuration
{
public class AutoMapperConfig : Profile
{
public AutoMapperConfig()
{
CreateMap<Machine, MachineStatusResponse>();
CreateMap<Machine, MachineDetailDto>();
CreateMap<MachineModel, Machine>();
CreateMap<PermissionTypeModel, PermissionType>();
CreateMap<PermissionModel, Permission>();
CreateMap<User, UserDto>();
CreateMap<User, UserBaseDto>();
}
}
}

View File

@ -0,0 +1,10 @@
namespace Lai_server.Configuration
{
public class JWTSetting
{
public string SecKey { get; set; }
public int ExpireMinutes { get; set; }
}
}

View File

@ -0,0 +1,66 @@

using LMS.Common.RSAKey;
using LMS.DAO;
using LMS.Repository.DB;
using LMS.Tools.Extensions;
using Microsoft.EntityFrameworkCore;
namespace LMS.service.Configuration
{
public class RsaConfigurattions(IServiceProvider serviceProvider) : IHostedService
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
try
{
int count = await dbContext.RsaKeys.CountAsync(cancellationToken: cancellationToken);
// 先删除无效的RSA数据
List<RsaKeys> rsaKeys = await dbContext.RsaKeys.Where(x => x.UseCount > 50 || x.ExpirationTime < BeijingTimeExtension.GetBeijingTime()).ToListAsync(cancellationToken: cancellationToken);
if (rsaKeys.Count > 0)
{
dbContext.RsaKeys.RemoveRange(rsaKeys);
}
// 新增 保证数据库中有指定数量的RSA数据
int AddCount = 20 - (count - rsaKeys.Count);
for (int i = 0; i < AddCount; i++)
{
RSAKeyGenerateModel rSAKeyGenerate = RsaKeyPairGenerator.InitRsaKey();
await dbContext.RsaKeys.AddAsync(new RsaKeys
{
PublicKey = rSAKeyGenerate.PublicKey,
EncryptedPrivateKey = rSAKeyGenerate.EncryptedPrivateKey,
EncryptionKey = rSAKeyGenerate.EncryptedKey,
EncryptionIV = rSAKeyGenerate.EncryptionIV,
UseCount = 0,
ExpirationTime = BeijingTimeExtension.GetBeijingTime().AddDays(10),
Id = Guid.NewGuid().ToString(),
}, cancellationToken);
}
await dbContext.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch (Exception ex)
{
await transaction.RollbackAsync(cancellationToken);
// 根据需要处理异常,例如终止应用程序
Console.WriteLine(ex.Message);
Environment.Exit(1);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,39 @@
using LMS.DAO.MachineDAO;
using LMS.DAO.PermissionDAO;
using LMS.DAO.RoleDAO;
using LMS.DAO.UserDAO;
using LMS.service.Configuration;
using LMS.service.Service;
using LMS.service.Service.PermissionService;
using LMS.service.Service.RoleService;
using LMS.service.Service.UserService;
namespace Lai_server.Configuration
{
public static class ServiceConfiguration
{
public static void AddServices(this IServiceCollection services)
{
// 注入Service
services.AddScoped<RsaConfigurattions>();
// 注入DDL
services.AddScoped<LoginService>();
services.AddScoped<SecurityService>();
services.AddScoped<MachineService>();
services.AddScoped<PermissionService>();
services.AddScoped<PremissionValidationService>();
services.AddScoped<RoleService>();
services.AddScoped<UserService>();
// 注入 DAO
services.AddScoped<UserBasicDao>();
services.AddScoped<RoleBasicDao>();
services.AddScoped<MachineBasicDao>();
services.AddScoped<PermissionBasicDao>();
services.AddScoped<PermissionTypeDao>();
}
}
}

View File

@ -0,0 +1,31 @@
using Microsoft.OpenApi.Models;
namespace Lai_server.Configuration
{
public static class SwaggerConfiguration
{
public static void AddSwaggerService(this IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
var scheme = new OpenApiSecurityScheme()
{
Description = "Authorization header. \r\nExample: 'Bearer 12345abcdef'",
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Authorization"
},
Scheme = "oauth2",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
};
c.AddSecurityDefinition("Authorization", scheme);
var requirement = new OpenApiSecurityRequirement();
requirement[scheme] = new List<string>();
c.AddSecurityRequirement(requirement);
});
}
}
}

View File

@ -0,0 +1,150 @@
using LMS.Repository.DTO;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.Machine;
using LMS.service.Service;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using static LMS.Common.Enums.MachineEnum;
using static LMS.Common.Enums.ResponseCodeEnum;
using static LMS.Repository.DTO.MachineResponse.MachineDto;
namespace LMS.service.Controllers
{
[Route("lms/[controller]/[action]")]
[ApiController]
public class MachineController(MachineService machineService) : ControllerBase
{
private readonly MachineService _machineService = machineService;
#region
/// <summary>
/// 获取指定机器码的状态
/// </summary>
/// <param name="machineId"></param>
/// <returns></returns>
[HttpGet("{machineId}")]
public async Task<ActionResult<APIResponseModel<MachineStatusResponse>>> GetMachineStatus(string machineId)
{
return await _machineService.GetMachineStatus(machineId);
}
#endregion
#region
/// <summary>
/// 修改当前机器码
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> ModifyMachine(string id, [FromBody] MachineModel request)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.ModifyMachine(id, request, userId);
}
#endregion
#region
/// <summary>
/// 添加机器码
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> AddMachine([FromBody] MachineModel request)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.AddMachine(request, userId);
}
#endregion
#region
/// <summary>
/// 通过查询条件获取机器码列表
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="machineId"></param>
/// <param name="CreatedUserName"></param>
/// <param name="status"></param>
/// <param name="useStatus"></param>
/// <param name="Remark"></param>
/// <param name="ownUserName"></param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<ActionResult<APIResponseModel<CollectionResponse<Machine>>>> QueryMachineCollection([Required] int page, [Required] int pageSize, string? machineId, string? createdUserName, MachineStatus? status, MachineUseStatus? useStatus, string? remark, string? ownUserName)
{
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.QueryMachineCollection(page, pageSize, machineId, createdUserName, status, useStatus, remark, ownUserName, requestUserId);
}
#endregion
#region
[HttpGet("{Id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<MachineDetailDto>>> GetMachineDetail(string Id)
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.GetMachineDetail(Id, userId);
}
#endregion
#region
[HttpPost("{Id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> DeactivateMachine(string Id)
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.DeactivateMachine(Id, userId);
}
#endregion
#region 使
[HttpPost("{Id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> UpgradeMachine(string Id)
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.UpgradeMachine(Id, userId);
}
#endregion
#region
/// <summary>
/// 删除机器码
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<object>>> DeleteMachine(string id)
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _machineService.DeleteMachine(id, userId);
}
#endregion
}
}

View File

@ -0,0 +1,134 @@
using LMS.Repository.Models.Promission;
using LMS.Repository.RequestModel.Permission;
using LMS.service;
using LMS.service.Service.PermissionService;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace Lai_server.Controllers
{
[Route("lms/[controller]/[action]")]
[ApiController]
public class PermissionController : ControllerBase
{
private readonly PermissionService _permissionService;
public PermissionController(PermissionService permissionService)
{
_permissionService = permissionService;
}
#region
/// <summary>
/// 创建权限类型
/// </summary>
/// <param name="permissionType"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<ActionResult<APIResponseModel<object>>> AddPermissionType([FromBody] PermissionTypeModel permissionType)
{
if (!ModelState.IsValid)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _permissionService.AddPermissionType(permissionType, userId);
}
/// <summary>
/// 新增权限类型
/// </summary>
/// <param name="permissionType"></param>
/// <returns></returns>
[HttpPost("{permissionId}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<object>>> ModifyPermissionType(string permissionId, [FromBody] PermissionTypeModel permissionType)
{
if (!ModelState.IsValid)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _permissionService.ModifyPermissionType(permissionId, permissionType, userId);
}
/// <summary>
/// 删除权限类型
/// </summary>
/// <param name="id"></param>
/// <param name="relation"></param>
/// <returns></returns>
[HttpDelete("{id}/{relation}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<object>>> DeletePermissionType(string id, bool relation)
{
if (!ModelState.IsValid)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _permissionService.DeletePermissionType(id, relation, userId);
}
#endregion
#region
/// <summary>
/// 添加对应的权限
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<ActionResult<APIResponseModel<object>>> AddPermission([FromBody] PermissionModel model)
{
if (!ModelState.IsValid)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _permissionService.AddPermission(model, userId);
}
///// <summary>
///// 修改权限
///// </summary>
///// <param name="request"></param>
///// <returns></returns>
[HttpPost("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<object>>> ModfiyPermission(string id, [FromBody] PermissionModel request)
{
if (!ModelState.IsValid)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _permissionService.ModfiyPermission(id, request, userId);
}
/// <summary>
/// 删除权限
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> DeletePermission(string Id)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _permissionService.DeletePermission(Id, userId);
}
#endregion
}
}

View File

@ -0,0 +1,107 @@
using LMS.Repository.DTO;
using LMS.Repository.Models.DB;
using LMS.Repository.Role;
using LMS.service.Service.RoleService;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Controllers
{
[Route("lms/[controller]/[action]")]
public class RoleController(RoleService roleService) : ControllerBase
{
private readonly RoleService _roleService = roleService;
/// <summary>
/// 获取角色列表数据
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="roleName"></param>
/// <param name="roleId"></param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<ActionResult<APIResponseModel<CollectionResponse<RoleDto>>>> QueryRoleCollection([Required] int page, [Required] int pageSize, string? roleName, long? roleId)
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _roleService.QueryRoleCollection(page, pageSize, roleName, roleId, userId);
}
/// <summary>
/// 获取所有的角色的数据对应的Name用作下拉框的数据
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<ActionResult<APIResponseModel<List<string>>>> QueryRoleOption()
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _roleService.QueryRoleOption(userId);
}
/// <summary>
/// 通过指定Id获取角色
/// </summary>
/// <param name="Id"></param>
/// <returns></returns>
[HttpGet("{Id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<RoleDto>>> QueryRoleById(long Id)
{
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _roleService.QueryRoleById(Id, userId);
}
/// <summary>
/// 添加角色
/// </summary>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> AddRole([FromBody] RoleModel model)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _roleService.AddRole(model, userId);
}
/// <summary>
/// 修改角色
/// </summary>
/// <param name="id"></param>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> UpdateRole(long id, [FromBody] RoleModel model)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _roleService.UpdateRole(id, model, userId);
}
/// <summary>
/// 删除角色
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> DeleteRole(long id)
{
return await _roleService.DeleteRole(id);
}
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
namespace LMS.service.Controllers
{
[Route("lms/[controller]/[action]")]
public class SoftWareController : Controller
{
//public Index()
//{
// var version = Assembly.GetExecutingAssembly().GetName().Version;
// Console.WriteLine($"Version: {version}");
//}
}
}

View File

@ -0,0 +1,225 @@
using LMS.DAO;
using LMS.Repository.DTO;
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.User;
using LMS.Repository.User;
using LMS.service.Service.UserService;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Controllers
{
[Route("lms/[controller]/[action]")]
public class UserController(ApplicationDbContext context, UserManager<User> userManager, LoginService loginService, SecurityService securityService, UserService userService) : ControllerBase
{
private readonly ApplicationDbContext _context = context;
private readonly UserManager<User> _userManager = userManager;
public readonly LoginService _loginService = loginService;
private readonly SecurityService _securityService = securityService;
private readonly UserService _userService = userService;
/// <summary>
/// 存储密钥的 ConcurrentDictionary
/// </summary>
private static readonly ConcurrentDictionary<string, (string Key, DateTime Expiry)> _keyStore = new();
#region
/// <summary>
/// 获取公钥
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult<APIResponseModel<PublicKeyResponse>>> GetPublicKey()
{
if (!ModelState.IsValid)
{
return APIResponseModel<PublicKeyResponse>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
return await _securityService.GetPublicKey(_keyStore);
}
#endregion
#region
/// <summary>
/// 用户登录接口
/// </summary>
/// <param name="login"></param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult<APIResponseModel<object>>> Login([FromBody] LoginModel login)
{
if (!ModelState.IsValid)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
string? ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(ip))
{
ip = HttpContext.Connection.RemoteIpAddress?.ToString();
}
APIResponseModel<object> apiResponse = await _loginService.UserLogin(login, ip, _keyStore);
if (apiResponse.Code != 1)
{
return apiResponse;
}
// 这个添加Cokkie
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = false, // 如果使用 HTTPS
SameSite = SameSiteMode.None,
Expires = DateTime.UtcNow.AddDays(7),
};
Response.Cookies.Append("refreshToken", ((LoginService.LoginResponse)apiResponse.Data).RefreshToken, cookieOptions);
return apiResponse;
}
#endregion
#region
/// <summary>
/// 用户注册接口
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult<APIResponseModel<string>>> Register([FromBody] RegisterModel model)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _userService.Register(model, _keyStore);
}
#endregion
#region token
[HttpPost]
public async Task<ActionResult<APIResponseModel<string>>> RefreshToken([FromBody] RefreshTokenModel model)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
string? ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(ip))
{
ip = HttpContext.Connection.RemoteIpAddress?.ToString();
}
// 获取Cookie 的值
string? refreshToken = model.refreshToken;
if (string.IsNullOrWhiteSpace(refreshToken))
{
//Response.StatusCode = StatusCodes.Status401Unauthorized;
//await Response.WriteAsync("Access denied, RefreshToken is null");
// 返回权限错误
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.RefreshTokenInvalid);
}
else
{
return await _loginService.RefreshToken(model, ip, refreshToken);
}
}
#endregion
#region 退
#endregion
#region
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<UserDto>>> GetUserInfo(long id)
{
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _userService.GetUserInfo(id, requestUserId);
}
#endregion
#region
/// <summary>
/// 获取用户列表,可以使用查询条件 userName, userId, nickName, phoneNumber, email, roleNames
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="userName"></param>
/// <param name="userId"></param>
/// <param name="nickName"></param>
/// <param name="phoneNumber"></param>
/// <param name="email"></param>
/// <param name="roleNames"></param>
/// <returns></returns>
[HttpGet]
[Authorize]
public async Task<ActionResult<APIResponseModel<CollectionResponse<UserCollectionDto>>>> QueryUserCollection([Required] int page, [Required] int pageSize, string userName, long? userId, string nickName, string phoneNumber, string email, string[] roleNames, long? parentId)
{
long reuqertUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _userService.QueryUserCollection(page, pageSize, userName, userId, nickName, phoneNumber, email, roleNames, parentId, reuqertUserId);
}
#endregion
#region
[HttpPost("{id}")]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> UpdatedUser(long id, [FromBody] UpdatedUserModel userModel)
{
if (!ModelState.IsValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _userService.UpdatedUser(id, userModel, requestUserId);
}
#endregion
#region
[HttpPost]
[Authorize]
public async Task<ActionResult<APIResponseModel<string>>> EnableAgent()
{
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _loginService.EnableAgent(requestUserId);
}
#endregion
#region
[HttpGet]
[Authorize]
public async Task<ActionResult<APIResponseModel<UserAgentInfoDto>>> GetUserAgentInfo()
{
long requestUserId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
return await _userService.GetUserAgentInfo(requestUserId);
}
#endregion
}
}

View File

@ -0,0 +1,299 @@
// <auto-generated />
using LMS.DAO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LMS.service.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240912114032_inital")]
partial class inital
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("LMS.service.Models.UserModels.Role", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("LMS.service.Models.UserModels.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedDate")
.HasColumnType("datetime");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("LastLoginDate")
.HasColumnType("datetime");
b.Property<string>("LastLoginDevice")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("LastLoginIp")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NickName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("UpdatedDate")
.HasColumnType("datetime");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<long>("RoleId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<long>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<long>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<long>", b =>
{
b.Property<long>("UserId")
.HasColumnType("bigint");
b.Property<long>("RoleId")
.HasColumnType("bigint");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<long>", b =>
{
b.Property<long>("UserId")
.HasColumnType("bigint");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,265 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LMS.service.Data.Migrations
{
/// <inheritdoc />
public partial class inital : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
NormalizedName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ConcurrencyStamp = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
NickName = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedDate = table.Column<DateTime>(type: "datetime", nullable: false),
UpdatedDate = table.Column<DateTime>(type: "datetime", nullable: false),
LastLoginDate = table.Column<DateTime>(type: "datetime", nullable: false),
LastLoginIp = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
LastLoginDevice = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
UserName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
NormalizedUserName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
NormalizedEmail = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
EmailConfirmed = table.Column<bool>(type: "tinyint(1)", nullable: false),
PasswordHash = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
SecurityStamp = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ConcurrencyStamp = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PhoneNumber = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PhoneNumberConfirmed = table.Column<bool>(type: "tinyint(1)", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
LockoutEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<long>(type: "bigint", nullable: false),
ClaimType = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ClaimValue = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<long>(type: "bigint", nullable: false),
ClaimType = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ClaimValue = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ProviderKey = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ProviderDisplayName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
UserId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<long>(type: "bigint", nullable: false),
RoleId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<long>(type: "bigint", nullable: false),
LoginProvider = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Name = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Value = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -0,0 +1,300 @@
// <auto-generated />
using System;
using LMS.DAO;
using LMS.service.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace LMS.service.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("LMS.service.Models.UserModels.Role", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("LMS.service.Models.UserModels.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedDate")
.HasColumnType("datetime");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("LastLoginDate")
.HasColumnType("datetime");
b.Property<string>("LastLoginDevice")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("LastLoginIp")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NickName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("UpdatedDate")
.HasColumnType("datetime");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<long>("RoleId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<long>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<long>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<long>", b =>
{
b.Property<long>("UserId")
.HasColumnType("bigint");
b.Property<long>("RoleId")
.HasColumnType("bigint");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<long>", b =>
{
b.Property<long>("UserId")
.HasColumnType("bigint");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.Role", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<long>", b =>
{
b.HasOne("LMS.service.Models.UserModels.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

31
LMS.service/Dockerfile Normal file
View File

@ -0,0 +1,31 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
# ¸´ÖÆËùÓеÄÏîÄ¿Îļþ
COPY ["LMS.service/LMS.service.csproj", "LMS.service/"]
COPY ["LMS.Common/LMS.Common.csproj", "LMS.Common/"]
COPY ["LMS.DAO/LMS.DAO.csproj", "LMS.DAO/"]
COPY ["LMS.Repository/LMS.Repository.csproj", "LMS.Repository/"]
COPY ["LMS.Tools/LMS.Tools.csproj", "LMS.Tools/"]
RUN dotnet restore "LMS.service/LMS.service.csproj"
COPY . .
WORKDIR "/src/LMS.service"
RUN dotnet build "LMS.service.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "LMS.service.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "LMS.service.dll"]

View File

@ -0,0 +1,65 @@
using LMS.service.Service.PermissionService;
using Microsoft.AspNetCore.Routing;
using System.Security.Claims;
namespace LMS.service.Extensions.Middleware
{
public class DynamicPermissionMiddleware
{
private readonly RequestDelegate _next;
public DynamicPermissionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, PremissionValidationService _premissionValidationServices)
{
var endpoint = context.GetEndpoint();
var userId = GetUserIdFromContext(context); // 从JWT token或session中获取用户ID
if (userId == -1) // 判断用户ID是否有效
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
await context.Response.WriteAsync("用户参数校验错误");
return;
}
if (endpoint != null)
{
var httpMethod = context.Request.Method;
var path = (endpoint as RouteEndpoint)?.RoutePattern.RawText;
if (await _premissionValidationServices.HasPermissionForEndpoint(userId, httpMethod, path))
{
await _next(context);
}
else
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsync("Access denied");
}
}
else
{
await _next(context);
}
}
private long GetUserIdFromContext(HttpContext context)
{
var userIdClaim = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
var userId = userIdClaim?.Value;
if (!string.IsNullOrWhiteSpace(userId))
{
// 判断userId是否为数字
if (!long.TryParse(userId, out var result))
{
return -1;
}
context.Items["UserId"] = userId;
return Convert.ToInt64(userIdClaim?.Value);
}
return 0;
}
}
}

View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>ed64fb6f-9c93-43d0-b418-61f507f28420</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\Migrations\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LMS.Common\LMS.Common.csproj" />
<ProjectReference Include="..\LMS.DAO\LMS.DAO.csproj" />
<ProjectReference Include="..\LMS.Repository\LMS.Repository.csproj" />
<ProjectReference Include="..\LMS.Tools\LMS.Tools.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@LMS.service_HostAddress = http://localhost:5181
GET {{LMS.service_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,49 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LMS.service", "LMS.service.csproj", "{E6EDD426-2283-40EA-93EA-2C718FBD66CB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LMS.Tools", "..\LMS.Tools\LMS.Tools.csproj", "{2551EC61-E4DA-4DF8-BCCC-2D0456D61424}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LMS.Common", "..\LMS.Common\LMS.Common.csproj", "{AB8D7E1E-2AA9-45C1-9C9A-A549C9FCA231}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LMS.Repository", "..\LMS.Repository\LMS.Repository.csproj", "{ABE14686-E9D8-4FCD-9D1F-767EC82D5538}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LMS.DAO", "..\LMS.DAO\LMS.DAO.csproj", "{CC297416-0545-4416-9E9A-EA738DA931B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E6EDD426-2283-40EA-93EA-2C718FBD66CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6EDD426-2283-40EA-93EA-2C718FBD66CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6EDD426-2283-40EA-93EA-2C718FBD66CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6EDD426-2283-40EA-93EA-2C718FBD66CB}.Release|Any CPU.Build.0 = Release|Any CPU
{2551EC61-E4DA-4DF8-BCCC-2D0456D61424}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2551EC61-E4DA-4DF8-BCCC-2D0456D61424}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2551EC61-E4DA-4DF8-BCCC-2D0456D61424}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2551EC61-E4DA-4DF8-BCCC-2D0456D61424}.Release|Any CPU.Build.0 = Release|Any CPU
{AB8D7E1E-2AA9-45C1-9C9A-A549C9FCA231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB8D7E1E-2AA9-45C1-9C9A-A549C9FCA231}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB8D7E1E-2AA9-45C1-9C9A-A549C9FCA231}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB8D7E1E-2AA9-45C1-9C9A-A549C9FCA231}.Release|Any CPU.Build.0 = Release|Any CPU
{ABE14686-E9D8-4FCD-9D1F-767EC82D5538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABE14686-E9D8-4FCD-9D1F-767EC82D5538}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABE14686-E9D8-4FCD-9D1F-767EC82D5538}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABE14686-E9D8-4FCD-9D1F-767EC82D5538}.Release|Any CPU.Build.0 = Release|Any CPU
{CC297416-0545-4416-9E9A-EA738DA931B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC297416-0545-4416-9E9A-EA738DA931B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC297416-0545-4416-9E9A-EA738DA931B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC297416-0545-4416-9E9A-EA738DA931B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BD24726A-3AE2-4762-8EC5-1528B1A863E0}
EndGlobalSection
EndGlobal

104
LMS.service/Program.cs Normal file
View File

@ -0,0 +1,104 @@
using Lai_server.Configuration;
using LMS.DAO;
using LMS.Repository.Models.DB;
using LMS.service.Configuration;
using LMS.service.Extensions.Middleware;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddAuthentication();
// 添加跨域
builder.Services.AddCorsServices();
// 配置注入自定义服务方法
builder.Services.AddServices();
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.None;
});
//配置JWT
builder.Services.AddJWTAuthentication();
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
string connectionString = $"server={Environment.GetEnvironmentVariable("DATABASE_SERVER")};port={Environment.GetEnvironmentVariable("DATABASE_PORT")};user={Environment.GetEnvironmentVariable("DATABASE_USER")};password={Environment.GetEnvironmentVariable("DATABASE_PASSWORD")};database={Environment.GetEnvironmentVariable("DATABASE_NAME")};ConvertZeroDateTime=True;SslMode={Environment.GetEnvironmentVariable("DATABASE_SSL_MODE") ?? "None"};";
options.UseMySql(connectionString, ServerVersion.Parse("8.0.18-mysql"));
});
builder.Services.AddIdentityCore<User>(options =>
{
options.SignIn.RequireConfirmedAccount = true; //已有账号才能登录
options.SignIn.RequireConfirmedEmail = true; //
options.Password.RequireDigit = true; // 数据库中至少有一个数字
options.Password.RequireLowercase = true; // 数据库中至少有一个小写字母
options.Password.RequireUppercase = true; // 数据库中至少有一个大写字母
options.Password.RequireNonAlphanumeric = true; // 数据库中至少有一个特殊字符
options.Password.RequiredLength = 8; // 密码长度最少8位
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // 锁定时间
options.Lockout.MaxFailedAccessAttempts = 10; // 尝试次数
options.Lockout.AllowedForNewUsers = true; // 新用户是否可以锁定
options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; // 用户名允许的字符
options.User.RequireUniqueEmail = true; // 允许重复邮箱
//options.User.
});
var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), builder.Services);
idBuilder.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddUserManager<UserManager<User>>()
.AddRoleManager<RoleManager<Role>>();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// 注入Swagger
builder.Services.AddSwaggerService();
builder.Services.AddHostedService<RsaConfigurattions>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseWhen(context => context.Request.Path.Value == "/", builder =>
builder.Run(context =>
{
context.Response.Redirect("/swagger");
return Task.CompletedTask;
}));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseMiddleware<DynamicPermissionMiddleware>();
app.UseEndpoints(endpoints =>
{
_ = endpoints.MapControllers();
});
app.Run();

View File

@ -0,0 +1,712 @@
using AutoMapper;
using LMS.DAO;
using LMS.Repository.DTO;
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.Machine;
using LMS.Tools;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static LMS.Common.Enums.MachineEnum;
using static LMS.Common.Enums.ResponseCodeEnum;
using static LMS.Repository.DTO.MachineResponse.MachineDto;
using Machine = LMS.Repository.Models.DB.Machine;
namespace LMS.service.Service
{
public class MachineService
{
private readonly ApplicationDbContext _context;
private readonly UserManager<User> _userManager;
private readonly IMapper _mapper;
public MachineService(ApplicationDbContext context, IMapper mapper, UserManager<User> userManager)
{
_context = context;
_mapper = mapper;
_userManager = userManager;
}
#region Private Methods
/// <summary>
/// 判断当前机器码所属的用户是不是可以添加永久机器码
/// 检查用户是不是VIP用户
/// 检查当前用户是不是已经拥有了最大的数量的机器码
/// 检查当前用户是不是还有免费更换次数
/// </summary>
/// <param name="ownerUser"></param>
/// <param name="machine"></param>
/// <returns></returns>
private async Task<APIResponseModel<string>> CanAddPermanentMachine(User ownerUser, long userId, string? id, bool checkVip)
{
// 判断是不是VIP
if (checkVip && !await _userManager.IsInRoleAsync(ownerUser, "Vip User"))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.UserNotVip);
}
// 判断是不是已经有正在使用的机器码
List<Machine> usingMachine = await _context.Machine.Where(x => x.UserID == userId && x.Status == MachineStatus.Active && (x.DeactivationTime == null || x.DeactivationTime >= BeijingTimeExtension.GetBeijingTime()) && (id == null || x.Id != id)).ToListAsync();
// 判断是不是超过了用户允许的最大机器码数量
if ((usingMachine.Count + 1) > ownerUser.AllDeviceCount)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前用户正在绑定的机器码数量超过最大数量");
}
// 判断免费更换次数
if (ownerUser.FreeCount <= 0)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前用户更换机器码的次数已用完");
}
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success);
}
#endregion
#region
/// <summary>
/// 添加机器码
/// </summary>
/// <param name="request"></param>
/// <param name="reqId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<string>>> AddMachine(MachineModel request, long reqId)
{
using var transaction = _context.Database.BeginTransaction();
try
{
// 新增
// 判断当前用户是否有新增和管理机器码的权限
//if (!await _context.Permission.AnyAsync(x => x.UserId == reqId && (x.PermissionCode == SubPermissionType.ManageMachine || x.PermissionCode == SubPermissionType.AddMachine)))
//{
// return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
//}
// 请求方法的用户
User? user = await _userManager.FindByIdAsync(reqId.ToString());
if (user == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断当前机器码是否已经存在
if (await _context.Machine.AnyAsync(x => x.MachineId == request.MachineId))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.MachineAlreadyExist);
}
if (request.Status == MachineStatus.Frozen)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "新增机器码不能直接停用");
}
if (request.UserId == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
// 判断OwnerUserId是不是还能添加
User? ownerUser = await _userManager.FindByIdAsync(request.UserId.ToString());
if (ownerUser == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断是不是管理员,不是管理员,只能添加自己的机器码
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Admin") || await _userManager.IsInRoleAsync(user, "Super Admin");
if (!isAdminOrSuperAdmin && ownerUser.Id != reqId)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
if (!EnumExtensions.IsValidPermissionType(typeof(MachineUseStatus), request.UseStatus))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "机器码使用状态不正确");
}
if (!EnumExtensions.IsValidPermissionType(typeof(MachineStatus), request.Status))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "机器码状态不正确");
}
// 判断是不是VIP不是VIP的话判断之不是已经存在机器码
if (!await _userManager.IsInRoleAsync(ownerUser, "VIP User"))
{
if (await _context.Machine.AnyAsync(x => x.UserID == ownerUser.Id))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "普通用户只能添加一个机器码");
}
}
if (request.UseStatus == MachineUseStatus.Trial)
{
var checkRes = await CanAddPermanentMachine(ownerUser, (long)request.UserId, null, false);
if (checkRes.Code != 1)
{
return checkRes;
}
// 必传停用时间
if (request.DeactivationTime == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "试用机器码必须传入停用时间");
}
request.DeactivationTime = request.DeactivationTime?.AddHours(8);
// 判断停用时间是不是少于当前的
if (request.DeactivationTime < BeijingTimeExtension.GetBeijingTime())
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "到期时间不能小于当前时间");
}
Console.WriteLine(BeijingTimeExtension.GetBeijingTime().ToString());
int s = (request.DeactivationTime - BeijingTimeExtension.GetBeijingTime()).Value.Days;
// 判断当前时间和现在的时间差大于三天,报错
if ((request.DeactivationTime - BeijingTimeExtension.GetBeijingTime()).Value.Days >= 3)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "到期时间不能超过三天");
}
// 先修改用户的免费更换次数
ownerUser.FreeCount -= 1;
_context.Users.Update(ownerUser);
}
else
{
var checkRes = await CanAddPermanentMachine(ownerUser, (long)request.UserId, null, true);
if (checkRes.Code != 1)
{
return checkRes;
}
// 先修改用户的免费更换次数
ownerUser.FreeCount -= 1;
_context.Users.Update(ownerUser);
request.DeactivationTime = null;
}
//开始新增
Machine machine = _mapper.Map<Machine>(request);
machine.Id = Guid.NewGuid().ToString();
machine.CreateId = reqId;
machine.UpdateId = reqId;
machine.CreateTime = BeijingTimeExtension.GetBeijingTime();
machine.UpdateTime = BeijingTimeExtension.GetBeijingTime();
await _context.Machine.AddAsync(machine);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 删除机器码
/// </summary>
/// <param name="machineId"></param>
/// <param name="userId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<object>>> DeleteMachine(string id, long userId)
{
try
{
Machine? machine = await _context.Machine.FirstOrDefaultAsync(x => x.Id == id);
if (machine == null)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindMachineByIdFail);
}
// 开始删除
_context.Machine.Remove(machine);
await _context.SaveChangesAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 获取机器状态(是否停用和截至时间)
/// </summary>
/// <param name="machineId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<MachineStatusResponse>>> GetMachineStatus(string machineId)
{
try
{
// 获取对应的machine
var machine = await _context.Machine.FirstOrDefaultAsync(x =>
x.MachineId == machineId &&
((x.DeactivationTime == null && x.UseStatus == MachineUseStatus.Permanent && x.Status == MachineStatus.Active)
|| (x.DeactivationTime != null && x.UseStatus == MachineUseStatus.Trial && DateTime.Now < x.DeactivationTime && x.Status == MachineStatus.Active)));
if (machine == null)
{
return APIResponseModel<MachineStatusResponse>.CreateErrorResponseModel(ResponseCode.MachineNotFound);
}
MachineStatusResponse machineStatus = _mapper.Map<MachineStatusResponse>(machine);
return APIResponseModel<MachineStatusResponse>.CreateSuccessResponseModel(ResponseCode.Success, machineStatus);
}
catch (Exception e)
{
return APIResponseModel<MachineStatusResponse>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 新增/修改机器状态
/// </summary>
/// <param name="request">请求体</param>
/// <param name="reqId">请求ID</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<string>>> ModifyMachine(string machineId, MachineModel request, long reqId)
{
using var transaction = _context.Database.BeginTransaction();
try
{
// 修改
// 判断当前用户是否有修改和管理机器码的权限
//if (!await _context.Permission.AnyAsync(x => x.UserId == reqId && (x.PermissionCode == SubPermissionType.ManageMachine || x.PermissionCode == SubPermissionType.ModifyMachine)))
//{
// return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
//}
// 判断传入的userId是否存在
User? user = await _userManager.FindByIdAsync(reqId.ToString());
if (user == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断当前ID是不是存在
var machine = await _context.Machine.FirstOrDefaultAsync(x => x.Id == machineId);
if (machine == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindMachineByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Admin") || await _userManager.IsInRoleAsync(user, "Super Admin");
if (!isAdminOrSuperAdmin)
{
if (reqId != machine.UserID || request.MachineId != machine.MachineId)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
if (machine.MachineId != request.MachineId)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "机器码不能修改");
}
}
// 判断当前修改后的机器码是否已经存在(不包含自己)
if (await _context.Machine.AnyAsync(x => x.MachineId == request.MachineId && x.Id != machineId))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.MachineAlreadyExist);
}
// 将标准时间转换为北京时间
request.DeactivationTime = request.DeactivationTime?.AddHours(8);
// 判断是不是永久改试用
if (machine.UseStatus == MachineUseStatus.Permanent && request.UseStatus == MachineUseStatus.Trial)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "机器码不能永久改试用");
}
if (!isAdminOrSuperAdmin)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
// 试用,除了管理员之外不能修改
if (request.UseStatus == MachineUseStatus.Trial)
{
// 判断结束时间是不是少于当前的
if (request.DeactivationTime != null && request.DeactivationTime < BeijingTimeExtension.GetBeijingTime())
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "到期时间不能小于当前时间");
}
// 判断当前时间和现在的时间差大于三天,报错
if (request.DeactivationTime != null && (request.DeactivationTime - BeijingTimeExtension.GetBeijingTime()).Value.Days > 3)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "到期时间不能超过三天");
}
}
else if (request.UseStatus == MachineUseStatus.Permanent)
{
request.DeactivationTime = null;
User? ownerUser = await _userManager.FindByIdAsync(machine.UserID.ToString());
if (ownerUser == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断是不是充试用升级到永久,这边要添加一些判断
if (machine.UseStatus == MachineUseStatus.Trial)
{
var checkRes = await CanAddPermanentMachine(ownerUser, machine.UserID, machine.Id, true);
if (checkRes.Code != 1)
{
return checkRes;
}
// 先修改用户的免费更换次数
ownerUser.FreeCount -= 1;
await _userManager.UpdateAsync(ownerUser);
}
// 判断是不是重新激活
if (request.Status == MachineStatus.Active && machine.Status == MachineStatus.Frozen)
{
var checkRes = await CanAddPermanentMachine(ownerUser, machine.UserID, machine.Id, true);
if (checkRes.Code != 1)
{
return checkRes;
}
// 先修改用户的免费更换次数
ownerUser.FreeCount -= 1;
await _userManager.UpdateAsync(ownerUser);
}
}
// 开始修改
machine.UpdateId = reqId;
machine.UpdateTime = BeijingTimeExtension.GetBeijingTime();
if (request.DeactivationTime != null)
{
machine.DeactivationTime = request.DeactivationTime;
}
if (request.Status == MachineStatus.Active && request.UseStatus == MachineUseStatus.Permanent)
{
machine.DeactivationTime = null;
}
machine.UseStatus = request.UseStatus;
machine.MachineId = request.MachineId;
machine.Status = request.Status;
machine.Remark = request.Remark;
_context.Machine.Update(machine);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 查询机器码列表
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="machineId"></param>
/// <param name="createdUserName"></param>
/// <param name="status"></param>
/// <param name="useStatus"></param>
/// <param name="remark"></param>
/// <param name="ownUserName"></param>
/// <param name="requestUserId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<CollectionResponse<Machine>>>> QueryMachineCollection(int page, int pageSize, string? machineId, string? createdUserName, MachineStatus? status, MachineUseStatus? useStatus, string? remark, string? ownUserName, long requestUserId)
{
try
{
User? user = await _userManager.FindByIdAsync(requestUserId.ToString());
if (user == null)
{
return APIResponseModel<CollectionResponse<Machine>>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin");
bool isAdmin = await _userManager.IsInRoleAsync(user, "Admin");
IQueryable<Machine> query = _context.Machine;
if (isAdmin)
{
List<long> superAdminUserIds = ((List<User>)await _userManager.GetUsersInRoleAsync("Super Admin")).Select(x => x.Id).ToList();
//.Result.Select(x => x.Id).ToList();
query = query.Where(x => !superAdminUserIds.Contains(x.UserID));
}
else if (!isSuperAdmin)
{
query = query.Where(x => x.UserID == requestUserId);
}
// 添加其他的查询条件
if (!string.IsNullOrWhiteSpace(machineId))
{
query = query.Where(x => x.MachineId == machineId);
}
// 管理员和超级管理员可以使用该字段查询所有创建者的机器码
if (!string.IsNullOrWhiteSpace(createdUserName) && (isAdmin || isSuperAdmin))
{
List<long> queryUserId = (await _userManager.Users.Where(x => x.UserName.Contains(createdUserName)).ToListAsync()).Select(x => x.Id).ToList();
query = query.Where(x => queryUserId.Contains(x.CreateId));
}
// 普通用户只能查找自己创建的机器码
else if (!string.IsNullOrWhiteSpace(createdUserName))
{
query = query.Where(x => x.CreateId == user.Id);
}
if (status != null)
{
query = query.Where(x => x.Status == status);
}
if (useStatus != null)
{
query = query.Where(x => x.UseStatus == useStatus);
}
if (!string.IsNullOrWhiteSpace(remark))
{
query = query.Where(x => x.Remark.Contains(remark));
}
// 管理员和超级管理员可以使用该字段查询所有的机器码的拥有者
if (!string.IsNullOrWhiteSpace(ownUserName) && (isAdmin || isSuperAdmin))
{
List<long> queryUserId = (await _userManager.Users.Where(x => x.UserName.Contains(ownUserName)).ToListAsync()).Select(x => x.Id).ToList();
query = query.Where(x => queryUserId.Contains(x.UserID));
}
// 普通用户只能查找自己拥有的机器码
else if (!string.IsNullOrWhiteSpace(ownUserName))
{
query = query.Where(x => x.UserID == user.Id);
}
int total = await query.CountAsync();
// 降序,取指定的条数的数据
List<Machine> machines = await query.OrderByDescending(x => x.CreateTime).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
return APIResponseModel<CollectionResponse<Machine>>.CreateSuccessResponseModel(ResponseCode.Success, new CollectionResponse<Machine>
{
Total = total,
Collection = machines,
Current = page
});
}
catch (Exception e)
{
return APIResponseModel<CollectionResponse<Machine>>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 将指定的机器码升级为永久使用
/// </summary>
/// <param name="id"></param>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<string>>> UpgradeMachine(string id, long requestUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
User? requestUser = await _userManager.FindByIdAsync(requestUserId.ToString());
if (requestUser == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
Machine? machine = await _context.Machine.FirstOrDefaultAsync(x => x.Id == id);
if (machine == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindMachineByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(requestUser, "Admin") || await _userManager.IsInRoleAsync(requestUser, "Super Admin");
if (!isAdminOrSuperAdmin && machine.UserID != requestUserId)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
User? ownerUser = await _userManager.FindByIdAsync(machine.UserID.ToString());
if (ownerUser == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断当前用户是不是可以升级永久
// 判断用户是不是VIP
if (!await _userManager.IsInRoleAsync(ownerUser, "Vip User"))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.UserNotVip);
}
// 判断是不是已经有正在使用的机器码
List<Machine> usingMachine = await _context.Machine.Where(x => x.UserID == machine.UserID && x.Status == MachineStatus.Active && (x.DeactivationTime == null || x.DeactivationTime >= BeijingTimeExtension.GetBeijingTime()) && x.Id != machine.Id).ToListAsync();
// 判断是不是超过了用户允许的最大机器码数量
if ((usingMachine.Count + 1) > ownerUser.AllDeviceCount)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前用户正在绑定的机器码数量超过最大数量");
}
// 判断免费更换次数
if (ownerUser.FreeCount <= 0)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前用户更换机器码的次数已用完");
}
// 判断当前机器码是否已经是永久使用
if (machine.Status == MachineStatus.Active && machine.UseStatus == MachineUseStatus.Permanent)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前机器码已是永久");
}
// 先修改用户的免费更换次数
ownerUser.FreeCount -= 1;
await _userManager.UpdateAsync(ownerUser);
machine.UpdateTime = BeijingTimeExtension.GetBeijingTime();
machine.Status = MachineStatus.Active;
machine.UseStatus = MachineUseStatus.Permanent;
machine.DeactivationTime = null;
_context.Machine.Update(machine);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 一键停用机器码,只有管理员和超级管理员可以操作,普通用户只能停用自己的机器码
/// </summary>
/// <param name="id"></param>
/// <param name="requestUserId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<string>>> DeactivateMachine(string id, long requestUserId)
{
try
{
User? user = await _userManager.FindByIdAsync(requestUserId.ToString());
if (user == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Admin") || await _userManager.IsInRoleAsync(user, "Super Admin");
Machine? machine = await _context.Machine.FirstOrDefaultAsync(x => x.Id == id);
if (machine == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindMachineByIdFail);
}
if (!isAdminOrSuperAdmin && machine.UserID != requestUserId)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
// 判断是不是已经停用
if (machine.Status == MachineStatus.Frozen)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "该机器码已经被冻结,不能重复操作");
}
// 开始冻结
machine.Status = MachineStatus.Frozen;
if (machine.DeactivationTime == null)
{
machine.DeactivationTime = BeijingTimeExtension.GetBeijingTime();
}
machine.UpdateTime = BeijingTimeExtension.GetBeijingTime();
machine.UpdateId = requestUserId;
_context.Update(machine);
await _context.SaveChangesAsync();
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 获取指定的机器码详情
/// </summary>
/// <param name="id"></param>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<MachineDetailDto>>> GetMachineDetail(string id, long userId)
{
try
{
User? user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
{
return APIResponseModel<MachineDetailDto>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
Machine? machine = await _context.Machine.FirstOrDefaultAsync(x => x.Id == id);
if (machine == null)
{
return APIResponseModel<MachineDetailDto>.CreateErrorResponseModel(ResponseCode.FindMachineByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Admin") || await _userManager.IsInRoleAsync(user, "Super Admin");
if (!isAdminOrSuperAdmin && machine.UserID != userId)
{
return APIResponseModel<MachineDetailDto>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
MachineDetailDto machineDetail = _mapper.Map<MachineDetailDto>(machine);
User? createdUser = await _userManager.FindByIdAsync(machine.CreateId.ToString());
User? updatedUser = await _userManager.FindByIdAsync(machine.UpdateId.ToString());
User? ownUser = await _userManager.FindByIdAsync(machine.UserID.ToString());
machineDetail.CreatedUser = _mapper.Map<UserBaseDto>(createdUser);
machineDetail.UpdatedUser = _mapper.Map<UserBaseDto>(createdUser);
machineDetail.OwnUser = _mapper.Map<UserBaseDto>(createdUser);
return APIResponseModel<MachineDetailDto>.CreateSuccessResponseModel(ResponseCode.Success, machineDetail);
}
catch (Exception e)
{
return APIResponseModel<MachineDetailDto>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
}
}

View File

@ -0,0 +1,460 @@
using AutoMapper;
using LMS.Common.Enums;
using LMS.DAO;
using LMS.DAO.MachineDAO;
using LMS.DAO.PermissionDAO;
using LMS.DAO.RoleDAO;
using LMS.DAO.UserDAO;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.Promission;
using LMS.Repository.RequestModel.Permission;
using LMS.service.Data;
using LMS.Tools;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using OneOf;
using static LMS.Common.Enums.PermissionEnum;
using static LMS.Common.Enums.ResponseCodeEnum;
using PermissionType = LMS.Repository.Models.DB.PermissionType;
namespace LMS.service.Service.PermissionService
{
public class PermissionService
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly UserManager<User> _userManager;
private readonly UserBasicDao _userBasicDAO;
private readonly RoleBasicDao _roleBasicDAO;
private readonly MachineBasicDao _machineBasicDAO;
private readonly PermissionBasicDao _permissionBasicDAO;
private readonly PermissionTypeDao _permissionTypeDao;
public PermissionService(ApplicationDbContext context, IMapper mapper, UserManager<User> userManager, UserBasicDao userBasicDAO, RoleBasicDao roleBasicDAO, MachineBasicDao machineBasicDAO, PermissionBasicDao permissionBasicDao, PermissionTypeDao permissionTypeDao)
{
_context = context;
_mapper = mapper;
_userManager = userManager;
_userBasicDAO = userBasicDAO;
_roleBasicDAO = roleBasicDAO;
_machineBasicDAO = machineBasicDAO;
_permissionBasicDAO = permissionBasicDao;
_permissionTypeDao = permissionTypeDao;
}
#region
/// <summary>
/// 添加权限类型数据
/// </summary>
/// <param name="permissionTypeReq"></param>
/// <returns></returns>
public async Task<ActionResult<APIResponseModel<object>>> AddPermissionType(PermissionTypeModel permissionTypeReq, long requestUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
// 添加数据
PermissionType permissionType = _mapper.Map<PermissionType>(permissionTypeReq);
permissionType.Id = Guid.NewGuid().ToString();
// 判断对应的类型是不是满足条件
if (!EnumExtensions.IsValidPermissionType(typeof(PermissionEnum.PermissionType), (int)permissionTypeReq.Type))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
// 判断名称code是不是存在存在报错
if (await _context.PermissionType.AnyAsync(x => x.Name == permissionType.Name || x.Code == permissionType.Code))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionTypeExist);
}
//// 判断当前用户是不是有新增权限的权限
//if (!await _context.Permission.AnyAsync(x => x.UserId == permissionType.CreateUserId && (x.PermissionCode == SubPermissionType.AddAndDeletePermission || x.PermissionCode == SubPermissionType.ManagePermission)))
//{
// return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
//}
permissionType.CreateUserId = requestUserId;
permissionType.UpdateUserId = requestUserId;
permissionType.UpdateTime = BeijingTimeExtension.GetBeijingTime();
permissionType.CreateTime = BeijingTimeExtension.GetBeijingTime();
// 开始新增
await _context.PermissionType.AddAsync(permissionType);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
/// <summary>
/// 修改权限类型数据
/// </summary>
/// <param name="permissionId"></param>
/// <param name="model"></param>
/// <param name="requestUserId"></param>
/// <returns></returns>
public async Task<ActionResult<APIResponseModel<object>>> ModifyPermissionType(string permissionId, PermissionTypeModel model, long requestUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
// 修改数据
PermissionType? permissionType = await _context.PermissionType.FirstOrDefaultAsync(x => x.Id == permissionId);
if (permissionType == null)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindPermissionTypeByIdFail);
}
permissionType.Name = model.Name;
permissionType.Code = model.Code;
permissionType.Type = model.Type;
permissionType.UpdateTime = DateTime.Now;
//// 判断当前用户是不是有修改和管理权限的权限
//if (!await _context.Permission.AnyAsync(x => x.UserId == model.UpdateUserId && (x.PermissionCode == model.ModifyPermission || x.PermissionCode == SubPermissionType.ManagePermission)))
//{
// return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
//}
// 判断对应的类型是不是满足条件
if (!EnumExtensions.IsValidPermissionType(typeof(PermissionEnum.PermissionType), (int)model.Type))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
// 判断对应的COde和Name是不是存在存在报错不包含当前ID的
if (await _context.PermissionType.AnyAsync(x => x.Name == permissionType.Name && x.Code == permissionType.Code && x.Id != permissionType.Id))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionTypeExist);
}
permissionType.UpdateUserId = requestUserId;
permissionType.Remark = model.Remark;
_context.PermissionType.Update(permissionType);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
transaction.Rollback();
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
/// <summary>
/// 删除权限类型数据
/// </summary>
/// <param name="id"></param>
/// <param name="userId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<object>>> DeletePermissionType(string id, bool relation, long requestUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
PermissionType? permissionType = await _context.PermissionType.FirstOrDefaultAsync(x => x.Id == id);
// 判断当前权限类型是不是存在
if (permissionType == null)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindPermissionTypeByIdFail);
}
//// 判断当前用户是不是有删除权限的权限
//if (!await _context.Permission.AnyAsync(x => x.UserId == permissionTypeRequest.UserId && (x.PermissionCode == SubPermissionType.AddAndDeletePermission || x.PermissionCode == SubPermissionType.ManagePermission)))
//{
// return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
//}
// 判断当前权限类型是不是有权限被使用
var permissions = await _context.Permission
.FromSqlRaw("SELECT * FROM Permission WHERE JSON_CONTAINS(PermissionTypeIds, JSON_ARRAY(@id))",
new MySqlParameter("@id", id))
.ToListAsync();
foreach (var item in permissions)
{
// 判断是不是有,判断是不是要删除对应的关联权限
if (relation)
{
List<string> permissionTypeIds = item.PermissionTypeIdsJson;
await Console.Out.WriteLineAsync("1");
// 删除对应的权限类型
permissionTypeIds.Remove(permissionType.Id);
if (permissionTypeIds.Count == 0)
{
// 删除
_context.Permission.Remove(item);
}
else
{
// 更新
item.PermissionTypeIdsJson = permissionTypeIds;
_context.Permission.Update(item);
}
}
else
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionTypeExist, "当前权限有关联的子权限,不能直接删除");
}
}
// 删除权限类型
_context.PermissionType.Remove(permissionType);
// 提交
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 检测不同的类型判断对应的ID的数据是不是有传入
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
private async Task<bool> CheckPermissionTypeAndId(PermissionModel model)
{
switch (model.Type)
{
case PType.User:
if (model.MachineId != null || model.RoleId != null)
{
return false;
}
if (!await _userBasicDAO.CheckUserExistsByID(model.UserId))
{
return false;
}
break;
case PType.Role:
if (model.MachineId != null || model.UserId != null)
{
return false;
}
if (!await _roleBasicDAO.CheckRoleExistById(model.RoleId))
{
return false;
}
break;
case PType.Machine:
if (model.UserId != null || model.RoleId != null)
{
return false;
}
if (!await _machineBasicDAO.CheckMachineByMachineId(model.MachineId))
{
return false;
}
break;
default:
return false;
}
return true;
}
/// <summary>
/// 检测不同的类型获取对应的ID数据
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private static OneOf<string, long?> GetPermissionRelationIdByType(PermissionModel model)
{
return model.Type switch
{
PType.User => model.UserId,
PType.Role => model.RoleId,
PType.Machine => model.MachineId,
_ => throw new NotImplementedException(),
};
}
/// <summary>
/// 新建权限
/// </summary>
/// <param name="model"></param>
/// <param name="requestUserId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<object>>> AddPermission(PermissionModel model, long requestUserId)
{
try
{
// 判断Type是不是存在
if (!EnumExtensions.IsValidPermissionType(typeof(PType), model.Type))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
// 检查对应的分类对应的ID是不是存在
if (!await CheckPermissionTypeAndId(model))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
// 获取对应的ID
OneOf<string, long?> relationId = GetPermissionRelationIdByType(model);
// 判断对应的用户ID角色ID机器ID是不是存在相对应的权限存在的话不许新增只能修改
if (await _permissionBasicDAO.CheckPermissionByTypeAndId(model.Type, relationId))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionExist);
}
// 判断传入的permissionTypeId是不是都存在
if (!await _permissionTypeDao.CheckPermissionTypeIdsExist(model.PermissionTypeIds))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindPermissionTypeByIdFail);
}
// 判断PermissionCode是不是重复
if (await _permissionBasicDAO.CheckPermissionCodeExist(model.PermissionCode))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionCodeExist);
}
// 开始新增
Permission permission = _mapper.Map<Permission>(model);
permission.Id = Guid.NewGuid().ToString();
permission.UpdateTime = BeijingTimeExtension.GetBeijingTime();
permission.CreateTime = BeijingTimeExtension.GetBeijingTime();
permission.CreateUserId = requestUserId;
permission.UpdateUserId = requestUserId;
permission.PermissionTypeIdsJson = model.PermissionTypeIds;
await _context.Permission.AddAsync(permission);
await _context.SaveChangesAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
/// <summary>
/// 修改权限
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<object>>> ModfiyPermission(string id, PermissionModel model, long requsetUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
Permission? permission = await _context.Permission.FirstOrDefaultAsync(x => x.Id == id);
if (permission == null)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindPermissionByIdFail);
}
// 判断Type是不是存在
if (!EnumExtensions.IsValidPermissionType(typeof(PType), model.Type))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
if (!await CheckPermissionTypeAndId(model))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.ParameterError);
}
// 获取对应的ID
OneOf<string, long?> relationId = GetPermissionRelationIdByType(model);
// 判断对应的关联ID是不是还有其他的权限有的话不能修改
if (await _permissionBasicDAO.CheckPermissionByTypeAndId(model.Type, relationId))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionExist);
}
// 判断传入的permissionTypeIDs是不是存在
if (!await _permissionTypeDao.CheckPermissionTypeIdsExist(model.PermissionTypeIds))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindPermissionTypeByIdFail);
}
// 判断permissionCode是不是重复
if (await _permissionBasicDAO.CheckPermissionCodeExist(model.PermissionCode, id))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.PermissionExist);
}
// 开始修改
permission.UserId = model.UserId;
permission.RoleId = model.RoleId;
permission.MachineId = model.MachineId;
permission.UpdateUserId = requsetUserId;
// 判断是不是有传权限code没有要获取permissionType的code
permission.PermissionCode = model.PermissionCode;
permission.UpdateTime = BeijingTimeExtension.GetBeijingTime();
_context.Permission.Update(permission);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
/// <summary>
/// 删除权限
/// </summary>
/// <param name="Id"></param>
/// <param name="requestUserId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<string>>> DeletePermission(string Id, long requestUserId)
{
try
{
// 判断当前请求的ID是不是存在
var permission = await _context.Permission.FirstOrDefaultAsync(x => x.Id == Id);
if (permission == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindPermissionByIdFail);
}
// 删除权限
_context.Permission.Remove(permission);
await _context.SaveChangesAsync();
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success);
}
catch (Exception e)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
}
}

View File

@ -0,0 +1,64 @@

using LMS.DAO;
using LMS.service.Data;
using Microsoft.EntityFrameworkCore;
namespace LMS.service.Service.PermissionService
{
public class PremissionValidationService
{
private readonly ApplicationDbContext _context;
public PremissionValidationService(ApplicationDbContext context)
{
_context = context;
}
/// <summary>
/// 获取用户的权限
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
private async Task<List<int>> GetUserPermissions(int userId)
{
return null;
//return await _context.UserRoles
// .Where(ur => ur.UserId == userId)
// .SelectMany(ur => ur.Role.RolePermissions.Select(rp => rp.PermissionId))
// .Distinct()
// .ToListAsync();
}
/// <summary>
/// 获取接口需要的权限
/// </summary>
/// <param name="httpMethod"></param>
/// <param name="path"></param>
/// <returns></returns>
private async Task<int?> GetRequiredPermissionForEndpoint(string httpMethod, string path)
{
return null;
//var endpoint = await _context.ApiEndpoints
// .FirstOrDefaultAsync(e => e.HttpMethod == httpMethod && e.Path == path);
//return endpoint?.RequiredPermissionId;
}
/// <summary>
/// 判断用户权限是不是存在
/// </summary>
/// <param name="userId"></param>
/// <param name="httpMethod"></param>
/// <param name="path"></param>
/// <returns></returns>
internal async Task<bool> HasPermissionForEndpoint(long userId, string httpMethod, object path)
{
return true;
//var userPermissions = await GetUserPermissions(userId);
//var requiredPermission = await GetRequiredPermissionForEndpoint(httpMethod, path);
//return userPermissions.Contains(requiredPermission);
}
}
}

View File

@ -0,0 +1,296 @@
using AutoMapper;
using LMS.DAO;
using LMS.Repository.DTO;
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
using LMS.Repository.Role;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Service.RoleService
{
public class RoleService(RoleManager<Role> roleManager, ApplicationDbContext dbContext, UserManager<User> userManager, IMapper mapper)
{
private readonly RoleManager<Role> _roleManager = roleManager;
private readonly ApplicationDbContext _context = dbContext;
private readonly UserManager<User> _userManager = userManager;
private readonly IMapper _mapper = mapper;
/// <summary>
/// 创建角色数据
/// </summary>
/// <param name="model"></param>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<ActionResult<APIResponseModel<string>>> AddRole(RoleModel model, long userId)
{
try
{
if (string.IsNullOrWhiteSpace(model.Name))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError, "角色名不能为空");
}
if (await _roleManager.RoleExistsAsync(model.Name))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.RoleNameExist);
}
await _roleManager.CreateAsync(new Role
{
Name = model.Name,
CreatedUserId = userId,
UpdatedUserId = userId,
CreatedTime = BeijingTimeExtension.GetBeijingTime(),
UpdatedTime = BeijingTimeExtension.GetBeijingTime(),
Remark = model.Remark
});
return APIResponseModel<string>.CreateSuccessResponseModel("添加角色成功");
}
catch (Exception ex)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
/// <summary>
/// 修改角色数据
/// </summary>
/// <param name="id"></param>
/// <param name="model"></param>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<string>>> UpdateRole(long id, RoleModel model, long userId)
{
try
{
Role? role = _roleManager.FindByIdAsync(id.ToString()).Result;
if (role == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
// 判断code是不是存在不报错自己
if (await _roleManager.Roles.FirstOrDefaultAsync(x => x.Name == model.Name && x.Id != id) != null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.RoleNameExist);
}
// 修改
role.Name = model.Name;
role.Remark = model.Remark;
role.UpdatedUserId = userId;
role.UpdatedTime = BeijingTimeExtension.GetBeijingTime();
await _roleManager.UpdateAsync(role);
return APIResponseModel<string>.CreateSuccessResponseModel("修改角色成功");
}
catch (Exception ex)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
/// <summary>
/// 删除角色数据
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<string>>> DeleteRole(long id)
{
try
{
Role? role = await _roleManager.FindByIdAsync(id.ToString());
if (role == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
if (await _context.UserRoles.AnyAsync(x => x.RoleId == id))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.RoleHasUser);
}
await _roleManager.DeleteAsync(role);
return APIResponseModel<string>.CreateSuccessResponseModel("删除角色成功");
}
catch (Exception ex)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
/// <summary>
/// 查询角色数据,返回集合
/// </summary>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <param name="roleName"></param>
/// <param name="roleId"></param>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<CollectionResponse<RoleDto>>>> QueryRoleCollection(int page, int pageSize, string? roleName, long? roleId, long requestUserId)
{
roleName = roleName == "null" ? null : roleName;
roleId = roleId == null ? null : roleId;
try
{
// 判断当前用户的角色
User? user = await _userManager.FindByIdAsync(requestUserId.ToString());
if (user == null)
{
return APIResponseModel<CollectionResponse<RoleDto>>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// TODO
//if (!await _userManager.IsInRoleAsync(user, "Super Admin"))
//{
// return APIResponseModel<CollectionResponse<RoleDto>>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
//}
// 查询数据判断是不是传入了roleName和roleId如果传入了就查询如果没有就查询全部
IQueryable<Role> rolesQueryable = _roleManager.Roles;
if (!string.IsNullOrWhiteSpace(roleName))
{
rolesQueryable = rolesQueryable.Where(x => !string.IsNullOrEmpty(x.Name) && x.Name.Contains(roleName));
}
if (roleId != null)
{
rolesQueryable = rolesQueryable.Where(x => x.Id == roleId);
}
// 将插叙出来的数据通过ID降序排序
rolesQueryable = rolesQueryable.OrderByDescending(x => x.Id);
// 查询总数
int total = await rolesQueryable.CountAsync();
// 分页
rolesQueryable = rolesQueryable.Skip((page - 1) * pageSize).Take(pageSize);
List<Role> roles = await rolesQueryable.ToListAsync();
List<RoleDto> roleDtos = roles.Select(x => new RoleDto
{
Id = x.Id,
Name = x.Name ?? string.Empty,
CreatedTime = x.CreatedTime,
UpdatedTime = x.UpdatedTime,
Remark = x.Remark ?? string.Empty,
}).ToList();
for (int i = 0; i < roleDtos.Count; i++)
{
long createdUserId = roles[i].CreatedUserId;
long updatedUserId = roles[i].UpdatedUserId;
User? createdUser = await _userManager.FindByIdAsync(createdUserId.ToString());
User? updatedUser = await _userManager.FindByIdAsync(updatedUserId.ToString());
roleDtos[i].CreatedUser = _mapper.Map<UserBaseDto>(createdUser);
roleDtos[i].UpdeatedUser = _mapper.Map<UserBaseDto>(updatedUser);
}
CollectionResponse<RoleDto> collectionResponse = new()
{
Total = total,
Collection = roleDtos,
Current = page,
};
return APIResponseModel<CollectionResponse<RoleDto>>.CreateSuccessResponseModel(collectionResponse);
}
catch (Exception ex)
{
return APIResponseModel<CollectionResponse<RoleDto>>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
/// <summary>
/// 查询角色数据通过角色ID
/// </summary>
/// <param name="id"></param>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<ActionResult<APIResponseModel<RoleDto>>> QueryRoleById(long id, long userId)
{
try
{
User? user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
{
return APIResponseModel<RoleDto>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断权限
// 开始查询
Role? role = await _roleManager.FindByIdAsync(id.ToString());
if (role == null)
{
return APIResponseModel<RoleDto>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
RoleDto roleDto = new()
{
Id = role.Id,
Name = role.Name ?? string.Empty,
CreatedTime = role.CreatedTime,
UpdatedTime = role.UpdatedTime,
Remark = role.Remark ?? string.Empty
};
long createdUserId = role.CreatedUserId;
long updatedUserId = role.UpdatedUserId;
User? createdUser = await _userManager.FindByIdAsync(createdUserId.ToString());
User? updatedUser = await _userManager.FindByIdAsync(updatedUserId.ToString());
roleDto.CreatedUser = _mapper.Map<UserDto>(createdUser);
roleDto.UpdeatedUser = _mapper.Map<UserDto>(updatedUser);
// 判断是不是超级管理员
if (roleDto.UpdeatedUser != null && roleDto.CreatedUser != null && !await _userManager.IsInRoleAsync(user, "Super Admin"))
{
roleDto.UpdeatedUser.PhoneNumber = "***********";
roleDto.CreatedUser.PhoneNumber = "***********";
roleDto.UpdeatedUser.Email = "***********";
roleDto.CreatedUser.Email = "***********";
}
return APIResponseModel<RoleDto>.CreateSuccessResponseModel(roleDto);
}
catch (Exception ex)
{
return APIResponseModel<RoleDto>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
/// <summary>
/// 查询角色数据返回下拉框数据只要Name
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<ActionResult<APIResponseModel<List<string>>>> QueryRoleOption(long userId)
{
try
{
User? user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
{
return APIResponseModel<List<string>>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 开始查询
List<string> roles = await _roleManager.Roles.Where(x => !string.IsNullOrWhiteSpace(x.Name)).Select(x => x.Name ?? string.Empty).ToListAsync();
return APIResponseModel<List<string>>.CreateSuccessResponseModel(roles);
}
catch (Exception ex)
{
return APIResponseModel<List<string>>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
}
}

View File

@ -0,0 +1,278 @@

using LMS.Common.RSAKey;
using LMS.DAO;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.User;
using LMS.service.Data;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Concurrent;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography.Xml;
using System.Text;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Service.UserService
{
public class LoginService
{
private readonly UserManager<User> _userManager;
private readonly ApplicationDbContext _context;
private readonly SecurityService _securityService;
public LoginService(UserManager<User> userManager, ApplicationDbContext context, SecurityService securityService)
{
_userManager = userManager;
_context = context;
_securityService = securityService;
}
public class LoginResponse
{
public string Token { get; set; }
public string UserName { get; set; }
public long Id { get; set; }
public string NickName { get; set; }
public string RefreshToken { get; set; }
}
/// <summary>
/// 生成JWT
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string GenerateJWT(User user)
{
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
// 获取角色(目前只有管理员)
claims.Add(new Claim(ClaimTypes.Role, "AdminUser"));
//foreach (string role in roles)
//{
// claims.Add(new Claim(ClaimTypes.Role, role));
//}
//key是检查签名的密钥
if (Environment.GetEnvironmentVariable("SecKey") == null)
{
throw new Exception("SecKey is not set in environment variables");
}
string key = Environment.GetEnvironmentVariable("SecKey");
//设置Token的过期时间
DateTime expires = DateTime.Now.AddHours(1);
byte[] secBytes = Encoding.UTF8.GetBytes(key);
var secKey = new SymmetricSecurityKey(secBytes);
//生成数字签名
var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);
//生成JWT中Token
var tokenDescriptor = new JwtSecurityToken(claims: claims,
expires: expires, signingCredentials: credentials);
string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
return jwt;
}
/// <summary>
/// 用户登录的服务层方法
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<APIResponseModel<object>> UserLogin(LoginModel model, string ip, ConcurrentDictionary<string, (string Key, DateTime Expiry)> _keyStore)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
if (!_keyStore.TryRemove(model.TokenId, out var keyInfo)) // 没有找到对应的公钥
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.UserPasswordFail);
}
if (BeijingTimeExtension.GetBeijingTime() > keyInfo.Expiry) // 公钥超时
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.UserPasswordFail);
}
var rsaKeyId = keyInfo.Key;
var privateKey = _securityService.DecryptWithAES(rsaKeyId);
string decryptedPassword = RsaKeyPairGenerator.Decrypt(privateKey, model.Password);
User? user = null;
if ((int)(model.LoginType) == 1) //用户名密码登录
{
if (model.UserName == null)
{
throw new ArgumentNullException("UserName");
}
user = await _userManager.FindByNameAsync(model.UserName);
}
else if ((int)(model.LoginType) == 0) //邮箱登录
{
if (model.Email == null)
{
throw new ArgumentNullException("Email");
}
user = await _userManager.FindByEmailAsync(model.Email);
}
else
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.UndefinedLoginType);
}
if (user == null)
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.FindUserByNameFail);
}
if (await _userManager.IsLockedOutAsync(user))
{
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.UserIsLockedOut);
}
var result = await _userManager.CheckPasswordAsync(user, decryptedPassword);
if (!result)
{
// 失败计数器加1
await _userManager.AccessFailedAsync(user);
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.UserPasswordFail);
}
// 密码正确返回token
string jwt = GenerateJWT(user);
// 生成一个刷新Token
//var refreshToken = Guid.NewGuid().ToString();
var securityService = new SecurityService(_context);
RefreshTokens refreshTokens = await securityService.CreateRefreshTokenByUserId(user.Id, ip, model.DeviceInfo);
await _context.RefreshTokens.AddAsync(refreshTokens);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<object>.CreateSuccessResponseModel(new LoginResponse()
{
Token = jwt,
UserName = user.UserName,
Id = user.Id,
NickName = user.NickName,
RefreshToken = refreshTokens.Token
});
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<object>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
/// <summary>
/// 刷新token的方法
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<string>>> RefreshToken(RefreshTokenModel model, string ip, string refreshToken)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
Boolean isValid = await _securityService.ValidateRefreshToken(refreshToken, model.UserId, model.DeviceInfo, ip);
if (!isValid)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.RefreshTokenInvalid);
}
// 开始生成token
User? user = await _userManager.FindByIdAsync(model.UserId.ToString());
if (user == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 这边修改ip
string jwt = GenerateJWT(user);
return APIResponseModel<string>.CreateSuccessResponseModel(jwt);
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#region
/// <summary>
/// 将用户设置为代理,要做一系列检查
/// </summary>
/// <param name="requestUserId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<ActionResult<APIResponseModel<string>>> EnableAgent(long requestUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
User? user = await _userManager.FindByIdAsync(requestUserId.ToString());
if (user == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 判断当前用户是不是VIP只有VIP才能成为代理
if (!await _userManager.IsInRoleAsync(user, "VIP User"))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前用户不是VIP不能成为代理");
}
// 检查是否已经是代理
if (await _userManager.IsInRoleAsync(user, "Agent User"))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidOptions, "当前用户已是代理,不能重复操作");
}
// 生成六位邀请码,并且不能重复
string affiliateCode = "";
do
{
affiliateCode = new Random().Next(100000, 999999).ToString("D6");
} while (await _context.Users.AnyAsync(u => u.AffiliateCode == affiliateCode));
// 修改分成位默认
user.AgentPercent = 0.50;
// 开始修改数据库
Role? role = await _context.Roles.FirstOrDefaultAsync(r => r.Name == "Agent User");
if (role == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
await _context.UserRoles.AddAsync(new IdentityUserRole<long>
{
RoleId = role.Id,
UserId = user.Id
});
if (string.IsNullOrWhiteSpace(user.AffiliateCode))
{
user.AffiliateCode = affiliateCode;
}
await _userManager.UpdateAsync(user);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<string>.CreateSuccessResponseModel("用户已成功升级为代理");
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
}
}

View File

@ -0,0 +1,230 @@
using LMS.Common.RSAKey;
using LMS.DAO;
using LMS.Repository.DB;
using LMS.Repository.Models.DB;
using LMS.Repository.User;
using LMS.service.Data;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Service.UserService
{
public class SecurityService
{
private readonly ApplicationDbContext _context;
public SecurityService(ApplicationDbContext context)
{
_context = context;
}
#region
/// <summary>
/// 生成刷新令牌
/// </summary>
/// <returns></returns>
public static string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
/// <summary>
/// 设置令牌的刷新时间
/// </summary>
/// <returns></returns>
public static DateTime GetRefreshTokenExpiryTime()
{
return BeijingTimeExtension.GetBeijingTime().AddDays(7); // 设置刷新令牌7天后过期
}
/// <summary>
/// 验证刷新令牌是否正确
/// </summary>
/// <param name="token">刷新token</param>
/// <param name="userId">用户ID</param>
/// <param name="deviceInfo">设备信息</param>
/// <returns></returns>
public async Task<bool> ValidateRefreshToken(string token, long userId, string deviceInfo, string ip)
{
RefreshTokens? refreshTokens = await _context.RefreshTokens.FirstOrDefaultAsync(x => x.Token == token && x.UserId == userId);
if (refreshTokens == null)
{
return false; // 用户对应的令牌不存在
}
if (BeijingTimeExtension.GetBeijingTime() > refreshTokens.Expiration)
{
_context.Remove(refreshTokens);
return false; // 刷新令牌过期
}
if (refreshTokens.DeviceInfo != deviceInfo)
{
_context.Remove(refreshTokens); // 不同的设备登录
return false;
}
// 刷新令牌通过修改当前ip
refreshTokens.LastCheckIp = ip;
await _context.SaveChangesAsync();
return true;
}
/// <summary>
/// 组合一个刷新令牌的实体类 用于存储到数据库
/// </summary>
/// <param name="userId"></param>
public async Task<RefreshTokens> CreateRefreshTokenByUserId(long userId, string ip, string deviceInfo)
{
// 判断当前用户是否已经有了刷新令牌,如果有则删除
var oldRefreshToken = await _context.RefreshTokens.FirstOrDefaultAsync(x => x.UserId == userId);
if (oldRefreshToken != null)
{
_context.RefreshTokens.Remove(oldRefreshToken);
}
// 创建新的刷新令牌
var refreshToken = GenerateRefreshToken();
var expiryTime = GetRefreshTokenExpiryTime();
// 添加刷新令牌到数据库
RefreshTokens refreshTokens = new()
{
Id = Guid.NewGuid().ToString(),
UserId = userId,
Token = refreshToken,
Expiration = expiryTime,
Revoked = false,
CreatedTime = BeijingTimeExtension.GetBeijingTime(),
LastCheckIp = ip,
DeviceInfo = deviceInfo
};
return refreshTokens;
}
#endregion
#region
/// <summary>
/// 生成公钥
/// </summary>
/// <param name="_keyStore"></param>
/// <param name="model"></param>
/// <returns></returns>
internal async Task<APIResponseModel<PublicKeyResponse>> GetPublicKey(ConcurrentDictionary<string, (string Key, DateTime Expiry)> _keyStore)
{
try
{
RsaKeys? rsa = await _context.RsaKeys.OrderBy(x => x.UseCount).FirstOrDefaultAsync();
if (rsa == null)
{
return APIResponseModel<PublicKeyResponse>.CreateErrorResponseModel(ResponseCode.SystemError, "No RSA key found.");
}
// 更新使用次数
rsa.UseCount++;
_context.Update(rsa);
await _context.SaveChangesAsync();
string token = Guid.NewGuid().ToString();
// 这边检查下所有超时的Key删掉
foreach (var item in _keyStore)
{
if (item.Value.Expiry < BeijingTimeExtension.GetBeijingTime())
{
_keyStore.TryRemove(item.Key, out _);
}
}
_keyStore[token] = (rsa.Id, BeijingTimeExtension.GetBeijingTime().AddSeconds(30));
PublicKeyResponse publicKeyResponse = new()
{
Token = token,
PublicKey = rsa.PublicKey
};
return APIResponseModel<PublicKeyResponse>.CreateSuccessResponseModel(publicKeyResponse);
}
catch (Exception ex)
{
return APIResponseModel<PublicKeyResponse>.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
}
}
private const int NonceSize = 12;
private const int TagSize = 16;
/// <summary>
/// 解码加密后的密码
/// </summary>
/// <param name="cipherText"></param>
/// <param name="key"></param>
/// <returns></returns>
//public string DecryptWithAES(string cipherText, byte[] key)
//{
// if (key.Length != 32)
// {
// throw new ArgumentException("Invalid key length. Expected 32 bytes.", nameof(key));
// }
// byte[] cipherData = Convert.FromBase64String(cipherText);
// if (cipherData.Length < NonceSize + TagSize)
// {
// throw new ArgumentException("Cipher text is too short", nameof(cipherText));
// }
// byte[] nonce = new byte[NonceSize];
// byte[] cipherTextWithTag = new byte[cipherData.Length - NonceSize];
// Array.Copy(cipherData, nonce, NonceSize);
// Array.Copy(cipherData, NonceSize, cipherTextWithTag, 0, cipherTextWithTag.Length);
// using var aes = new AesGcm(key, TagSize);
// byte[] plaintext = new byte[cipherTextWithTag.Length - TagSize];
// aes.Decrypt(nonce, cipherTextWithTag, null, plaintext, cipherTextWithTag.AsSpan(cipherTextWithTag.Length - TagSize));
// return Encoding.UTF8.GetString(plaintext);
//}
/// <summary>
/// 解密密钥
/// </summary>
/// <param name="cipherText"></param>
/// <param name="rasKeyId"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string DecryptWithAES(string rasKeyId)
{
try
{
RsaKeys? rsa = _context.RsaKeys.FirstOrDefault(x => x.Id == rasKeyId) ?? throw new Exception("未找到对应的RSA密钥");
string secertIV = rsa.EncryptionIV;
string secertKey = rsa.EncryptionKey;
byte[] key = ComplexKeyObfuscator.Deobfuscate(Convert.FromBase64String(secertKey));
byte[] iv = ComplexKeyObfuscator.Deobfuscate(Convert.FromBase64String(secertIV));
string privateKey = AESGenerate.Decrypt(rsa.EncryptedPrivateKey, key, iv);
return privateKey;
}
catch (Exception ex)
{
throw new Exception("密码解密失败");
}
}
#endregion
}
}

View File

@ -0,0 +1,482 @@
using LMS.Common.RSAKey;
using LMS.DAO;
using LMS.Repository.DTO;
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
using LMS.Repository.Models.User;
using LMS.Repository.User;
using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Concurrent;
using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Service.UserService
{
public class UserService(UserManager<User> userManager, RoleManager<Role> roleManager, ApplicationDbContext context, SecurityService securityService)
{
private readonly UserManager<User> _userManager = userManager;
private readonly RoleManager<Role> _roleManager = roleManager;
private readonly ApplicationDbContext _context = context;
private readonly SecurityService _securityService = securityService;
#region
/// <summary>
/// 获取用户信息的方法
/// </summary>
/// <param name="userId"></param>
/// <param name="requestUserId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<UserDto>>> GetUserInfo(long userId, long requestUserId)
{
try
{
User? user = await _userManager.FindByIdAsync(userId.ToString());
if (user == null)
{
return APIResponseModel<UserDto>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
if (userId != requestUserId)
{
// 这边单独的判断是否有权限
User? requertUser = await _userManager.FindByIdAsync(requestUserId.ToString());
if (requertUser == null)
{
return APIResponseModel<UserDto>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(requertUser, "Super Admin") || await _userManager.IsInRoleAsync(requertUser, "Admin");
// 判断角色
if (!isAdminOrSuperAdmin)
{
return APIResponseModel<UserDto>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
}
List<string>? roles = [.. (await _userManager.GetRolesAsync(user))];
UserDto userDto = new()
{
Id = user.Id,
UserName = user.UserName ?? string.Empty,
NickName = user.NickName,
Email = user.Email ?? string.Empty,
PhoneNumber = user.PhoneNumber ?? string.Empty,
Avatar = string.Empty,
RoleNames = roles,
CreatedDate = user.CreatedDate,
AllDeviceCount = user.AllDeviceCount,
FreeCount = user.FreeCount,
Options = user.OptionsJson,
AgentPercent = user.AgentPercent,
AffiliateCode = user.AffiliateCode,
ParentId = user.ParentId ?? 0
};
return APIResponseModel<UserDto>.CreateSuccessResponseModel(userDto);
}
catch (Exception e)
{
return APIResponseModel<UserDto>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
internal async Task<ActionResult<APIResponseModel<CollectionResponse<UserCollectionDto>>>> QueryUserCollection(int page, int pageSize, string userName, long? userId, string nickName, string phoneNumber, string email, string[] roleNames, long? parentId, long reuqertUserId)
{
try
{
User? user = await _userManager.FindByIdAsync(reuqertUserId.ToString());
if (user == null)
{
return APIResponseModel<CollectionResponse<UserCollectionDto>>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin") || await _userManager.IsInRoleAsync(user, "Admin");
bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin");
bool isAgent = !isAdminOrSuperAdmin && await _userManager.IsInRoleAsync(user, "Agent User");
if (!isAdminOrSuperAdmin && !isAgent)
{
return APIResponseModel<CollectionResponse<UserCollectionDto>>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
List<long> roleIds = [];
List<long> userIds = [];
// 先查询指定的角色名字对应的用户,再对用户进行筛选
if (roleNames != null && roleNames.Length > 0)
{
for (int i = 0; i < roleNames.Length; i++)
{
Role? role = await _roleManager.FindByNameAsync(roleNames[i]);
if (role == null)
{
return APIResponseModel<CollectionResponse<UserCollectionDto>>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
roleIds.Add(role.Id);
}
// 找到和角色ID对应的用户ID
userIds = await _context.UserRoles.Where(x => roleIds.Contains(x.RoleId)).Select(x => x.UserId).Distinct().ToListAsync();
}
// 开始查询数据
IQueryable<User>? query = _userManager.Users;
if (isAgent)
{
query = query.Where(x => x.ParentId == user.Id);
}
// 判断是不是管理员
IList<User> superUsers = await _userManager.GetUsersInRoleAsync("Super Admin");
List<long> superUserIds = superUsers.Select(x => x.Id).ToList();
// 默认把自己排除
//query = query.Where(x => x.Id != reuqertUserId);
if (!isSuperAdmin)
{
// 不是草鸡管理员,就把超级管理员排除
query = query.Where(x => reuqertUserId == x.Id || (!superUserIds.Contains(x.ParentId ?? 0) && !superUserIds.Contains(x.Id)));
}
// 添加查询条件
if (!string.IsNullOrWhiteSpace(userName))
{
query = query.Where(x => !string.IsNullOrWhiteSpace(x.UserName) && x.UserName.Contains(userName));
}
if (!string.IsNullOrWhiteSpace(nickName))
{
query = query.Where(x => !string.IsNullOrWhiteSpace(x.NickName) && x.NickName.Contains(nickName));
}
if (userId != null)
{
query = query.Where(x => x.Id == userId);
}
if (parentId != null)
{
query = query.Where(x => x.ParentId == parentId);
}
if (!string.IsNullOrWhiteSpace(phoneNumber))
{
query = query.Where(x => !string.IsNullOrWhiteSpace(x.PhoneNumber) && x.PhoneNumber.Contains(phoneNumber));
}
if (!string.IsNullOrWhiteSpace(email))
{
query = query.Where(x => !string.IsNullOrWhiteSpace(x.Email) && x.Email.Contains(email));
}
if (roleIds.Count > 0)
{
query = query.Where(x => userIds.Contains(x.Id));
}
// 通过ID降序
query = query.OrderByDescending(x => x.Id);
// 查询总数
int total = await query.CountAsync();
// 分页
query = query.Skip((page - 1) * pageSize).Take(pageSize);
List<User>? users = await query.ToListAsync();
List<UserCollectionDto>? userCollections = users.Select(x =>
new UserCollectionDto
{
Id = x.Id,
UserName = x.UserName ?? string.Empty,
NickName = x.NickName,
Email = x.Email ?? string.Empty,
PhoneNumber = x.PhoneNumber ?? string.Empty,
CreatedDate = x.CreatedDate,
LastLoginDate = x.LastLoginDate,
LastLoginIp = x.LastLoginIp,
LastLoginDevice = x.LastLoginDevice,
ParentId = x.ParentId ?? 0
}).ToList();
for (int i = 0; i < userCollections.Count; i++)
{
List<string>? roles = [.. (await _userManager.GetRolesAsync(users[i]))];
userCollections[i].RoleNames = roles;
if (!isAdminOrSuperAdmin)
{
userCollections[i].PhoneNumber = "***********";
userCollections[i].Email = "***********";
}
}
return APIResponseModel<CollectionResponse<UserCollectionDto>>.CreateSuccessResponseModel(new CollectionResponse<UserCollectionDto>
{
Total = total,
Collection = userCollections,
Current = page,
});
}
catch (Exception e)
{
return APIResponseModel<CollectionResponse<UserCollectionDto>>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 修改用户信息
/// </summary>
/// <param name="id"></param>
/// <param name="userModel"></param>
/// <param name="reuqertUserId"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<ActionResult<APIResponseModel<string>>> UpdatedUser(long id, UpdatedUserModel userModel, long requestUserId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
User? user = await _userManager.FindByIdAsync(id.ToString());
if (user == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
// 检查当前用户的权限
User? requestUser = await _userManager.FindByIdAsync(requestUserId.ToString());
if (requestUser == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(requestUser, "Super Admin") || await _userManager.IsInRoleAsync(requestUser, "Admin");
if (requestUserId != id && !isAdminOrSuperAdmin)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
// 开始修改用户信息
if (userModel.AgentPercent != null)
{
user.AgentPercent = userModel.AgentPercent.Value;
}
if (userModel.AllDeviceCount != null)
{
user.AllDeviceCount = userModel.AllDeviceCount.Value;
}
if (userModel.FreeCount != null)
{
user.FreeCount = userModel.FreeCount.Value;
}
if (userModel.NickName != null)
{
user.NickName = userModel.NickName;
}
if (userModel.UserName != null)
{
user.UserName = userModel.UserName;
user.NormalizedUserName = userModel.UserName.ToUpper();
}
if (userModel.Email != null)
{
user.Email = userModel.Email;
user.NormalizedEmail = userModel.Email.ToUpper();
}
if (userModel.PhoneNumber != null)
{
user.PhoneNumber = userModel.PhoneNumber;
}
// 只有管理员才能修改角色
if (isAdminOrSuperAdmin)
{
// 开始删除之前的绑定关系
List<IdentityUserRole<long>> userRoles = await _context.UserRoles.Where(x => x.UserId == id).ToListAsync();
_context.UserRoles.RemoveRange(userRoles);
// 判断是不是所有的角色名都存在
if (userModel.RoleNames != null && userModel.RoleNames.Count > 0)
{
// 将之前的绑定关系全部删掉,重新绑定
List<long> roleIds = [];
for (int i = 0; i < userModel.RoleNames.Count; i++)
{
Role? role = await _roleManager.FindByNameAsync(userModel.RoleNames[i]);
if (role == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
roleIds.Add(role.Id);
}
// 开始新增
List<IdentityUserRole<long>> userRole = roleIds.Select(x => new IdentityUserRole<long>
{
UserId = id,
RoleId = x
}).ToList();
await _context.UserRoles.AddRangeAsync(userRole);
}
}
_context.Update(user);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<string>.CreateSuccessResponseModel("修改成功");
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 获取用户代理信息
/// </summary>
/// <param name="requestUserId"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<UserAgentInfoDto>>> GetUserAgentInfo(long requestUserId)
{
try
{
User? user = await _userManager.FindByIdAsync(requestUserId.ToString());
if (user == null)
{
return APIResponseModel<UserAgentInfoDto>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin") || await _userManager.IsInRoleAsync(user, "Admin");
// 判断当前用户是不是代理
if (!isAdminOrSuperAdmin && !await _userManager.IsInRoleAsync(user, "Agent User"))
{
return APIResponseModel<UserAgentInfoDto>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
// 查询所有用户判断他的parentId是不是当前用户
int allUserCount = await _context.Users.Where(x => x.ParentId == requestUserId).CountAsync();
// 查询所有在VIP User角色中的用户
Role? role = await _roleManager.FindByNameAsync("VIP User");
if (role == null)
{
return APIResponseModel<UserAgentInfoDto>.CreateErrorResponseModel(ResponseCode.FindRoleByIdFail);
}
int vipUserCount = await _context.UserRoles
.Where(ur => ur.RoleId == role.Id)
.Join(_context.Users,
ur => ur.UserId,
u => u.Id,
(ur, u) => u)
.CountAsync(u => u.ParentId == requestUserId);
UserAgentInfoDto userAgentInfoDto = new()
{
UserId = user.Id,
UserName = user.UserName ?? string.Empty,
NickName = user.NickName,
AffiliateCode = user.AffiliateCode,
AgentPercent = user.AgentPercent,
AffiliateNumber = allUserCount,
AffiliateVIPNumber = vipUserCount,
AffiliateMoney = 0
};
return APIResponseModel<UserAgentInfoDto>.CreateSuccessResponseModel(userAgentInfoDto);
}
catch (Exception e)
{
return APIResponseModel<UserAgentInfoDto>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
#region
/// <summary>
/// 用户注册
/// </summary>
/// <param name="model"></param>
/// <param name="_keyStore"></param>
/// <returns></returns>
internal async Task<ActionResult<APIResponseModel<string>>> Register(RegisterModel model, ConcurrentDictionary<string, (string Key, DateTime Expiry)> _keyStore)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
if (!_keyStore.TryRemove(model.TokenId, out var keyInfo)) // 没有找到对应的公钥
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.UserPasswordFail);
}
if (BeijingTimeExtension.GetBeijingTime() > keyInfo.Expiry) // 公钥超时
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.UserPasswordFail);
}
// 判断邀请码是不是存在
if (string.IsNullOrWhiteSpace(model.AffiliateCode))
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.ParameterError, "邀请码必填");
}
User? affiliateUser = await _userManager.Users.FirstOrDefaultAsync(x => x.AffiliateCode == model.AffiliateCode);
if (affiliateUser == null)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.InvalidAffiliateCode);
}
var rsaKeyId = keyInfo.Key;
var privateKey = _securityService.DecryptWithAES(rsaKeyId);
string decryptedPassword = RsaKeyPairGenerator.Decrypt(privateKey, model.Password);
var user = new User { UserName = model.UserName, Email = model.Email ?? string.Empty, NickName = model.UserName };
var result = await _userManager.CreateAsync(user, decryptedPassword);
if (!result.Succeeded)
{
foreach (var s in result.Errors)
{
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.UserRegisterFial, s.Description);
}
}
try
{
await _userManager.AddToRoleAsync(user, "Simple User");
// 给用户设置初始值
user.AllDeviceCount = 1;
user.AgentPercent = 0.50;
user.FreeCount = 10;
user.ParentId = affiliateUser.Id;
}
catch (Exception e)
{
// 如果添加角色失败,删除用户
await _userManager.DeleteAsync(user);
throw;
}
await _userManager.UpdateAsync(user);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return APIResponseModel<string>.CreateSuccessResponseModel(ResponseCode.Success, "User Register Success");
}
catch (Exception e)
{
await transaction.RollbackAsync();
return APIResponseModel<string>.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
}
}
#endregion
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

1
docker.txt Normal file
View File

@ -0,0 +1 @@
docker build -t lms-service .