Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
155 KiB
JSON
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "bigbugcc/admin.net", "files": {"Admin.NET/Admin.NET.Core/Service/LangText/SysLangTextService.cs": {"debae158e974f61f5ef4e44893b1166227da46ac": {"t": 1785745801.6113398, "n": 16003, "h": "34454f4850981493", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY\n var deepSeekOptions = App.GetConfig<DeepSeekOptions>(\"DeepSeekSettings\", true);\n if (deepSeekOptions == null)\n {\n throw new InvalidOperationException(\"DeepSeek.json文件 未定义\");\n }\n if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {deepSeekOptions.ApiKey}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"sourceLang\"></param>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string sourceLang, string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({sourceLang}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & |\n• UI占位符:{{0}} %s [ ]\n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n\n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}"}, "d74960d70ed14fa86d17bea68abc200f92106d81": {"t": 1785756655.485599, "n": 16003, "h": "34454f4850981493", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY\n var deepSeekOptions = App.GetConfig<DeepSeekOptions>(\"DeepSeekSettings\", true);\n if (deepSeekOptions == null)\n {\n throw new InvalidOperationException(\"DeepSeek.json文件 未定义\");\n }\n if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {deepSeekOptions.ApiKey}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"sourceLang\"></param>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string sourceLang, string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({sourceLang}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & |\n• UI占位符:{{0}} %s [ ]\n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n\n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}"}, "18235259f2929aed591fd183dc9e00ffe3aaa527": {"t": 1785756656.8245335, "n": 15961, "h": "5c56a7a57b4fb5eb", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY\n var deepSeekOptions = App.GetConfig<DeepSeekOptions>(\"DeepSeekSettings\", true);\n if (deepSeekOptions == null)\n {\n throw new InvalidOperationException(\"DeepSeek.json文件 未定义\");\n }\n if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {deepSeekOptions.ApiKey}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string sourceLang, string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({sourceLang}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & |\n• UI占位符:{{0}} %s [ ]\n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n\n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}"}, "5272b0b84b9a41f0a80f7b441f2321aff76590a2": {"t": 1785756658.1866922, "n": 16029, "h": "1d8e4c86082b3b8e", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\n\nusing AngleSharp.Dom;\nusing Microsoft.AspNetCore.Components.Forms;\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY\n var deepSeekOptions = App.GetConfig<DeepSeekOptions>(\"DeepSeekSettings\", true);\n if (deepSeekOptions == null)\n {\n throw new InvalidOperationException(\"DeepSeek.json文件 未定义\");\n }\n if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {deepSeekOptions.ApiKey}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string sourceLang, string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({sourceLang}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & | \n• UI占位符:{{0}} %s [ ] \n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n \n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}\n"}, "ce17de21fddf97be9b8a7424a0ff8bd5e0c6f145": {"t": 1785756659.5075068, "n": 15867, "h": "ed67c116b5b34c4f", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\n\nusing AngleSharp.Dom;\nusing Microsoft.AspNetCore.Components.Forms;\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName,entity.FieldName,entity.EntityId,entity.LangCode,entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n \n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n private const string SOURCE_LANG = \"zh-cn\";\n private const string API_URL = \"https://api.deepseek.com/v1/chat/completions\";\n private const string DEEPSEEK_API_KEY = \"你的 API KEY\";\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n if (string.IsNullOrEmpty(DEEPSEEK_API_KEY))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {DEEPSEEK_API_KEY}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(API_URL, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({SOURCE_LANG}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & | \n• UI占位符:{{0}} %s [ ] \n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n \n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}\n"}, "dcdf3f38b7ac406990d252b248d2d913349029ea": {"t": 1785756660.780569, "n": 11897, "h": "c1083e39298a7769", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\n\nusing AngleSharp.Dom;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName,entity.FieldName,entity.EntityId,entity.LangCode,entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n \n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n}\n"}, "e7f0853f71f8ce6694749690f342e78b388e2b27": {"t": 1785756662.225752, "n": 10846, "h": "85279e9cc30f64a7", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n\n public SysLangTextService(SqlSugarRepository<SysLangText> sysLangTextRep, ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n}\n"}, "6b6ce64e0f86d1b81b4714676ba2f56fb1b5c283": {"t": 1786067471.2029727, "n": 16003, "h": "34454f4850981493", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY\n var deepSeekOptions = App.GetConfig<DeepSeekOptions>(\"DeepSeekSettings\", true);\n if (deepSeekOptions == null)\n {\n throw new InvalidOperationException(\"DeepSeek.json文件 未定义\");\n }\n if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {deepSeekOptions.ApiKey}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"sourceLang\"></param>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string sourceLang, string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({sourceLang}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & |\n• UI占位符:{{0}} %s [ ]\n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n\n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}"}, "e5361b7c412eb10c61e062b51e907095338dc2b0": {"t": 1786068322.2034166, "n": 16003, "h": "34454f4850981493", "c": "// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。\n//\n// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。\n//\n// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!\n\nusing Newtonsoft.Json;\n\nnamespace Admin.NET.Core.Service;\n\n/// <summary>\n/// 翻译服务 🧩\n/// </summary>\n[ApiDescriptionSettings(Order = 100, Description = \"翻译服务\")]\npublic partial class SysLangTextService : IDynamicApiController, ITransient\n{\n private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;\n private readonly ISqlSugarClient _sqlSugarClient;\n private readonly SysLangTextCacheService _sysLangTextCacheService;\n\n public SysLangTextService(\n SqlSugarRepository<SysLangText> sysLangTextRep,\n SysLangTextCacheService sysLangTextCacheService,\n ISqlSugarClient sqlSugarClient)\n {\n _sysLangTextRep = sysLangTextRep;\n _sqlSugarClient = sqlSugarClient;\n _sysLangTextCacheService = sysLangTextCacheService;\n }\n\n /// <summary>\n /// 分页查询翻译表 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"分页查询翻译表\")]\n [ApiDescriptionSettings(Name = \"Page\"), HttpPost]\n public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)\n {\n input.Keyword = input.Keyword?.Trim();\n var query = _sysLangTextRep.AsQueryable()\n .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))\n .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))\n .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))\n .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)\n .Select<SysLangTextOutput>();\n return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);\n }\n\n [DisplayName(\"获取翻译表\")]\n [ApiDescriptionSettings(Name = \"List\"), HttpPost]\n public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)\n {\n var query = _sysLangTextRep.AsQueryable()\n .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)\n .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())\n .Select<SysLangTextOutput>();\n return await query.ToListAsync();\n }\n\n /// <summary>\n /// 获取翻译表详情 ℹ️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"获取翻译表详情\")]\n [ApiDescriptionSettings(Name = \"Detail\"), HttpGet]\n public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)\n {\n return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);\n }\n\n /// <summary>\n /// 增加翻译表 ➕\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"增加翻译表\")]\n [ApiDescriptionSettings(Name = \"Add\"), HttpPost]\n public async Task<long> Add(AddSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;\n }\n\n /// <summary>\n /// 更新翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"更新翻译表\")]\n [ApiDescriptionSettings(Name = \"Update\"), HttpPost]\n public async Task Update(UpdateSysLangTextInput input)\n {\n var entity = input.Adapt<SysLangText>();\n await _sysLangTextRep.AsUpdateable(entity)\n .ExecuteCommandAsync();\n _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);\n }\n\n /// <summary>\n /// 删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"删除翻译表\")]\n [ApiDescriptionSettings(Name = \"Delete\"), HttpPost]\n public async Task Delete(DeleteSysLangTextInput input)\n {\n var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);\n\n await _sysLangTextRep.DeleteAsync(entity); //真删除\n _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);\n }\n\n /// <summary>\n /// 批量删除翻译表 ❌\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量删除翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchDelete\"), HttpPost]\n public async Task BatchDelete([Required(ErrorMessage = \"主键列表不能为空\")] List<DeleteSysLangTextInput> input)\n {\n var exp = Expressionable.Create<SysLangText>();\n foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);\n var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();\n\n await _sysLangTextRep.DeleteAsync(list); //真删除\n foreach (var item in list)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n }\n\n private static readonly object _sysLangTextBatchSaveLock = new object();\n\n /// <summary>\n /// 批量保存翻译表 ✏️\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"批量保存翻译表\")]\n [ApiDescriptionSettings(Name = \"BatchSave\"), HttpPost]\n public void BatchSave([Required(ErrorMessage = \"列表不能为空\")] List<ImportSysLangTextInput> input)\n {\n lock (_sysLangTextBatchSaveLock)\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = input.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n if (storageable.ErrorList.Any())\n {\n throw Oops.Oh($\"处理过程中出现以下错误:{string.Join(\";\", storageable.ErrorList.Distinct())}\");\n }\n }\n }\n\n /// <summary>\n /// 导出翻译表记录 🔖\n /// </summary>\n /// <param name=\"input\"></param>\n /// <returns></returns>\n [DisplayName(\"导出翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Export\"), HttpPost, NonUnify]\n public async Task<IActionResult> Export(PageSysLangTextInput input)\n {\n var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();\n if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();\n return ExcelHelper.ExportTemplate(list, \"翻译表导出记录\");\n }\n\n /// <summary>\n /// 下载翻译表数据导入模板 ⬇️\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"下载翻译表数据导入模板\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpGet, NonUnify]\n public IActionResult DownloadTemplate()\n {\n return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), \"翻译表导入模板\");\n }\n\n private static readonly object _sysLangTextImportLock = new object();\n\n /// <summary>\n /// 导入翻译表记录 💾\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"导入翻译表记录\")]\n [ApiDescriptionSettings(Name = \"Import\"), HttpPost, NonUnify, UnitOfWork]\n public IActionResult ImportData([Required] IFormFile file)\n {\n lock (_sysLangTextImportLock)\n {\n var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>\n {\n _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>\n {\n // 校验并过滤必填基本类型为null的字段\n var rows = pageItems.Where(x =>\n {\n if (!string.IsNullOrWhiteSpace(x.Error)) return false;\n if (x.EntityId == null)\n {\n x.Error = \"所属实体ID不能为空\";\n return false;\n }\n return true;\n }).Adapt<List<SysLangText>>();\n\n var storageable = _sysLangTextRep.Context.Storageable(rows)\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), \"所属实体名不能为空\")\n .SplitError(it => it.Item.EntityName?.Length > 255, \"所属实体名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), \"字段名不能为空\")\n .SplitError(it => it.Item.FieldName?.Length > 255, \"字段名长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), \"语言代码不能为空\")\n .SplitError(it => it.Item.LangCode?.Length > 255, \"语言代码长度不能超过255个字符\")\n .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), \"翻译内容不能为空\")\n .SplitError(it => it.Item.Content?.Length > 255, \"翻译内容长度不能超过255个字符\")\n .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })\n .SplitInsert(it => it.NotAny())\n .SplitUpdate(it => it.Any())\n .ToStorage();\n\n storageable.AsInsertable.ExecuteCommand();// 不存在插入\n storageable.AsUpdateable.UpdateColumns(it => new\n {\n it.EntityName,\n it.EntityId,\n it.FieldName,\n it.LangCode,\n it.Content,\n }).ExecuteCommand();// 存在更新\n\n foreach (var item in rows)\n {\n _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);\n }\n // 标记错误信息\n markerErrorAction.Invoke(storageable, pageItems, rows);\n });\n });\n\n return stream;\n }\n }\n\n /// <summary>\n /// DEEPSEEK 翻译接口\n /// </summary>\n /// <returns></returns>\n [DisplayName(\"DEEPSEEK 翻译接口\")]\n [ApiDescriptionSettings(Name = \"AiTranslateText\"), HttpPost]\n public async Task<string> AiTranslateText(AiTranslateTextInput input)\n {\n // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY\n var deepSeekOptions = App.GetConfig<DeepSeekOptions>(\"DeepSeekSettings\", true);\n if (deepSeekOptions == null)\n {\n throw new InvalidOperationException(\"DeepSeek.json文件 未定义\");\n }\n if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))\n {\n throw new InvalidOperationException(\"环境变量 DEEPSEEK_API_KEY 未定义\");\n }\n\n using (HttpClient client = new HttpClient())\n {\n // 构建请求头\n client.DefaultRequestHeaders.Add(\"Authorization\", $\"Bearer {deepSeekOptions.ApiKey}\");\n\n // 构建系统提示词\n string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);\n\n // 构建请求体\n var requestBody = new\n {\n model = \"deepseek-chat\",\n messages = new[]\n {\n new { role = \"system\", content = systemPrompt },\n new { role = \"user\", content = input.OriginalText }\n },\n temperature = 0.3,\n max_tokens = 2000\n };\n\n // 使用 Newtonsoft.Json 序列化\n var json = JsonConvert.SerializeObject(requestBody);\n var content = new StringContent(json, Encoding.UTF8, \"application/json\");\n\n // 发送请求\n HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);\n\n // 处理响应\n string responseBody = await response.Content.ReadAsStringAsync();\n\n if (!response.IsSuccessStatusCode)\n {\n // 使用 Newtonsoft.Json 反序列化错误响应\n var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);\n string errorMsg = errorResponse?.error?.message ?? $\"HTTP {response.StatusCode}: {response.ReasonPhrase}\";\n throw new HttpRequestException($\"翻译API返回错误:{errorMsg}\");\n }\n\n // 解析有效响应\n var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);\n\n if (result?.choices == null || result.choices.Length == 0 ||\n result.choices[0]?.message?.content == null)\n {\n throw new InvalidOperationException(\"API返回无效的翻译结果\");\n }\n\n return result.choices[0].message.content.Trim();\n }\n }\n\n // JSON 响应模型\n private class TranslationResponse\n {\n public Choice[] choices { get; set; }\n }\n\n private class Choice\n {\n public Message message { get; set; }\n }\n\n private class Message\n {\n public string content { get; set; }\n }\n\n private class ErrorResponse\n {\n public ErrorInfo error { get; set; }\n }\n\n private class ErrorInfo\n {\n public string message { get; set; }\n }\n\n /// <summary>\n /// 生成提示词\n /// </summary>\n /// <param name=\"sourceLang\"></param>\n /// <param name=\"targetLang\"></param>\n /// <returns></returns>\n private static string BuildSystemPrompt(string sourceLang, string targetLang)\n {\n return $@\"作为企业软件系统专业翻译,严格遵守以下铁律:\n\n■ 核心原则\n1. 严格逐符号翻译({sourceLang}→{targetLang})\n2. 禁止添加/删除/改写任何内容\n3. 保持批量翻译的编号格式\n\n■ 符号保留规则\n! 所有符号必须原样保留:\n• 编程符号:\\${{ }} <% %> @ # & |\n• UI占位符:{{0}} %s [ ]\n• 货币单位:¥100.00 kg cm²\n• 中文符号:【 】 《 》 :\n\n■ 中文符号位置规范\n# 三级处理机制:\n1. 成对符号必须保持完整结构:\n ✓ 正确:【Warning】Text\n ✗ 禁止:Warning【 】Text\n\n2. 独立符号位置:\n • 优先句尾 → Text】?\n • 次选句首 → 】Text?\n • 禁止句中 → Text】Text?\n\n3. 跨字符串符号处理:\n • 前段含【时 → 保留在段尾(\"\"Synchronize【\"\")\n • 后段含】时 → 保留在段首(\"\"】authorization data?\"\")\n • 符号后接字母时添加空格:】 Authorization\n\n■ 语法规范\n• 外文 → 被动语态(\"\"Item was created\"\")\n• 中文 → 主动语态(\"\"已创建项目\"\")\n• 禁止推测上下文(只翻译当前字符串内容)\n\n■ 错误预防(绝对禁止)\n✗ 将中文符号改为西式符号(】→])\n✗ 移动非中文符号位置\n✗ 添加原文不存在的内容\n✗ 合并/拆分原始字符串\n\n■ 批量处理\n▸ 严格保持原始JSON结构\n▸ 语言键名精确匹配(zh-cn/en/it等)\";\n }\n}"}}, "Admin.NET/Admin.NET.Application/Configuration/DeepSeek.example": {"6b6ce64e0f86d1b81b4714676ba2f56fb1b5c283": {"t": 1786067734.4703145, "n": 238, "h": "435a98eff1417e2c", "c": "{\n \"$schema\": \"https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json\",\n\n \"DeepSeekSettings\": {\n \"SourceLang\": \"zh-cn\",\n \"ApiUrl\": \"https://api.deepseek.com/v1/chat/completions\",\n \"ApiKey\": \"你的 API KEY\"\n }\n}"}}}}