> SimpleOptionsRequestQuery = new()
{
- { "ttsrole", ["EdgeTTsRoles"] },
- { "laitoolinfo", ["LaitoolHomePage", "LaitoolNotice", "LaitoolUpdateContent", "LaitoolVersion"] },
- { OptionKeyName.LaitoolFluxApiModelList, [OptionKeyName.LaitoolFluxApiModelList] }
+ { SimpleOptionKey.Ttsrole, ["EdgeTTsRoles"] },
+ { SimpleOptionKey.Laitoolinfo, ["LaitoolHomePage", "LaitoolNotice", "LaitoolUpdateContent", "LaitoolVersion"] },
+ { SimpleOptionKey.LaitoolFluxApiModelList, [OptionKeyName.LaitoolFluxApiModelList] },
+ { SimpleOptionKey.EnableMailService, [OptionKeyName.EnableMailService]}
};
}
\ No newline at end of file
diff --git a/LMS.Common/Enum/OptionTypeEnum.cs b/LMS.Common/Enum/OptionTypeEnum.cs
index 8648191..318d89d 100644
--- a/LMS.Common/Enum/OptionTypeEnum.cs
+++ b/LMS.Common/Enum/OptionTypeEnum.cs
@@ -5,6 +5,7 @@ public enum OptionTypeEnum
String = 1,
JSON = 2,
Number = 3,
+ Boolean = 4
}
public static class OptionKeyName
@@ -13,4 +14,14 @@ public static class OptionKeyName
/// LaiTool Flux API 模型列表的Option Key
///
public const string LaitoolFluxApiModelList = "LaitoolFluxApiModelList";
+
+ ///
+ /// SMTP的邮件设置
+ ///
+ public const string SMTPMailSetting = "SMTPMailSetting";
+
+ ///
+ /// 是否开启邮箱服务
+ ///
+ public const string EnableMailService = "EnableMailService";
}
diff --git a/LMS.Common/LMS.Common.csproj b/LMS.Common/LMS.Common.csproj
index fa71b7a..67198ff 100644
--- a/LMS.Common/LMS.Common.csproj
+++ b/LMS.Common/LMS.Common.csproj
@@ -6,4 +6,8 @@
enable
+
+
+
+
diff --git a/LMS.Common/Templates/EmailTemplateService.cs b/LMS.Common/Templates/EmailTemplateService.cs
new file mode 100644
index 0000000..a1d5fb6
--- /dev/null
+++ b/LMS.Common/Templates/EmailTemplateService.cs
@@ -0,0 +1,37 @@
+namespace LMS.Common.Templates
+{
+ public class EmailTemplateService()
+ {
+ ///
+ /// 注册邮件模板
+ ///
+ public const string RegisterHtmlTemplates = """
+
+
+
+ LMS Registration Code
+ Hi there,
+ Your code is {RegisterCode}
+ The code is valid for 10 minutes, if it is not you, please ignore it.
+
+
+ """;
+
+
+ ///
+ /// 替换模板的占位符
+ ///
+ ///
+ ///
+ ///
+ public static string ReplaceTemplate(string template, Dictionary parameters)
+ {
+ // 替换占位符
+ foreach (var param in parameters)
+ {
+ template = template.Replace($"{{{param.Key}}}", param.Value);
+ }
+ return template;
+ }
+ }
+}
diff --git a/LMS.DAO/ApplicationDbContext.cs b/LMS.DAO/ApplicationDbContext.cs
index 6c45f11..178a037 100644
--- a/LMS.DAO/ApplicationDbContext.cs
+++ b/LMS.DAO/ApplicationDbContext.cs
@@ -4,6 +4,7 @@ using LMS.Repository.DB;
using LMS.Repository.Models.DB;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Text.Json;
namespace LMS.DAO
@@ -52,6 +53,15 @@ namespace LMS.DAO
v => string.IsNullOrEmpty(v) || v == "[]"
? new List() // 如果存储的是空字符串或空数组,则返回空列表
: JsonSerializer.Deserialize>(v, (JsonSerializerOptions?)null) ?? new List()
+ ).Metadata.SetValueComparer(
+ new ValueComparer>(
+ // 比较两个集合是否相等
+ (c1, c2) => c1 != null && c2 != null && c1.SequenceEqual(c2),
+ // 计算集合的哈希码 - 这里修复了问题
+ c => c == null ? 0 : c.Aggregate(0, (a, v) => HashCode.Combine(a, v != null ? v.GetHashCode() : 0)),
+ // 创建集合的副本
+ c => c == null ? null : c.ToList()
+ )
);
modelBuilder.Entity()
.HasKey(us => new { us.UserId, us.SoftwareId });
diff --git a/LMS.DAO/LMS.DAO.csproj b/LMS.DAO/LMS.DAO.csproj
index c3d9456..76fc1e0 100644
--- a/LMS.DAO/LMS.DAO.csproj
+++ b/LMS.DAO/LMS.DAO.csproj
@@ -8,6 +8,7 @@
+
diff --git a/LMS.DAO/UserDAO/UserBasicDAO.cs b/LMS.DAO/UserDAO/UserBasicDAO.cs
index 2c18e3a..ad46373 100644
--- a/LMS.DAO/UserDAO/UserBasicDAO.cs
+++ b/LMS.DAO/UserDAO/UserBasicDAO.cs
@@ -55,6 +55,42 @@ namespace LMS.DAO.UserDAO
bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin");
return isSuperAdmin;
}
+
+ ///
+ /// 检查用户是不是管理员
+ ///
+ ///
+ ///
+ ///
+ public async Task CheckUserIsAdmin(long? userId)
+ {
+ if (userId == null)
+ {
+ return false;
+ }
+ User? user = await _userManager.FindByIdAsync(userId.ToString() ?? "0") ?? throw new Exception("用户不存在");
+
+ bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Admin");
+ return isSuperAdmin;
+ }
+
+ ///
+ /// 检查用户是不是代理
+ ///
+ ///
+ ///
+ ///
+ public async Task CheckUserIsAgent(long? userId)
+ {
+ if (userId == null)
+ {
+ return false;
+ }
+ User? user = await _userManager.FindByIdAsync(userId.ToString() ?? "0") ?? throw new Exception("用户不存在");
+
+ bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Agent User");
+ return isSuperAdmin;
+ }
}
}
diff --git a/LMS.Repository/User/RegisterModel.cs b/LMS.Repository/User/RegisterModel.cs
index 281ba9f..d06155d 100644
--- a/LMS.Repository/User/RegisterModel.cs
+++ b/LMS.Repository/User/RegisterModel.cs
@@ -6,7 +6,8 @@ namespace LMS.Repository.Models.User
{
[Required]
public required string UserName { get; set; }
- public string? Email { get; set; }
+ [Required]
+ public required string Email { get; set; }
[Required]
public required string Password { get; set; }
[Required]
@@ -14,5 +15,7 @@ namespace LMS.Repository.Models.User
[Required]
public required string AffiliateCode { get; set; }
+
+ public string? VerificationCode { get; set; }
}
}
diff --git a/LMS.service/Configuration/AddLoggerConfig.cs b/LMS.service/Configuration/AddLoggerConfig.cs
new file mode 100644
index 0000000..45fa1c6
--- /dev/null
+++ b/LMS.service/Configuration/AddLoggerConfig.cs
@@ -0,0 +1,29 @@
+using Serilog;
+
+namespace LMS.service.Configuration
+{
+ public static class AddLoggerConfig
+ {
+ public static void AddLoggerService(this IServiceCollection services)
+ {
+ // 确保logs目录存在
+ Directory.CreateDirectory("logs");
+ // 加载配置
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: false)
+ .Build();
+ // 配置Serilog
+ Log.Logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration)
+ .CreateLogger();
+
+ // 添加Serilog到.NET Core的日志系统
+ services.AddLogging(builder =>
+ {
+ builder.ClearProviders();
+ builder.AddSerilog(dispose: true);
+ });
+ }
+ }
+}
diff --git a/LMS.service/Configuration/AuthenticationExtensions.cs b/LMS.service/Configuration/AuthenticationExtensions.cs
index c7ab974..cb1e120 100644
--- a/LMS.service/Configuration/AuthenticationExtensions.cs
+++ b/LMS.service/Configuration/AuthenticationExtensions.cs
@@ -2,7 +2,7 @@
using Microsoft.IdentityModel.Tokens;
using System.Text;
-namespace Lai_server.Configuration
+namespace LMS.service.Configuration
{
public static class AuthenticationExtensions
{
diff --git a/LMS.service/Configuration/InitConfiguration/DatabaseConfiguration.cs b/LMS.service/Configuration/InitConfiguration/DatabaseConfiguration.cs
index 9bcec72..a44a91c 100644
--- a/LMS.service/Configuration/InitConfiguration/DatabaseConfiguration.cs
+++ b/LMS.service/Configuration/InitConfiguration/DatabaseConfiguration.cs
@@ -101,7 +101,9 @@ public class DatabaseConfiguration(IServiceProvider serviceProvider) : IHostedSe
new Options { Key = "LaitoolNotice", Value = string.Empty, Type = OptionTypeEnum.String },
new Options { Key = "LaitoolVersion", Value = string.Empty, Type = OptionTypeEnum.String },
new Options { Key = "LaiToolTrialDays", Value = "2" , Type = OptionTypeEnum.Number},
- new Options { Key = OptionKeyName.LaitoolFluxApiModelList, Value = "{}" , Type = OptionTypeEnum.JSON }
+ new Options { Key = OptionKeyName.LaitoolFluxApiModelList, Value = "{}" , Type = OptionTypeEnum.JSON },
+ new Options {Key = OptionKeyName.EnableMailService, Value = false.ToString(), Type = OptionTypeEnum.Boolean},
+ new Options {Key = OptionKeyName.SMTPMailSetting, Value ="{}" , Type = OptionTypeEnum.JSON }
];
// 遍历所有的配置项,如果没有则添加
diff --git a/LMS.service/Configuration/ServiceConfiguration.cs b/LMS.service/Configuration/ServiceConfiguration.cs
index f965608..a587beb 100644
--- a/LMS.service/Configuration/ServiceConfiguration.cs
+++ b/LMS.service/Configuration/ServiceConfiguration.cs
@@ -3,6 +3,7 @@ using LMS.DAO.PermissionDAO;
using LMS.DAO.RoleDAO;
using LMS.DAO.UserDAO;
using LMS.service.Configuration.InitConfiguration;
+using LMS.service.Extensions.Mail;
using LMS.service.Service;
using LMS.service.Service.PermissionService;
using LMS.service.Service.PromptService;
@@ -44,6 +45,13 @@ namespace Lai_server.Configuration
services.AddScoped();
services.AddScoped();
+
+ // 注入 Extensions
+ services.AddScoped();
+ services.AddScoped();
+
+ // 添加分布式缓存(用于存储验证码)
+ services.AddDistributedMemoryCache();
}
}
}
diff --git a/LMS.service/Controllers/OptionsController.cs b/LMS.service/Controllers/OptionsController.cs
index 9ae222b..83b7ae9 100644
--- a/LMS.service/Controllers/OptionsController.cs
+++ b/LMS.service/Controllers/OptionsController.cs
@@ -1,5 +1,6 @@
using LMS.Repository.DB;
using LMS.Repository.DTO;
+using LMS.Repository.Models.DB;
using LMS.Repository.Options;
using LMS.service.Service;
using LMS.Tools.Extensions;
@@ -61,5 +62,16 @@ namespace LMS.service.Controllers
#endregion
+ #region 测试邮箱发送
+
+ [HttpPost]
+ [Authorize]
+ public async Task>> TestSendMail()
+ {
+ long userId = ConvertExtension.ObjectToLong(HttpContext.Items["UserId"] ?? 0);
+ return await _optionsService.TestSendMail(userId);
+ }
+
+ #endregion
}
}
diff --git a/LMS.service/Controllers/UserController.cs b/LMS.service/Controllers/UserController.cs
index f9e3d18..24a0cde 100644
--- a/LMS.service/Controllers/UserController.cs
+++ b/LMS.service/Controllers/UserController.cs
@@ -77,7 +77,7 @@ namespace LMS.service.Controllers
var cookieOptions = new CookieOptions
{
HttpOnly = true,
- Secure = false, // 如果使用 HTTPS
+ Secure = true, // 如果使用 HTTPS
SameSite = SameSiteMode.None,
Expires = DateTime.UtcNow.AddDays(7),
};
@@ -129,6 +129,19 @@ namespace LMS.service.Controllers
#endregion
+ #region 获取邮箱验证码
+
+ [HttpPost]
+ public async Task>> SendVerificationCode([FromBody] EmailVerificationService.SendVerificationCodeDto model)
+ {
+ if (!ModelState.IsValid)
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError);
+
+ return await _userService.SendVerificationCode(model);
+ }
+
+ #endregion
+
#region 刷新token
[HttpPost]
diff --git a/LMS.service/Extensions/Mail/EmailService.cs b/LMS.service/Extensions/Mail/EmailService.cs
new file mode 100644
index 0000000..1c8c282
--- /dev/null
+++ b/LMS.service/Extensions/Mail/EmailService.cs
@@ -0,0 +1,180 @@
+using LMS.Common.Enum;
+using LMS.DAO;
+using MailKit.Net.Smtp;
+using MailKit.Security;
+using Microsoft.EntityFrameworkCore;
+using MimeKit;
+using Newtonsoft.Json;
+using ILogger = Serilog.ILogger; // 明确使用 Serilog 的 ILogger
+namespace LMS.service.Extensions.Mail
+{
+
+ public class MailSetting
+ {
+ ///
+ /// 是否开启邮箱服务
+ ///
+ public bool EnableMailService { get; set; }
+ ///
+ /// 是否开启SSL
+ ///
+ public bool EnableSSL { get; set; }
+ ///
+ /// SMTP服务器
+ ///
+ public string SmtpServer { get; set; }
+ ///
+ /// 端口
+ ///
+ public int Port { get; set; }
+ ///
+ /// 发送用户名
+ ///
+ public string Username { get; set; }
+ ///
+ /// 密码
+ ///
+ public string Password { get; set; }
+ ///
+ /// 发送者邮箱
+ ///
+ public string SenderEmail { get; set; }
+ ///
+ /// 测试接收邮箱
+ ///
+ public string TestReceiveMail { get; set; }
+ }
+
+ public class EmailService
+ {
+ private readonly ILogger _logger;
+ private readonly ApplicationDbContext _context;
+
+ // 构造函数注入
+ public EmailService(ILogger logger, ApplicationDbContext context)
+ {
+ _logger = logger;
+ _context = context;
+ }
+
+ ///
+ /// 发送安全邮件
+ ///
+ /// 收信邮箱
+ /// 邮件主题
+ /// 邮件信息 body
+ ///
+ public async Task SendEmailSafelyAsync(string to, string subject, string body, bool isTest = false)
+ {
+ if (string.IsNullOrEmpty(to))
+ {
+ _logger.Information("收件人地址为空,邮件未发送");
+ return;
+ }
+
+ try
+ {
+ await SendEmailInternalAsync(to, subject, body, isTest);
+ _logger.Information($"邮件已成功发送至: {to}");
+ }
+ catch (Exception ex)
+ {
+ // 记录错误但不抛出异常,不影响业务流程
+ _logger.Error(ex, $"发送邮件至 {to} 时出错: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 发送邮件,捕获异常并记录
+ ///
+ /// 收信邮箱
+ /// 邮件主题
+ /// 邮件信息 body
+ ///
+ ///
+ public async Task SendEmailAsync(string to, string subject, string body, bool isTest = false)
+ {
+ if (string.IsNullOrEmpty(to))
+ {
+ _logger.Information("收件人地址为空,邮件未发送");
+ throw new Exception("收件人地址为空,邮件未发送");
+ }
+
+ try
+ {
+ await SendEmailInternalAsync(to, subject, body, isTest);
+ _logger.Information($"邮件已成功发送至: {to}");
+ }
+ catch (Exception ex)
+ {
+ // 记录错误但不抛出异常,不影响业务流程
+ _logger.Error(ex, $"发送邮件至 {to} 时出错: {ex.Message}");
+ throw new Exception(ex.Message);
+ }
+ }
+
+ private async Task SendEmailInternalAsync(string to, string subject, string body, bool isTest = false)
+ {
+ try
+ {
+
+ var mailSettingString = await _context.Options.FirstOrDefaultAsync(x => x.Key == OptionKeyName.SMTPMailSetting) ?? throw new Exception("邮件配置不存在,请先配置");
+
+ MailSetting? mailSetting = JsonConvert.DeserializeObject(mailSettingString.Value ?? "{}") ?? throw new Exception("邮件配置不存在,请先配置");
+ string smtpServer = mailSetting.SmtpServer;
+ int port = mailSetting.Port;
+ string username = mailSetting.Username;
+ string password = mailSetting.Password;
+ string senderEmail = mailSetting.SenderEmail;
+ // 加载邮件设置
+
+ if (mailSetting.EnableMailService == false)
+ {
+ throw new Exception("邮件服务未开启,请先开启");
+ }
+
+ if (isTest)
+ {
+ if (string.IsNullOrEmpty(mailSetting.TestReceiveMail))
+ {
+ throw new Exception("测试接收邮箱未设置,请先设置");
+ }
+ to = mailSetting.TestReceiveMail;
+ }
+
+ // 邮箱信息检查成功,开始发送邮件
+
+ var message = new MimeMessage();
+ message.From.Add(new MailboxAddress(username, senderEmail));
+ message.To.Add(new MailboxAddress("", to));
+ message.Subject = subject;
+
+ var bodyBuilder = new BodyBuilder
+ {
+ HtmlBody = body
+ };
+ message.Body = bodyBuilder.ToMessageBody();
+
+ // 发送邮件
+ using var client = new SmtpClient();
+
+ // 使用配置的服务器和端口
+ await client.ConnectAsync(smtpServer, port, SecureSocketOptions.SslOnConnect);
+
+ // 登录
+ await client.AuthenticateAsync(senderEmail, password);
+
+ // 设置超时
+ client.Timeout = 30000; // 30秒超时
+
+ // 发送
+ await client.SendAsync(message);
+ await client.DisconnectAsync(true);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("邮件发送失败,失败信息" + ex.Message);
+ }
+ }
+ }
+}
diff --git a/LMS.service/Extensions/Mail/EmailVerificationService.cs b/LMS.service/Extensions/Mail/EmailVerificationService.cs
new file mode 100644
index 0000000..cb097db
--- /dev/null
+++ b/LMS.service/Extensions/Mail/EmailVerificationService.cs
@@ -0,0 +1,71 @@
+using LMS.Common.Templates;
+using LMS.service.Extensions.Mail;
+using Microsoft.Extensions.Caching.Distributed;
+using System.ComponentModel.DataAnnotations;
+
+public class EmailVerificationService
+{
+ private readonly IDistributedCache _cache;
+ private readonly Random _random;
+ private readonly EmailService _emailService;
+
+ public EmailVerificationService(IDistributedCache cache, EmailService emailService)
+ {
+ _cache = cache;
+ _random = new Random();
+ _emailService = emailService;
+ }
+
+ public class SendVerificationCodeDto
+ {
+ [Required(ErrorMessage = "电子邮件地址是必填项")]
+ [EmailAddress(ErrorMessage = "请输入有效的电子邮件地址")]
+ public string Email { get; set; }
+ }
+
+ // 生成并发送验证码
+ public async Task SendVerificationCodeAsync(string email)
+ {
+ // 生成6位数验证码
+ string verificationCode = GenerateVerificationCode();
+
+ // 将验证码保存到分布式缓存,设置10分钟过期
+ var options = new DistributedCacheEntryOptions
+ {
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
+ };
+
+ await _cache.SetStringAsync($"EmailVerification_{email}", verificationCode, options);
+
+ var emailBody = EmailTemplateService.ReplaceTemplate(EmailTemplateService.RegisterHtmlTemplates, new Dictionary
+ {
+ { "RegisterCode", verificationCode }
+ });
+
+ // 发送验证码邮件
+ await _emailService.SendEmailAsync(email, "LMS注册验证码", emailBody);
+ }
+
+ // 验证用户提交的验证码
+ public async Task VerifyCodeAsync(string email, string code)
+ {
+ var storedCode = await _cache.GetStringAsync($"EmailVerification_{email}");
+
+ if (string.IsNullOrEmpty(storedCode))
+ return false;
+
+ // 使用完就删除验证码
+ if (storedCode == code)
+ {
+ await _cache.RemoveAsync($"EmailVerification_{email}");
+ return true;
+ }
+
+ return false;
+ }
+
+ private string GenerateVerificationCode()
+ {
+ return _random.Next(100000, 999999).ToString();
+ }
+}
\ No newline at end of file
diff --git a/LMS.service/Extensions/Middleware/DynamicPermissionMiddleware.cs b/LMS.service/Extensions/Middleware/DynamicPermissionMiddleware.cs
index 2f798d9..e20d221 100644
--- a/LMS.service/Extensions/Middleware/DynamicPermissionMiddleware.cs
+++ b/LMS.service/Extensions/Middleware/DynamicPermissionMiddleware.cs
@@ -4,14 +4,9 @@ using System.Security.Claims;
namespace LMS.service.Extensions.Middleware
{
- public class DynamicPermissionMiddleware
+ public class DynamicPermissionMiddleware(RequestDelegate next)
{
- private readonly RequestDelegate _next;
-
- public DynamicPermissionMiddleware(RequestDelegate next)
- {
- _next = next;
- }
+ private readonly RequestDelegate _next = next;
public async Task InvokeAsync(HttpContext context, PremissionValidationService _premissionValidationServices)
{
@@ -45,7 +40,7 @@ namespace LMS.service.Extensions.Middleware
}
}
- private long GetUserIdFromContext(HttpContext context)
+ private static long GetUserIdFromContext(HttpContext context)
{
var userIdClaim = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
var userId = userIdClaim?.Value;
diff --git a/LMS.service/LMS.service.csproj b/LMS.service/LMS.service.csproj
index d71f536..5be8ef6 100644
--- a/LMS.service/LMS.service.csproj
+++ b/LMS.service/LMS.service.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -7,11 +7,10 @@
ed64fb6f-9c93-43d0-b418-61f507f28420
Linux
.
- 1.0.3
-
+
@@ -23,8 +22,15 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/LMS.service/Program.cs b/LMS.service/Program.cs
index 0c38641..8402867 100644
--- a/LMS.service/Program.cs
+++ b/LMS.service/Program.cs
@@ -6,8 +6,7 @@ using LMS.service.Configuration.InitConfiguration;
using LMS.service.Extensions.Middleware;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
+using Serilog;
var builder = WebApplication.CreateBuilder(args);
@@ -33,6 +32,10 @@ builder.Services.ConfigureApplicationCookie(options =>
//JWT
builder.Services.AddJWTAuthentication();
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
+builder.Services.AddLoggerService();
+builder.Host.UseSerilog();
+// ؼ裺ע Serilog.ILogger DI
+builder.Services.AddSingleton(Log.Logger);
builder.Services.AddDbContext(options =>
{
@@ -76,6 +79,8 @@ builder.Services.AddHostedService();
var app = builder.Build();
+var version = builder.Configuration["Version"];
+
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
@@ -102,5 +107,6 @@ app.UseEndpoints(endpoints =>
_ = endpoints.MapControllers();
});
+Log.Information("̨ɹϵͳ汾" + version);
-app.Run();
+app.Run();
\ No newline at end of file
diff --git a/LMS.service/Service/MachineService.cs b/LMS.service/Service/MachineService.cs
index 685c7db..50f14c0 100644
--- a/LMS.service/Service/MachineService.cs
+++ b/LMS.service/Service/MachineService.cs
@@ -10,6 +10,8 @@ using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
+using System.Linq;
using static LMS.Common.Enums.MachineEnum;
using static LMS.Common.Enums.ResponseCodeEnum;
using static LMS.Repository.DTO.MachineResponse.MachineDto;
@@ -321,7 +323,7 @@ namespace LMS.service.Service
///
///
///
- internal async Task>>> QueryMachineCollection(int page, int pageSize, string? machineId, string? createdUserName, MachineStatus? status, MachineUseStatus? useStatus, string? remark, string? ownUserName, long requestUserId)
+ public async Task>>> QueryMachineCollection(int page, int pageSize, string? machineId, string? createdUserName, MachineStatus? status, MachineUseStatus? useStatus, string? remark, string? ownUserName, long requestUserId)
{
try
{
@@ -332,35 +334,94 @@ namespace LMS.service.Service
}
bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin");
bool isAdmin = await _userManager.IsInRoleAsync(user, "Admin");
+ bool isAgent = await _userManager.IsInRoleAsync(user, "Agent User");
IQueryable query = _context.Machine;
- if (isAdmin)
- {
- List superAdminUserIds = ((List)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))
+
+ // 更具用户角色判断当前可能查询那些用户的机器码
+
+ if (!isAdmin && !isSuperAdmin && !isAgent)
{
- List queryUserId = (await _userManager.Users.Where(x => x.UserName.Contains(createdUserName)).ToListAsync()).Select(x => x.Id).ToList();
- query = query.Where(x => queryUserId.Contains(x.CreateId));
+ // 普通用户只能查看所属自己的机器码,不具备查询创建者和所属者的权限
+ query = query.Where(x => x.UserID == user.Id);
}
- // 普通用户只能查找自己创建的机器码
- else if (!string.IsNullOrWhiteSpace(createdUserName))
+ else
{
- query = query.Where(x => x.CreateId == user.Id);
+ // 获取相关用户ID
+ var userLookupQuery = _userManager.Users.AsNoTracking();
+
+ HashSet filteredCreatorIds = null;
+ HashSet filteredOwnerIds = null;
+
+ if (!string.IsNullOrWhiteSpace(createdUserName))
+ {
+ // 获取列表后直接转换为HashSet
+ var list = await userLookupQuery
+ .Where(u => u.UserName.Contains(createdUserName))
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ filteredCreatorIds = new HashSet(list);
+ }
+
+ if (!string.IsNullOrWhiteSpace(ownUserName))
+ {
+ var list = await userLookupQuery
+ .Where(u => u.UserName.Contains(ownUserName))
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ filteredOwnerIds = new HashSet(list);
+ }
+
+ // 数据过滤
+ if (filteredCreatorIds?.Count > 0)
+ {
+ query = query.Where(x => filteredCreatorIds.Contains(x.CreateId));
+ }
+
+ if (filteredOwnerIds?.Count > 0)
+ {
+ query = query.Where(x => filteredOwnerIds.Contains(x.UserID));
+ }
+
+
+ if (isAdmin && !isSuperAdmin)
+ {
+ // 除了超级管理员的代理 其他都能看到
+ IList superUsers = await _userManager.GetUsersInRoleAsync("Super Admin");
+ List superUserIds = superUsers.Select(x => x.Id).ToList();
+ var list = await userLookupQuery
+ .Where(u => u.ParentId == null || superUserIds.Contains((long)u.ParentId) || superUserIds.Contains(u.Id))
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ HashSet filteredParentIds = new(list);
+ if (filteredParentIds?.Count > 0)
+ {
+ query = query.Where(x => !filteredParentIds.Contains(x.UserID) || x.UserID == requestUserId);
+ }
+ }
+ else if (isAgent && !isSuperAdmin)
+ {
+ // 代理只能看到自己下面的用户
+ var list = await userLookupQuery
+ .Where(u => u.ParentId == requestUserId)
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ HashSet filteredParentIds = new(list);
+ if (filteredParentIds?.Count > 0)
+ {
+ query = query.Where(x => filteredParentIds.Contains(x.UserID) || x.UserID == requestUserId);
+ }
+ }
}
if (status != null)
@@ -376,18 +437,6 @@ namespace LMS.service.Service
query = query.Where(x => x.Remark.Contains(remark));
}
- // 管理员和超级管理员可以使用该字段查询所有的机器码的拥有者
- if (!string.IsNullOrWhiteSpace(ownUserName) && (isAdmin || isSuperAdmin))
- {
- List 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();
// 降序,取指定的条数的数据
diff --git a/LMS.service/Service/OptionsService.cs b/LMS.service/Service/OptionsService.cs
index f234527..d4fea9a 100644
--- a/LMS.service/Service/OptionsService.cs
+++ b/LMS.service/Service/OptionsService.cs
@@ -1,11 +1,13 @@
using AutoMapper;
using LMS.Common.Dictionary;
+using LMS.Common.Templates;
using LMS.DAO;
+using LMS.DAO.UserDAO;
using LMS.Repository.DB;
using LMS.Repository.DTO;
-using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
using LMS.Repository.Options;
+using LMS.service.Extensions.Mail;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -13,11 +15,13 @@ using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Service
{
- public class OptionsService(ApplicationDbContext context, UserManager userManager, IMapper mapper)
+ public class OptionsService(ApplicationDbContext context, UserManager userManager, IMapper mapper, UserBasicDao userBasicDao, EmailService emailService)
{
private readonly ApplicationDbContext _context = context;
private readonly UserManager _userManager = userManager;
private readonly IMapper _mapper = mapper;
+ private readonly UserBasicDao _userBasicDao = userBasicDao;
+ private readonly EmailService _emailService = emailService;
#region 获取简单的配置项,无需权限
@@ -159,5 +163,42 @@ namespace LMS.service.Service
}
}
#endregion
+
+ #region 测试邮箱发送
+
+ ///
+ /// 测试邮箱发送
+ ///
+ ///
+ ///
+ public async Task>> TestSendMail(long userId)
+ {
+ try
+ {
+ // 判断是不是超级管理员
+ bool isSuperAdmin = await _userBasicDao.CheckUserIsSuperAdmin(userId);
+ if (!isSuperAdmin)
+ {
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
+ }
+
+ var emailBody = EmailTemplateService.ReplaceTemplate(EmailTemplateService.RegisterHtmlTemplates, new Dictionary
+ {
+ { "RegisterCode", "验证码" }
+ });
+
+ // 调用发送邮件的方法
+ await _emailService.SendEmailAsync("user@example.com", "邮件连通测试", emailBody, true);
+
+
+ return APIResponseModel.CreateSuccessResponseModel("邮箱测试发送成功");
+ }
+ catch (Exception ex)
+ {
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.SystemError, ex.Message);
+ }
+ }
+
+ #endregion
}
}
diff --git a/LMS.service/Service/PromptService/PromptService.cs b/LMS.service/Service/PromptService/PromptService.cs
index 7a1fb61..49c8df5 100644
--- a/LMS.service/Service/PromptService/PromptService.cs
+++ b/LMS.service/Service/PromptService/PromptService.cs
@@ -12,7 +12,6 @@ using LMS.Tools.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
-using System.Collections.Generic;
using static LMS.Common.Enums.PromptEnum;
using static LMS.Common.Enums.ResponseCodeEnum;
diff --git a/LMS.service/Service/SoftwareService/SoftwareControlService.cs b/LMS.service/Service/SoftwareService/SoftwareControlService.cs
index aa0230a..f3e26e8 100644
--- a/LMS.service/Service/SoftwareService/SoftwareControlService.cs
+++ b/LMS.service/Service/SoftwareService/SoftwareControlService.cs
@@ -10,17 +10,19 @@ using LMS.Repository.Models.DB;
using LMS.Repository.Software;
using LMS.Tools;
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.SoftwareService
{
- public class SoftwareControlService(UserBasicDao userBasicDao, ApplicationDbContext dbContext, IMapper mapper)
+ public class SoftwareControlService(UserBasicDao userBasicDao, ApplicationDbContext dbContext, IMapper mapper, UserManager userManager)
{
private readonly UserBasicDao _userBasicDao = userBasicDao;
private readonly ApplicationDbContext _dbContext = dbContext;
private readonly IMapper _mapper = mapper;
+ private readonly UserManager _userManager = userManager;
#region 软件控制-同步用户的软件控制权限
///
@@ -251,9 +253,13 @@ namespace LMS.service.Service.SoftwareService
{
// 判断权限,如果不是管理员或超级管理员,就判断是不是自己的数据,不是的话,返回无权限操作
IQueryable query = _dbContext.SoftwareControl.AsQueryable();
- bool isAdminOrSuperAdmin = await _userBasicDao.CheckUserIsAdminOrSuperAdmin(requestUserId);
- if (!isAdminOrSuperAdmin && userId != requestUserId)
+ bool isSuperAdmin = await _userBasicDao.CheckUserIsSuperAdmin(requestUserId);
+ bool isAdmin = await _userBasicDao.CheckUserIsAdmin(requestUserId);
+ bool isAgent = await _userBasicDao.CheckUserIsAgent(requestUserId);
+
+
+ if (!(isSuperAdmin || isAdmin || isAgent) && userId != requestUserId)
{
return APIResponseModel>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
@@ -275,14 +281,52 @@ namespace LMS.service.Service.SoftwareService
query = query.Where(x => x.Remark.Contains(remark));
}
-
- if (!isAdminOrSuperAdmin)
+ if (!(isSuperAdmin || isAdmin))
{
// 通过 softwareId 过滤掉isUse为false的数
List softwareIds = await _dbContext.Software.Where(x => x.IsUse == true).Select(x => x.Id).ToListAsync();
query = query.Where(x => softwareIds.Contains(x.SoftwareId));
}
+ // 做筛选权限
+ // 获取相关用户ID
+ var userLookupQuery = _userManager.Users.AsNoTracking();
+ if (isAdmin && !isSuperAdmin)
+ {
+ // 除了超级管理员的代理 其他都能看到
+ IList superUsers = await _userManager.GetUsersInRoleAsync("Super Admin");
+ List superUserIds = superUsers.Select(x => x.Id).ToList();
+ var list = await userLookupQuery
+ .Where(u => u.ParentId == null || superUserIds.Contains((long)u.ParentId) || superUserIds.Contains(u.Id))
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ HashSet filteredParentIds = new(list);
+ if (filteredParentIds?.Count > 0)
+ {
+ query = query.Where(x => !filteredParentIds.Contains(x.UserId) || x.UserId == requestUserId);
+ }
+ }
+ else if (isAgent && !isSuperAdmin)
+ {
+ // 代理只能看到自己下面的用户
+ var list = await userLookupQuery
+ .Where(u => u.ParentId == requestUserId)
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ HashSet filteredParentIds = new(list);
+ if (filteredParentIds?.Count > 0)
+ {
+ query = query.Where(x => filteredParentIds.Contains(x.UserId));
+ }
+ }
+ else if (!isSuperAdmin)
+ {
+ // 普通用户只能看到自己的
+ query = query.Where(x => x.UserId == requestUserId);
+ }
+
// 通过ID降序
query = query.OrderByDescending(x => x.CreatedTime);
diff --git a/LMS.service/Service/UserService/UserService.cs b/LMS.service/Service/UserService/UserService.cs
index 5bdbbd5..3ca6168 100644
--- a/LMS.service/Service/UserService/UserService.cs
+++ b/LMS.service/Service/UserService/UserService.cs
@@ -1,5 +1,8 @@
-using LMS.Common.RSAKey;
+using LMS.Common.Enum;
+using LMS.Common.RSAKey;
using LMS.DAO;
+using LMS.DAO.UserDAO;
+using LMS.Repository.DB;
using LMS.Repository.DTO;
using LMS.Repository.DTO.UserDto;
using LMS.Repository.Models.DB;
@@ -7,7 +10,6 @@ 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;
@@ -15,12 +17,14 @@ using static LMS.Common.Enums.ResponseCodeEnum;
namespace LMS.service.Service.UserService
{
- public class UserService(UserManager userManager, RoleManager roleManager, ApplicationDbContext context, SecurityService securityService)
+ public class UserService(UserManager userManager, RoleManager roleManager, ApplicationDbContext context, SecurityService securityService, EmailVerificationService emailVerificationService, UserBasicDao userBasicDao)
{
private readonly UserManager _userManager = userManager;
private readonly RoleManager _roleManager = roleManager;
private readonly ApplicationDbContext _context = context;
private readonly SecurityService _securityService = securityService;
+ private readonly EmailVerificationService _verificationService = emailVerificationService;
+ private readonly UserBasicDao _userBasicDao = userBasicDao;
#region 获取用户信息
///
@@ -97,11 +101,10 @@ namespace LMS.service.Service.UserService
{
return APIResponseModel>.CreateErrorResponseModel(ResponseCode.FindUserByIdFail);
}
-
- bool isAdminOrSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin") || await _userManager.IsInRoleAsync(user, "Admin");
+ bool isAdmin = await _userBasicDao.CheckUserIsAdmin(reuqertUserId);
bool isSuperAdmin = await _userManager.IsInRoleAsync(user, "Super Admin");
- bool isAgent = !isAdminOrSuperAdmin && await _userManager.IsInRoleAsync(user, "Agent User");
- if (!isAdminOrSuperAdmin && !isAgent)
+ bool isAgent = await _userManager.IsInRoleAsync(user, "Agent User");
+ if (!isAdmin && !isSuperAdmin && !isAgent)
{
return APIResponseModel>.CreateErrorResponseModel(ResponseCode.NotPermissionAction);
}
@@ -128,21 +131,46 @@ namespace LMS.service.Service.UserService
// 开始查询数据
IQueryable? query = _userManager.Users;
- if (isAgent)
- {
- query = query.Where(x => x.ParentId == user.Id);
- }
- // 判断是不是管理员
- IList superUsers = await _userManager.GetUsersInRoleAsync("Super Admin");
- List 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)));
+ query = query.Where(x => x.Id != reuqertUserId);
}
+ // 获取相关用户ID
+ var userLookupQuery = _userManager.Users.AsNoTracking();
+ if (isAdmin && !isSuperAdmin)
+ {
+ // 除了超级管理员的代理 其他都能看到
+ IList superUsers = await _userManager.GetUsersInRoleAsync("Super Admin");
+ List superUserIds = superUsers.Select(x => x.Id).ToList();
+ var list = await userLookupQuery
+ .Where(u => u.ParentId == null || superUserIds.Contains((long)u.ParentId) || superUserIds.Contains(u.Id))
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ HashSet filteredParentIds = new(list);
+ if (filteredParentIds?.Count > 0)
+ {
+ query = query.Where(x => !filteredParentIds.Contains(x.Id));
+ }
+ }
+ else if (isAgent && !isSuperAdmin)
+ {
+ // 代理只能看到自己下面的用户
+ var list = await userLookupQuery
+ .Where(u => u.ParentId == reuqertUserId)
+ .Select(u => u.Id)
+ .ToListAsync();
+
+ HashSet filteredParentIds = new(list);
+ if (filteredParentIds?.Count > 0)
+ {
+ query = query.Where(x => filteredParentIds.Contains(x.Id));
+ }
+ }
+
+
// 添加查询条件
if (!string.IsNullOrWhiteSpace(userName))
{
@@ -214,7 +242,7 @@ namespace LMS.service.Service.UserService
{
List? roles = [.. (await _userManager.GetRolesAsync(users[i]))];
userCollections[i].RoleNames = roles;
- if (!isAdminOrSuperAdmin)
+ if (!isSuperAdmin || isAdmin)
{
userCollections[i].PhoneNumber = "***********";
userCollections[i].Email = "***********";
@@ -448,6 +476,25 @@ namespace LMS.service.Service.UserService
return APIResponseModel.CreateErrorResponseModel(ResponseCode.InvalidAffiliateCode);
}
+ // 判断邮箱是不是被使用了
+ var existingUser = await _userManager.FindByEmailAsync(model.Email);
+ if (existingUser != null)
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "当前邮箱已注册,请直接登录!");
+
+ // 验证验证码
+ // 判断是不是需要校验验证码
+ Options? enaleMailService = await _context.Options.FirstOrDefaultAsync(x => x.Key == OptionKeyName.EnableMailService);
+ if (enaleMailService != null)
+ {
+ _ = bool.TryParse(enaleMailService.Value, out bool enableMail);
+ if (enableMail)
+ {
+ var isCodeValid = await _verificationService.VerifyCodeAsync(model.Email, model.VerificationCode);
+ if (!isCodeValid)
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "验证码无效或已过期");
+ }
+ }
+
var rsaKeyId = keyInfo.Key;
var privateKey = _securityService.DecryptWithAES(rsaKeyId);
@@ -491,5 +538,26 @@ namespace LMS.service.Service.UserService
}
#endregion
+
+ #region 发送用户注册验证码
+ public async Task>> SendVerificationCode(EmailVerificationService.SendVerificationCodeDto model)
+ {
+ try
+ {
+ // 检查邮箱是否已被使用
+ var existingUser = await _userManager.FindByEmailAsync(model.Email);
+ if (existingUser != null)
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "当前邮箱已注册,请直接登录!");
+
+ // 发送验证码
+ await _verificationService.SendVerificationCodeAsync(model.Email);
+ return APIResponseModel.CreateSuccessResponseModel(ResponseCode.Success, "验证码发送成功,请在邮箱中查收!");
+ }
+ catch (Exception e)
+ {
+ return APIResponseModel.CreateErrorResponseModel(ResponseCode.SystemError, e.Message);
+ }
+ }
+ #endregion
}
}
diff --git a/LMS.service/appsettings.json b/LMS.service/appsettings.json
index 10f68b8..d3a6715 100644
--- a/LMS.service/appsettings.json
+++ b/LMS.service/appsettings.json
@@ -5,5 +5,27 @@
"Microsoft.AspNetCore": "Warning"
}
},
+ "Serilog": {
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Warning",
+ "System": "Warning"
+ }
+ },
+ "WriteTo": [
+ {
+ "Name": "File",
+ "Args": {
+ "path": "logs/app-.log",
+ "rollingInterval": "Day",
+ "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
+ "retainedFileCountLimit": 31
+ }
+ }
+ ],
+ "Enrich": [ "FromLogContext" ]
+ },
+ "Version": "1.0.4",
"AllowedHosts": "*"
}