From 25d481d7d62896f0b2801ea85c27906d7ba98863 Mon Sep 17 00:00:00 2001 From: lq1405 <2769838458@qq.com> Date: Sat, 13 Sep 2025 18:04:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=20=20=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E8=BD=AC?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LMS.Repository/DTO/FileUploadDto.cs | 13 + LMS.Repository/DTO/PromptDto/PromptNameDto.cs | 2 + LMS.Tools/FileTool/FileService.cs | 70 ++++ LMS.Tools/FileTool/IQiniuService.cs | 60 +++ LMS.Tools/FileTool/QiniuService.cs | 135 +++++++ LMS.Tools/HttpTool/HttpService.cs | 173 +++++++++ LMS.Tools/HttpTool/IHttpService.cs | 45 +++ LMS.Tools/LMS.Tools.csproj | 2 + .../Configuration/ServiceConfiguration.cs | 11 + .../Controllers/FileUploadController.cs | 39 +- LMS.service/Controllers/ForwardController.cs | 22 +- LMS.service/Dockerfile | 2 +- LMS.service/Program.cs | 50 +-- .../FileUploadService/IQiniuUploadService.cs | 44 ++- .../FileUploadService/QiniuUploadService.cs | 357 +++++++++--------- .../Service/PromptService/PromptService.cs | 3 +- 16 files changed, 785 insertions(+), 243 deletions(-) create mode 100644 LMS.Tools/FileTool/FileService.cs create mode 100644 LMS.Tools/FileTool/IQiniuService.cs create mode 100644 LMS.Tools/FileTool/QiniuService.cs create mode 100644 LMS.Tools/HttpTool/HttpService.cs create mode 100644 LMS.Tools/HttpTool/IHttpService.cs diff --git a/LMS.Repository/DTO/FileUploadDto.cs b/LMS.Repository/DTO/FileUploadDto.cs index c6b3350..784251e 100644 --- a/LMS.Repository/DTO/FileUploadDto.cs +++ b/LMS.Repository/DTO/FileUploadDto.cs @@ -17,6 +17,19 @@ namespace LMS.Repository.DTO public Dictionary Metadata { get; set; } = new(); } + /// + /// URL上传请求 + /// + public class UrlUploadRequest + { + [Required] + [Url] + public required string Url { get; set; } + + [Required] + public required string FileName { get; set; } + } + public class UploadResult { public bool Success { get; set; } diff --git a/LMS.Repository/DTO/PromptDto/PromptNameDto.cs b/LMS.Repository/DTO/PromptDto/PromptNameDto.cs index 3ecb9b3..64b871d 100644 --- a/LMS.Repository/DTO/PromptDto/PromptNameDto.cs +++ b/LMS.Repository/DTO/PromptDto/PromptNameDto.cs @@ -9,4 +9,6 @@ public class PromptNameDto public string PromptTypeId { get; set; } public string? Remark { get; set; } + + public string? Description { get; set; } } diff --git a/LMS.Tools/FileTool/FileService.cs b/LMS.Tools/FileTool/FileService.cs new file mode 100644 index 0000000..f7a52f7 --- /dev/null +++ b/LMS.Tools/FileTool/FileService.cs @@ -0,0 +1,70 @@ +using LMS.DAO.OptionDAO; +using LMS.Tools.ImageTool; +using static LMS.Repository.DTO.FileUploadDto; + +namespace LMS.Tools.FileTool +{ + public static class FileService + { + public static byte[]? ConvertBase64ToBytes(string base64String) + { + if (string.IsNullOrEmpty(base64String)) + { + return null; + } + // 检查是否以 "data:" 开头并包含 base64 编码,如果是则提取实际的 base64 部分 + if (base64String.StartsWith("data:")) + { + // 提取 base64 编码部分 + var commaIndex = base64String.IndexOf(','); + if (commaIndex >= 0) + { + base64String = base64String.Substring(commaIndex + 1); + } + } + try + { + // 尝试将 base64 字符串转换为字节数组 + return Convert.FromBase64String(base64String); + } + catch (FormatException) + { + // 如果格式不正确,返回 null + return null; + } + } + + public static bool IsValidImageFile(byte[] fileBytes) + { + if (fileBytes == null || fileBytes.Length == 0) + { + return false; + } + + return ImageTypeDetector.IsValidImage(fileBytes); + } + + public static async Task CheckFileSize(byte[] fileBytes, double MaxFileSize) + { + if (fileBytes == null || fileBytes.Length == 0) + { + return new UploadResult + { + Success = false, + Message = "文件不能为空" + }; + } + + if (fileBytes.Length > MaxFileSize * 1024 * 1024) + { + return new UploadResult + { + Success = false, + Message = $"文件大小({fileBytes.Length} bytes)超过限制({MaxFileSize * 1024 * 1024} bytes)" + }; + } + + return new UploadResult { Success = true }; + } + } +} diff --git a/LMS.Tools/FileTool/IQiniuService.cs b/LMS.Tools/FileTool/IQiniuService.cs new file mode 100644 index 0000000..2a1efea --- /dev/null +++ b/LMS.Tools/FileTool/IQiniuService.cs @@ -0,0 +1,60 @@ +using LMS.Repository.DB; +using LMS.Repository.FileUpload; +using Qiniu.Http; +using static LMS.Repository.DTO.FileUploadDto; + +namespace LMS.Tools.FileTool +{ + public interface IQiniuService + { + /// + /// 检查文件的字节大小是否符合要求 + /// + /// + /// + public Task CheckFileBytesSize(byte[] fileBytes); + + /// + /// 生成七牛云上传的路径 key + /// + /// + /// + /// + string GenerateFileKey(long userId, string fileName); + + /// + /// 计算文件的 SHA1 哈希值 + /// + /// + /// + string ComputeSHA1Hash(byte[] data); + + /// + /// 获取七牛云的配置 用于上传图片 + /// + /// + Task InitQiniuSetting(); + + /// + /// 生成七牛的上传凭证 + /// + /// + /// + string GeneratePolicy(QiniuSettings qiniuSettings); + + /// + /// 将 byte 数组上传到七牛云 + /// + /// + /// + Task UploadFileToQiNiu(byte[] fileBytes, long userId, string fileName, string fileKey); + + /// + /// 构建文件的访问 URL + /// + /// + /// + /// + string BuildFileUrl(string domain, string fileKey); + } +} diff --git a/LMS.Tools/FileTool/QiniuService.cs b/LMS.Tools/FileTool/QiniuService.cs new file mode 100644 index 0000000..63b0bb1 --- /dev/null +++ b/LMS.Tools/FileTool/QiniuService.cs @@ -0,0 +1,135 @@ +using LMS.Common.Extensions; +using LMS.DAO.OptionDAO; +using LMS.Repository.DB; +using LMS.Repository.FileUpload; +using Microsoft.Extensions.Logging; +using Qiniu.Http; +using Qiniu.IO; +using Qiniu.IO.Model; +using Qiniu.Util; +using System.Security.Cryptography; +using static LMS.Repository.DTO.FileUploadDto; + +namespace LMS.Tools.FileTool +{ + public class QiniuService(OptionGlobalDAO optionGlobalDAO, ILogger logger) : IQiniuService + { + private readonly OptionGlobalDAO _optionGlobalDAO = optionGlobalDAO; + private readonly UploadManager _uploadManager = new UploadManager(); + private readonly ILogger _logger = logger; + + /// + /// 检查文件的字节大小是否符合要求 + /// + /// + /// + public async Task CheckFileBytesSize(byte[] fileBytes) + { + if (fileBytes == null || fileBytes.Length == 0) + { + return new UploadResult + { + Success = false, + Message = "文件字节数据不能为空" + }; + } + + double MaxFileSize = await _optionGlobalDAO.FindAndReturnOption("SYS_MaxUploadFileSize"); + + if (fileBytes.Length > MaxFileSize * 1024 * 1024) + { + return new UploadResult + { + Success = false, + Message = $"文件大小不能超过 {MaxFileSize}MB" + }; + } + + return new UploadResult { Success = true, Message = string.Empty }; + } + + public string ComputeSHA1Hash(byte[] data) + { + var hash = SHA1.HashData(data); + return Convert.ToHexString(hash).ToLower(); + } + + public string GenerateFileKey(long userId, string fileName) + { + var date = DateTime.Now.ToString("yyyyMMdd"); + //var extension = Path.GetExtension(fileName); + return $"user/{userId}/{date}/{fileName}"; + } + + public async Task InitQiniuSetting() + { + QiniuSettings? qiniuSettings = await _optionGlobalDAO.FindAndReturnOption("SYS_QiniuSetting"); + + if (qiniuSettings == null || string.IsNullOrEmpty(qiniuSettings.AccessKey) || string.IsNullOrEmpty(qiniuSettings.SecretKey) || string.IsNullOrEmpty(qiniuSettings.BucketName) || string.IsNullOrEmpty(qiniuSettings.Domain)) + { + throw new Exception("七牛云配置不完整,请检查配置,请联系管理员"); + } + return qiniuSettings; + } + + public string GeneratePolicy(QiniuSettings qiniuSettings) + { + Mac mac = new(qiniuSettings.AccessKey, qiniuSettings.SecretKey); + var putPolicy = new PutPolicy + { + Scope = qiniuSettings.BucketName + }; + if (qiniuSettings.DeleteDay != null) + { + putPolicy.DeleteAfterDays = qiniuSettings.DeleteDay.Value; // 设置过期时间 + } + putPolicy.SetExpires(3600); + string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); + return token; + } + + public string BuildFileUrl(string domain, string fileKey) + { + return $"{domain}/{fileKey}"; + } + + public async Task UploadFileToQiNiu(byte[] fileBytes, long userId, string fileName, string fileKey) + { + QiniuSettings qiniuSettings = await InitQiniuSetting(); + string token = GeneratePolicy(qiniuSettings); + string hash = ComputeSHA1Hash(fileBytes); + HttpResult uploadResult; + + _logger.LogInformation("开始上传文件, 用户ID: {userId}, 文件名: {fileName}, 文件大小: {fileLength} 字节, 文件Key: {fileKey}", userId, fileName, fileBytes.Length, fileKey); + + using (var stream = new MemoryStream(fileBytes)) + { + uploadResult = await _uploadManager.UploadStreamAsync(stream, fileKey, token); + } + // 8. 检查上传结果 + if (uploadResult.Code != 200) + { + _logger.LogError("文件上传失败, 上传用户ID: {userId}, 错误信息: {error}", userId, uploadResult.Text); + throw new Exception(uploadResult.Text); + } + + var qiniuUrl = BuildFileUrl(qiniuSettings.Domain, fileKey); + + _logger.LogInformation("文件上传成功, 上传用户ID: {userId}, 文件Key: {fileKey},文件链接: {url}", userId, fileKey, qiniuUrl); + return new FileUploads + { + UserId = userId, + FileName = fileName, + FileKey = fileKey, + FileSize = fileBytes.Length, + ContentType = "application/octet-stream", + Hash = hash, + QiniuUrl = qiniuUrl, + UploadTime = DateTime.Now, + Status = "active", + CreatedAt = DateTime.Now, + DeleteTime = qiniuSettings.DeleteDay != null ? BeijingTimeExtension.GetBeijingTime().AddDays((double)qiniuSettings.DeleteDay) : DateTime.MaxValue // 默认未删除 + }; + } + } +} diff --git a/LMS.Tools/HttpTool/HttpService.cs b/LMS.Tools/HttpTool/HttpService.cs new file mode 100644 index 0000000..7199af7 --- /dev/null +++ b/LMS.Tools/HttpTool/HttpService.cs @@ -0,0 +1,173 @@ +using LMS.Tools.FileTool; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace LMS.Tools.HttpTool +{ + /// + /// HTTP + /// + public class HttpService : IHttpService + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + + public HttpService( + IHttpClientFactory httpClientFactory, + ILogger logger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + } + + /// + /// ļֽ + /// + /// ļURL + /// ļֽ + public async Task DownloadFileAsync(string url, double maxFileSize) + { + try + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentException("URLΪ", nameof(url)); + + if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) + throw new ArgumentException("ЧURLʽ", nameof(url)); + + using var httpClient = _httpClientFactory.CreateClient("HttpService"); + using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"HTTPʧܣ״̬: {response.StatusCode}"); + } + + // ļС + if (response.Content.Headers.ContentLength.HasValue) + { + if (response.Content.Headers.ContentLength.Value > maxFileSize * 1024 * 1024) + { + throw new InvalidOperationException($"ļС({response.Content.Headers.ContentLength.Value} bytes)({maxFileSize * 1024 * 1024} bytes)"); + } + } + + var fileBytes = await response.Content.ReadAsByteArrayAsync(); + + if (fileBytes.Length > maxFileSize * 1024 * 1024) + { + throw new InvalidOperationException($"صļС({fileBytes.Length} bytes)({maxFileSize * 1024 * 1024} bytes)"); + } + + return fileBytes; + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "HTTP쳣: {Url}", url); + throw; + } + catch (TaskCanceledException ex) + { + _logger.LogError(ex, "ʱ: {Url}", url); + throw new TimeoutException("ʱ", ex); + } + catch (Exception ex) + { + _logger.LogError(ex, "ļʧ: {Url}", url); + throw; + } + } + + /// + /// GET + /// + /// URL + /// Ӧ + public async Task GetAsync(string url) + { + try + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentException("URLΪ", nameof(url)); + + using var httpClient = _httpClientFactory.CreateClient("HttpService"); + var response = await httpClient.GetStringAsync(url); + return response; + } + catch (Exception ex) + { + _logger.LogError(ex, "GETʧ: {Url}", url); + throw; + } + } + + /// + /// POST + /// + /// URL + /// + /// Ӧ + public async Task PostAsync(string url, string content) + { + try + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentException("URLΪ", nameof(url)); + + using var httpClient = _httpClientFactory.CreateClient("HttpService"); + var httpContent = new StringContent(content, Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(url, httpContent); + + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, "POSTʧ: {Url}", url); + throw; + } + } + + /// + /// URLǷɷ + /// + /// ҪURL + /// Ƿɷ + public async Task IsUrlAccessibleAsync(string url) + { + try + { + using var httpClient = _httpClientFactory.CreateClient("HttpService"); + using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + return response.IsSuccessStatusCode; + } + catch + { + return false; + } + } + + /// + /// ȡURLContent-Type + /// + /// ҪURL + /// Content-Type + public async Task GetContentTypeAsync(string url) + { + try + { + using var httpClient = _httpClientFactory.CreateClient("HttpService"); + using var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.ContentType?.MediaType; + } + return null; + } + catch + { + return null; + } + } + } +} \ No newline at end of file diff --git a/LMS.Tools/HttpTool/IHttpService.cs b/LMS.Tools/HttpTool/IHttpService.cs new file mode 100644 index 0000000..f47b5f0 --- /dev/null +++ b/LMS.Tools/HttpTool/IHttpService.cs @@ -0,0 +1,45 @@ +namespace LMS.Tools.HttpTool +{ + /// + /// HTTPӿ + /// + public interface IHttpService + { + /// + /// ļֽ + /// + /// ļURL + /// ļСƣֽڣ + /// ļֽ + Task DownloadFileAsync(string url, double maxFileSize); + + /// + /// GET + /// + /// URL + /// Ӧ + Task GetAsync(string url); + + /// + /// POST + /// + /// URL + /// + /// Ӧ + Task PostAsync(string url, string content); + + /// + /// URLǷɷ + /// + /// ҪURL + /// Ƿɷ + Task IsUrlAccessibleAsync(string url); + + /// + /// ȡURLContent-Type + /// + /// ҪURL + /// Content-Type + Task GetContentTypeAsync(string url); + } +} \ No newline at end of file diff --git a/LMS.Tools/LMS.Tools.csproj b/LMS.Tools/LMS.Tools.csproj index 615b310..62f918f 100644 --- a/LMS.Tools/LMS.Tools.csproj +++ b/LMS.Tools/LMS.Tools.csproj @@ -9,6 +9,8 @@ + + diff --git a/LMS.service/Configuration/ServiceConfiguration.cs b/LMS.service/Configuration/ServiceConfiguration.cs index b72eb60..716b043 100644 --- a/LMS.service/Configuration/ServiceConfiguration.cs +++ b/LMS.service/Configuration/ServiceConfiguration.cs @@ -14,6 +14,8 @@ using LMS.service.Service.PromptService; using LMS.service.Service.RoleService; using LMS.service.Service.SoftwareService; using LMS.service.Service.UserService; +using LMS.Tools.FileTool; +using LMS.Tools.HttpTool; using LMS.Tools.MJPackage; namespace Lai_server.Configuration @@ -67,7 +69,16 @@ namespace Lai_server.Configuration services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + // 注册HTTP服务(注意:由于HttpService是单例,这里使用工厂模式) + services.AddSingleton(); // 注册为单例 + services.AddHttpClient("HttpService", client => + { + client.Timeout = TimeSpan.FromSeconds(30); + client.DefaultRequestHeaders.Add("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); + }); // 注册后台服务 services.AddHostedService(); diff --git a/LMS.service/Controllers/FileUploadController.cs b/LMS.service/Controllers/FileUploadController.cs index 371b37b..1135b29 100644 --- a/LMS.service/Controllers/FileUploadController.cs +++ b/LMS.service/Controllers/FileUploadController.cs @@ -54,34 +54,17 @@ namespace LMS.service.Controllers return await _qiniuUploadService.GetFilesByUser(requestUserId, userId, page, pageSize); } + /// + /// URL转存文件 + /// + /// + /// + /// + [HttpPost("{machineId}")] + public async Task>> UrlUpload(string machineId, [FromBody] UrlUploadRequest request) + { + return await _qiniuUploadService.UrlUpload(machineId, request); + } - - ///// - ///// 删除文件 - ///// - ///// 文件ID - ///// - //[HttpDelete("files/{fileId}")] - //public async Task DeleteFile(long fileId) - //{ - // try - // { - // var userId = GetCurrentUserId(); - // var success = await _qiniuUploadService.DeleteFileAsync(fileId, userId); - - // if (success) - // { - // return Ok(new { message = "删除成功" }); - // } - // else - // { - // return NotFound(new { message = "文件不存在或无权限删除" }); - // } - // } - // catch (Exception ex) - // { - // return BadRequest(new { message = $"删除失败: {ex.Message}" }); - // } - //} } } diff --git a/LMS.service/Controllers/ForwardController.cs b/LMS.service/Controllers/ForwardController.cs index 267dc55..2c6d42f 100644 --- a/LMS.service/Controllers/ForwardController.cs +++ b/LMS.service/Controllers/ForwardController.cs @@ -4,6 +4,7 @@ using LMS.service.Service; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using System.Text.Json; using static LMS.Common.Enums.ResponseCodeEnum; namespace LMS.service.Controllers; @@ -11,9 +12,10 @@ namespace LMS.service.Controllers; [Route("lms/[controller]/[action]")] [ApiController] -public class ForwardController(ForwardWordService forwardWordService) : ControllerBase +public class ForwardController(ForwardWordService forwardWordService, ILogger logger) : ControllerBase { private readonly ForwardWordService _forwardWordService = forwardWordService; + private readonly ILogger _logger = logger; #region 非流转发接口,需要系统数据 @@ -114,4 +116,22 @@ public class ForwardController(ForwardWordService forwardWordService) : Controll } #endregion + + [HttpPost] + [Route("/lms/v1/chat/completions")] + public async Task>> Chat(JsonElement json) + { + _logger.LogInformation("Received chat request: {Json}", json.GetRawText()); + return APIResponseModel.CreateSuccessResponseModel(""); + } + + [HttpPost] + [Route("/lms/mj-relax/mj/submit/video")] + [Route("/lms/mj-fast/mj/submit/video")] + [Route("/lms/mj/submit/video")] + public async Task>> Imagine(JsonElement json) + { + _logger.LogInformation("Received chat request: {Json}", json.GetRawText()); + return APIResponseModel.CreateSuccessResponseModel(""); + } } diff --git a/LMS.service/Dockerfile b/LMS.service/Dockerfile index 018a754..b4cc7e5 100644 --- a/LMS.service/Dockerfile +++ b/LMS.service/Dockerfile @@ -9,7 +9,7 @@ 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/"] diff --git a/LMS.service/Program.cs b/LMS.service/Program.cs index 61d0d7b..a1fe13d 100644 --- a/LMS.service/Program.cs +++ b/LMS.service/Program.cs @@ -18,10 +18,10 @@ builder.Services.AddControllers(); builder.Services.AddAuthentication(); -// ӿ +// 添加跨域 builder.Services.AddCorsServices(); -// עԶ񷽷 +// 配置注入自定义服务方法 builder.Services.AddServices(); builder.Services.ConfigureApplicationCookie(options => @@ -31,12 +31,12 @@ builder.Services.ConfigureApplicationCookie(options => options.Cookie.SameSite = SameSiteMode.None; }); -//JWT +//配置JWT builder.Services.AddJWTAuthentication(); builder.Services.AddAutoMapper(typeof(AutoMapperConfig)); builder.Services.AddLoggerService(); builder.Host.UseSerilog(); -// ؼ裺ע Serilog.ILogger DI +// 关键步骤:注册 Serilog.ILogger 到 DI 容器 builder.Services.AddSingleton(Log.Logger); builder.Services.AddQuartzTaskSchedulerService(); @@ -46,22 +46,22 @@ builder.Services.AddDbContext(options => options.UseMySql(connectionString, ServerVersion.Parse("8.0.18-mysql")); }); -// ڴ滺棨ڴ洢Ƽ +// 添加内存缓存(用于存储速率限制计数器) builder.Services.AddMemoryCache(); -// ͨãappsettings.json +// 加载通用配置(从app settings.json) builder.Services.Configure(builder.Configuration.GetSection("IpRateLimiting")); builder.Services.Configure( builder.Configuration.GetSection("FileUploadSettings")); -// ģ֤ +// 配置模型验证 builder.Services.Configure(options => { - options.SuppressModelStateInvalidFilter = false; // ȷģ֤Ч + options.SuppressModelStateInvalidFilter = false; // 确保模型验证生效 }); -// ע͹洢 +// 注入计数器和规则存储 builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -71,20 +71,20 @@ builder.Services.AddSingleton(options => { - options.SignIn.RequireConfirmedAccount = true; //˺Ųܵ¼ + 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.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.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // 锁定时间 + options.Lockout.MaxFailedAccessAttempts = 10; // 尝试次数 + options.Lockout.AllowedForNewUsers = true; // 新用户是否可以锁定 - options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; // ûַ - options.User.RequireUniqueEmail = true; // ظ + options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; // 用户名允许的字符 + options.User.RequireUniqueEmail = true; // 允许重复邮箱 //options.User. }); @@ -96,7 +96,7 @@ idBuilder.AddEntityFrameworkStores() // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -// עSwagger +// 注入Swagger builder.Services.AddSwaggerService(); @@ -124,16 +124,18 @@ app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); -// ڹܵʹIPм +// 你好 + +// 在管道中使用IP速率限制中间件 app.UseIpRateLimiting(); -// Ӷ̬Ȩ޵м +// 添加动态权限的中间件 app.UseMiddleware(); app.UseEndpoints(endpoints => { _ = endpoints.MapControllers(); }); -Log.Information("̨ɹϵͳ汾" + version); +Log.Information("后台启动成功,系统版本:" + version); app.Run(); \ No newline at end of file diff --git a/LMS.service/Service/FileUploadService/IQiniuUploadService.cs b/LMS.service/Service/FileUploadService/IQiniuUploadService.cs index 13dc062..91e53a9 100644 --- a/LMS.service/Service/FileUploadService/IQiniuUploadService.cs +++ b/LMS.service/Service/FileUploadService/IQiniuUploadService.cs @@ -1,19 +1,51 @@ -using LMS.Repository.DB; -using LMS.Repository.DTO; -using LMS.Repository.FileUpload; +using LMS.Repository.DTO; using Microsoft.AspNetCore.Mvc; using static LMS.Repository.DTO.FileUploadDto; using static LMS.Repository.FileUpload.FileRequestReturn; namespace LMS.service.Service.FileUploadService { + /// + /// 七牛云文件上传服务接口 + /// 提供文件上传、文件列表查询等功能 + /// public interface IQiniuUploadService { + /// + /// 上传本地的文件到七牛云,接收 Base64 格式的文件 + /// + /// Base64格式的文件上传请求参数 + /// 机器码标识 + /// 返回上传结果,包含文件URL、Hash等信息 Task> UploadBase64Async(ByteUploadRequest request, string machineId); - //Task> GetUserFilesAsync(string userId, int page = 1, int pageSize = 20); - //Task GetUserFilesCountAsync(string userId); - //Task DeleteFileAsync(long fileId, string userId); + + /// + /// 通过网络URL转存文件到七牛云 + /// + /// 机器码标识 + /// URL上传请求参数,包含文件URL和文件名 + /// 返回转存结果,包含文件URL、Hash等信息 + Task>> UrlUpload(string machineId, UrlUploadRequest request); + + /// + /// 获取用户文件列表,通过机器码查询 + /// + /// 机器码标识 + /// 查看页码,从1开始 + /// 分页大小,每页显示的文件数量 + /// 返回分页的文件列表,包含文件基本信息 Task>>> GetFilesByMachineId(string machineId, int page, int pageSize); + + /// + /// 获取指定用户的文件列表 + /// 如果请求用户是超级管理员,可以查看所有用户的文件 + /// 普通用户只能查看自己的文件 + /// + /// 请求用户的ID + /// 要查询的用户ID + /// 查看页码,从1开始 + /// 分页大小,每页显示的文件数量 + /// 返回分页的用户文件列表,包含文件详细信息 Task>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize); } } diff --git a/LMS.service/Service/FileUploadService/QiniuUploadService.cs b/LMS.service/Service/FileUploadService/QiniuUploadService.cs index c60afc1..c1695d8 100644 --- a/LMS.service/Service/FileUploadService/QiniuUploadService.cs +++ b/LMS.service/Service/FileUploadService/QiniuUploadService.cs @@ -5,64 +5,78 @@ using LMS.DAO.UserDAO; using LMS.Repository.DB; using LMS.Repository.DTO; using LMS.Repository.FileUpload; -using LMS.Tools.ImageTool; +using LMS.Tools.FileTool; +using LMS.Tools.HttpTool; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using Qiniu.Http; -using Qiniu.IO; -using Qiniu.IO.Model; -using Qiniu.Util; -using System.Security.Cryptography; using static LMS.Common.Enums.ResponseCodeEnum; using static LMS.Repository.DTO.FileUploadDto; using static LMS.Repository.FileUpload.FileRequestReturn; namespace LMS.service.Service.FileUploadService { + /// + /// 七牛云文件上传服务实现类 + /// 提供Base64文件上传、URL转存、文件列表查询等功能 + /// 支持图片格式验证、文件大小限制、用户权限控制 + /// public class QiniuUploadService : IQiniuUploadService { + #region 私有字段 + private readonly FileUploadSettings _uploadSettings; private readonly ApplicationDbContext _dbContext; - private readonly UploadManager _uploadManager; private readonly ILogger _logger; private readonly UserBasicDao _userBasicDao; private readonly OptionGlobalDAO _optionGlobalDAO; + private readonly IQiniuService _qiniuService; + private readonly IHttpService _httpService; + #endregion + + #region 构造函数 + + /// + /// 初始化七牛云上传服务 + /// + /// 文件上传配置 + /// 日志记录器 + /// 用户基础数据访问对象 + /// 全局选项数据访问对象 + /// 七牛云服务接口 + /// 文件服务接口 + /// 数据库上下文 public QiniuUploadService( IOptions uploadSettings, ILogger logger, UserBasicDao userBasicDao, OptionGlobalDAO optionGlobalDAO, + IQiniuService qiniuService, + IHttpService httpService, ApplicationDbContext dbContext) { _uploadSettings = uploadSettings.Value; _logger = logger; _dbContext = dbContext; _optionGlobalDAO = optionGlobalDAO; - _userBasicDao = userBasicDao; ; - _uploadManager = new UploadManager(); + _userBasicDao = userBasicDao; + _qiniuService = qiniuService; + _httpService = httpService; } - /// - /// 通过字节数组上传文件到七牛云 - /// - /// - /// - /// + #endregion + + #region 公共方法 public async Task> UploadBase64Async(ByteUploadRequest request, string machineId) { try { - // 将 文件的base64 字符串转换为字节数组 - var fileBytes = ConvertBase64ToBytes(request.File); - if (fileBytes == null || fileBytes.Length == 0) - { - return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的文件数据"); - } + // 将文件的base64字符串转换为字节数组 + byte[] fileBytes = FileService.ConvertBase64ToBytes(request.File) ?? []; - // 1. 验证数据 - var validationResult = ValidateUploadRequest(request, fileBytes); + // 1. 验证数据 - 使用FileService中的验证方法 + var validationResult = await ValidateUploadRequest(request, fileBytes); if (!validationResult.Success) { return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, validationResult.Message); @@ -82,67 +96,12 @@ namespace LMS.service.Service.FileUploadService return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "今日上传文件数量已达上限,请明天再试"); } - QiniuSettings? qiniuSettings = await _optionGlobalDAO.FindAndReturnOption("SYS_QiniuSetting"); - if (qiniuSettings == null || string.IsNullOrEmpty(qiniuSettings.AccessKey) || string.IsNullOrEmpty(qiniuSettings.SecretKey) || string.IsNullOrEmpty(qiniuSettings.BucketName) || string.IsNullOrEmpty(qiniuSettings.Domain)) - { - return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "配置不完整,请检查配置,请联系管理员"); - } + string fileKey = $"diantu/user/{userId}/{DateTime.Now:yyyyMMdd}/{request.FileName}"; - Mac mac = new(qiniuSettings.AccessKey, qiniuSettings.SecretKey); - - - // 4. 生成文件key - var fileKey = GenerateFileKey(userId.Value, request.FileName); - - - // 5. 生成上传凭证 - var putPolicy = new PutPolicy - { - Scope = qiniuSettings.BucketName - }; - if (qiniuSettings.DeleteDay != null) - { - putPolicy.DeleteAfterDays = qiniuSettings.DeleteDay.Value; // 设置过期时间 - } - putPolicy.SetExpires(3600); - var token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); - - // 6. 计算文件哈希 - var hash = ComputeSHA1Hash(fileBytes); - - // 7. 上传到七牛云 - HttpResult uploadResult; - using (var stream = new MemoryStream(fileBytes)) - { - uploadResult = await _uploadManager.UploadStreamAsync(stream, fileKey, token); - } - - // 8. 检查上传结果 - if (uploadResult.Code != 200) - { - _logger.LogError($"文件上传失败, 上传用户ID: {userId}, 错误信息: {uploadResult.Text}"); - return APIResponseModel.CreateErrorResponseModel(ResponseCode.SystemError, $"文件上传失败: {uploadResult.Text}"); - } - - // 9. 构建访问URL - var qiniuUrl = BuildFileUrl(qiniuSettings.Domain, fileKey); - - // 10. 保存到数据库 - var fileUpload = new FileUploads - { - UserId = userId.Value, - FileName = request.FileName, - FileKey = fileKey, - FileSize = fileBytes.Length, - ContentType = request.ContentType ?? "application/octet-stream", - Hash = hash, - QiniuUrl = qiniuUrl, - UploadTime = DateTime.Now, - Status = "active", - CreatedAt = DateTime.Now, - DeleteTime = qiniuSettings.DeleteDay != null ? BeijingTimeExtension.GetBeijingTime().AddDays((double)qiniuSettings.DeleteDay) : DateTime.MaxValue // 默认未删除 - }; + // 4. 上传到七牛云 + FileUploads fileUpload = await _qiniuService.UploadFileToQiNiu(fileBytes, userId.Value, request.FileName, fileKey); + // 5. 修改数据库 _dbContext.FileUploads.Add(fileUpload); await _dbContext.SaveChangesAsync(); @@ -150,9 +109,9 @@ namespace LMS.service.Service.FileUploadService { Success = true, Message = "上传成功", - Url = qiniuUrl, - FileKey = fileKey, - Hash = hash, + Url = fileUpload.QiniuUrl, + FileKey = fileUpload.FileKey, + Hash = fileUpload.Hash, FileId = fileUpload.Id, FileSize = fileBytes.Length }); @@ -165,27 +124,21 @@ namespace LMS.service.Service.FileUploadService } } - /// - /// 获取对应机器码上传的图片信息 - /// - /// - /// - /// - /// public async Task>>> GetFilesByMachineId(string machineId, int page, int pageSize) { try { - // 1. 判断机器码 是不是 存在 并且获取对应的ID + // 1. 验证机器码并获取关联的用户ID long? userId = await GetUserIdFromMachine(machineId); if (userId == null) { return APIResponseModel>.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户"); } + // 2. 获取用户的文件列表 var filesList = await GetUserFilesAsync(userId.Value, page, pageSize); - // 4. 构建返回结果 + // 3. 构建返回结果,将数据库实体转换为响应模型 var fileList = filesList.fileList.Select(f => new FileMachineRequestReturn { MachineId = machineId, @@ -198,34 +151,37 @@ namespace LMS.service.Service.FileUploadService CreatedAt = f.CreatedAt, DeleteTime = f.DeleteTime }).ToList(); + var response = new CollectionResponse { Current = page, Total = filesList.totlaCount, Collection = fileList }; + return APIResponseModel>.CreateSuccessResponseModel(response); } catch (Exception ex) { - // 这里可以记录日志或处理异常 _logger.LogError(ex, $"获取文件列表失败, 机器码: {machineId}"); return APIResponseModel>.CreateErrorResponseModel(ResponseCode.SystemError, "获取文件列表失败"); } } /// - /// 获取指定的用户的文件列表,如果是超级管理员则获取所有用户的文件列表 + /// 获取指定用户的文件列表 + /// 超级管理员可以查看所有用户的文件,普通用户只能查看自己的文件 /// - /// - /// - /// - /// + /// 请求用户的ID + /// 要查询的目标用户ID + /// 页码,从1开始 + /// 每页显示的文件数量 + /// 分页的用户文件列表响应 public async Task>>> GetFilesByUser(long requestUserId, long userId, int page, int pageSize) { try { - // 1. 判断用户是不是超级管理员,不是超级管理员只能获取自己的 + // 1. 检查用户权限:判断是否为超级管理员 bool isSuperAdmin = await _userBasicDao.CheckUserIsSuperAdmin(requestUserId); var fileMessage = (0, new List()); @@ -236,14 +192,16 @@ namespace LMS.service.Service.FileUploadService } else { + // 普通用户权限检查:只能查看自己的文件 if (requestUserId != userId) { return APIResponseModel>.CreateErrorResponseModel(ResponseCode.NotPermissionAction); } - // 普通用户只能获取自己的文件 + // 获取用户自己的文件列表 fileMessage = await GetUserFilesAsync(requestUserId, page, pageSize); } - // 2. 构建返回结果 + + // 2. 构建返回结果,将数据库实体转换为响应模型 var fileList = fileMessage.Item2.Select(f => new FileUserRequestReturn { Id = f.Id, @@ -257,12 +215,14 @@ namespace LMS.service.Service.FileUploadService CreatedAt = f.CreatedAt, DeleteTime = f.DeleteTime }).ToList(); + var response = new CollectionResponse { Current = page, Total = fileMessage.Item1, Collection = fileList }; + return APIResponseModel>.CreateSuccessResponseModel(response); } catch (Exception ex) @@ -274,49 +234,49 @@ namespace LMS.service.Service.FileUploadService public async Task<(int totlaCount, List fileList)> GetUserFilesAsync(long userId, int page = 1, int pageSize = 10) { - // 获取用户的文件总数 + // 获取用户的活跃文件总数 int totalCount = await _dbContext.FileUploads .Where(f => f.UserId == userId && f.Status == "active") .CountAsync(); - // 获取用户的文件列表 + + // 如果没有文件,直接返回空结果 if (totalCount == 0) { return (0, new List()); } + // 分页查询用户文件列表,按上传时间倒序排列 var fileList = _dbContext.FileUploads .Where(f => f.UserId == userId && f.Status == "active") .OrderByDescending(f => f.UploadTime) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); + return (totalCount, await fileList); } public async Task<(int totleCount, List fileList)> GetAllUserFilesAsync(int page = 1, int pageSize = 10) { - // 获取所有用户的文件总数 + // 获取所有活跃文件的总数 int totalCount = await _dbContext.FileUploads .Where(f => f.Status == "active") .CountAsync(); + if (totalCount == 0) { return (0, new List()); } - // 获取所有用户的文件列表 + + // 分页查询所有用户的文件列表,按上传时间倒序排列 List fileUploads = await _dbContext.FileUploads .Where(f => f.Status == "active") .OrderByDescending(f => f.UploadTime) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); - return (totalCount, fileUploads); - } - public async Task GetUserFilesCountAsync(long userId) - { - return await _dbContext.FileUploads - .CountAsync(f => f.UserId == userId && f.Status == "active"); + return (totalCount, fileUploads); } private async Task GetUserUploadToday(long userId) @@ -325,44 +285,17 @@ namespace LMS.service.Service.FileUploadService .CountAsync(f => f.UserId == userId && f.CreatedAt.Date == BeijingTimeExtension.GetBeijingTime().Date); } - private static byte[]? ConvertBase64ToBytes(string base64String) - { - if (string.IsNullOrEmpty(base64String)) - { - return null; - } - // 检查是否以 "data:" 开头并包含 base64 编码 - if (base64String.StartsWith("data:")) - { - // 提取 base64 编码部分 - var commaIndex = base64String.IndexOf(','); - if (commaIndex >= 0) - { - base64String = base64String.Substring(commaIndex + 1); - } - } - // 判断会不会出现异常 - try - { - // 尝试将 base64 字符串转换为字节数组 - return Convert.FromBase64String(base64String); - } - catch (FormatException) - { - // 如果格式不正确,返回 null - return null; - } - } - private async Task GetUserIdFromMachine(string machineId) { if (string.IsNullOrWhiteSpace(machineId)) { return null; } + + // 查询有效的机器码(未过期或无过期时间) Machine? machine = await _dbContext.Machine .Where(m => m.MachineId == machineId && (m.DeactivationTime > BeijingTimeExtension.GetBeijingTime() || m.DeactivationTime == null)) - .FirstOrDefaultAsync(); // 改回原来的 FirstOrDefaultAsync + .FirstOrDefaultAsync(); if (machine != null) { @@ -374,19 +307,102 @@ namespace LMS.service.Service.FileUploadService return null; } } - - // 私有方法 - private UploadResult ValidateUploadRequest(ByteUploadRequest request, byte[] fileBytes) + public async Task>> UrlUpload(string machineId, UrlUploadRequest request) { - if (fileBytes == null || fileBytes.Length == 0) + try { - return new UploadResult + // 1. 获取用户 + long? userId = await GetUserIdFromMachine(machineId); + if (userId == null) { - Success = false, - Message = "文件字节数据不能为空" - }; + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的机器ID或未找到关联用户"); + } + + // 2. 验证URL格式 + if (!Uri.IsWellFormedUriString(request.Url, UriKind.Absolute)) + { + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "无效的URL格式"); + } + + //4.下载网络图片获取字节数组 + byte[] fileBytes; + try + { + double maxFileSize = await _optionGlobalDAO.FindAndReturnOption("SYS_MaxUploadFileSize"); + fileBytes = await _httpService.DownloadFileAsync(request.Url, maxFileSize) ?? []; + if (fileBytes == null || fileBytes.Length == 0) + { + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "无法下载指定的文件或文件为空"); + } + } + catch (InvalidOperationException ex) when (ex.Message.Contains("文件大小") && ex.Message.Contains("超过限制")) + { + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "文件大小超过限制"); + } + catch (TimeoutException) + { + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "下载文件超时,请检查网络连接或URL有效性"); + } + catch (HttpRequestException ex) + { + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, $"下载文件失败: {ex.Message}"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"下载文件时发生未知错误: {request.Url}"); + return APIResponseModel.CreateErrorResponseModel(ResponseCode.ParameterError, "下载文件失败"); + } + + // 7. 生成文件路径 + string fileKey = $"upfile/user/{userId}/{DateTime.Now:yyyyMMdd}/transfer_{request.FileName}"; + + // 8. 上传到七牛云 + FileUploads fileUpload = await _qiniuService.UploadFileToQiNiu(fileBytes, userId.Value, request.FileName, fileKey); + + // 10. 返回成功结果 + return APIResponseModel.CreateSuccessResponseModel(new UploadResult + { + Success = true, + Message = "URL转存成功", + Url = fileUpload.QiniuUrl, + FileKey = fileUpload.FileKey, + Hash = fileUpload.Hash, + FileId = fileUpload.Id, + FileSize = fileBytes.Length + }); + } + catch (Exception ex) + { + _logger.LogError(ex, $"URL转存文件失败, 上传机器码: {machineId},URL: {request.Url}"); + return APIResponseModel.CreateErrorResponseModel(ResponseCode.SystemError, $"转存失败: {ex.Message}"); + } + } + + #endregion + + + #region 私有辅助方法 + + /// + /// 验证上传请求的文件是否符合要求 + /// + /// 上传请求参数 + /// 文件字节数组 + /// 允许的文件类型列表 + /// 最大文件大小(字节) + /// 验证结果 + public async Task ValidateUploadRequest(ByteUploadRequest request, byte[] fileBytes) + { + + double maxFileSize = await _optionGlobalDAO.FindAndReturnOption("SYS_MaxUploadFileSize"); + // 1. 检查文件大小 + var sizeCheckResult = await FileService.CheckFileSize(fileBytes, maxFileSize); + if (!sizeCheckResult.Success) + { + return sizeCheckResult; } + // 2. 校验文件名不能为空 if (string.IsNullOrEmpty(request.FileName)) { return new UploadResult @@ -396,18 +412,11 @@ namespace LMS.service.Service.FileUploadService }; } - if (fileBytes.Length > _uploadSettings.MaxFileSize) - { - return new UploadResult - { - Success = false, - Message = $"文件大小不能超过 {_uploadSettings.MaxFileSize / (1024 * 1024)}MB" - }; - } - - if (_uploadSettings.AllowedContentTypes.Count != 0 && + List allowedContentTypes = await _optionGlobalDAO.FindAndReturnOption>("SYS.AllowedContentTypes") ?? []; + // 3. 检查文件类型(如果有配置允许的类型) + if (allowedContentTypes.Count > 0 && !string.IsNullOrEmpty(request.ContentType) && - !_uploadSettings.AllowedContentTypes.Contains(request.ContentType.ToLower())) + !allowedContentTypes.Contains(request.ContentType.ToLower())) { return new UploadResult { @@ -416,9 +425,8 @@ namespace LMS.service.Service.FileUploadService }; } - // 检查实际的文件类型是否在允许的列表中 - // 只检查是否为图片,不是图片就拒绝 - if (!ImageTypeDetector.IsValidImage(fileBytes)) + // 4. 检查是否为有效的图片文件 + if (!FileService.IsValidImageFile(fileBytes)) { return new UploadResult { @@ -430,23 +438,8 @@ namespace LMS.service.Service.FileUploadService return new UploadResult { Success = true }; } - private static string GenerateFileKey(long userId, string fileName) - { - var date = DateTime.Now.ToString("yyyyMMdd"); - var guid = Guid.NewGuid().ToString("N"); - var extension = Path.GetExtension(fileName); - return $"diantu/user/{userId}/{date}/{guid}{extension}"; - } + #endregion - private static string ComputeSHA1Hash(byte[] data) - { - var hash = SHA1.HashData(data); - return Convert.ToHexString(hash).ToLower(); - } - - private static string BuildFileUrl(string domain, string fileKey) - { - return $"{domain}/{fileKey}"; - } } + } diff --git a/LMS.service/Service/PromptService/PromptService.cs b/LMS.service/Service/PromptService/PromptService.cs index 7448047..54a1bf4 100644 --- a/LMS.service/Service/PromptService/PromptService.cs +++ b/LMS.service/Service/PromptService/PromptService.cs @@ -361,7 +361,8 @@ public class PromptService(UserBasicDao userBasicDao, ApplicationDbContext conte Id = prompt.Id, Name = prompt.Name, PromptTypeId = prompt.PromptTypeId, - Remark = prompt.Remark + Remark = prompt.Remark, + Description = prompt.Description }; promptNames.Add(promptName); }