9 Commity 3948b50323 ... fd960b174e

Autor SHA1 Wiadomość Data
  Lyyyi fd960b174e Merge branch 'develop' of http://132.232.92.186:3000/yuanrf/OA2023 into develop 1 tydzień temu
  Lyyyi d089f690e8 hotmail 邮件代码调试 1 tydzień temu
  Lyyyi 60761b978c Merge branch 'develop' of http://132.232.92.186:3000/yuanrf/OA2023 into develop 2 tygodni temu
  Lyyyi 97bfa851b8 hotmail 代码编写 2 tygodni temu
  Lyyyi 5344a30e5f 20260320165338 2 tygodni temu
  Lyyyi 762187b29f 商邀资料AI 设置复选框选中 批量、AI查询出的数据添加值商邀资料库 2 tygodni temu
  Lyyyi 7704a7ca02 商邀资料AI 设置复选框单条选中 2 tygodni temu
  Lyyyi 44c5980061 20260320 更改 2 tygodni temu
  Lyyyi 88b82cfbb6 111 2 tygodni temu

+ 34 - 2
OASystem/OASystem.Api/Controllers/AITestController.cs

@@ -1,9 +1,10 @@
 using Microsoft.AspNetCore.Mvc;
-using System.IO;
 using OASystem.API.OAMethodLib.DoubaoAPI;
+using OASystem.API.OAMethodLib.HotmailEmail;
 using OASystem.API.OAMethodLib.HunYuanAPI;
 using OASystem.API.OAMethodLib.QiYeWeChatAPI;
 using OASystem.Domain.ViewModels.QiYeWeChat;
+using System.IO;
 using TencentCloud.Hunyuan.V20230901.Models;
 
 namespace OASystem.API.Controllers
@@ -18,13 +19,15 @@ namespace OASystem.API.Controllers
         private readonly IDoubaoService _doubaoService;
         private readonly ILogger<AITestController> _logger;
         private readonly IQiYeWeChatApiService _qiYeWeChatApiService;
+        private readonly IHotmailEmailService _hotmailEmailService;
 
-        public AITestController(IHunyuanService hunyuanService, IDoubaoService doubaoService, ILogger<AITestController> logger, IQiYeWeChatApiService qiYeWeChatApiService)
+        public AITestController(IHunyuanService hunyuanService, IDoubaoService doubaoService, ILogger<AITestController> logger, IQiYeWeChatApiService qiYeWeChatApiService, IHotmailEmailService hotmailEmailService)
         {
             _hunyuanService = hunyuanService;
             _doubaoService = doubaoService;
             _logger = logger;
             _qiYeWeChatApiService = qiYeWeChatApiService;
+            _hotmailEmailService = hotmailEmailService;
         }
 
         #region 企业微信发送邮件测试
@@ -320,5 +323,34 @@ namespace OASystem.API.Controllers
         }
         #endregion
 
+        #region hotmail 测试
+        [HttpPost("send-notification")]
+        public async Task<IActionResult> SendNotification([FromBody] EmailRequest request)
+        {
+            if (string.IsNullOrEmpty(request.Email))
+                return BadRequest("邮箱地址不能为空");
+
+            // 调用服务
+            bool isSent = await _hotmailEmailService.SendEmailAsync(
+                request.Email,
+                "OASystem 业务提醒",
+                $"<p>您有一条新的待办事项:<b>{request.Content}</b></p>"
+            );
+
+            if (isSent)
+                return Ok(new { code = 200, msg = "发送成功" });
+
+            return StatusCode(500, "邮件发送失败,请检查服务器配置或应用密码");
+        }
+
+        // 定义请求实体
+        public class EmailRequest
+        {
+            public string Email { get; set; } = string.Empty;
+            public string Content { get; set; } = string.Empty;
+        }
+
+        #endregion
+
     }
 }

+ 724 - 4
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -2,6 +2,8 @@
 using Aspose.Words;
 using Aspose.Words.Tables;
 using EyeSoft.Extensions;
+using Microsoft.AspNetCore.Http.Features;
+using Newtonsoft.Json.Serialization;
 using NodaTime;
 using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
@@ -2320,17 +2322,30 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 .Where(x => x.IsDel == 0 && x.InvName == name)
                 .FirstAsync();
 
+            var groupInfo = new Grp_DelegationInfo();
+
             if (info == null)
             {
+                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.TeamName.Equals(name)).FirstAsync();
+
+                var entry = new EntryInfo() {
+                    OriginUnit = groupInfo?.ClientUnit ?? "",
+                    TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? ""),
+                };
+
                 return Ok(JsonView(true, $"暂无数据",new {
                     Id = 0,
-                    GroupId= 0,
-                    InvName = "",
+                    GroupId= groupInfo?.Id ?? 0,
+                    InvName = name,
                     AiCrawledDetails = new List<InvitationAIInfo>(),
+                    Entry = entry
                 }));
             }
 
-            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.Id == info.GroupId).FirstAsync();
+            if (info.GroupId != 0)
+            {
+                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.Id == info.GroupId).FirstAsync();
+            }
             // 设置国家、单位默认值
             if (string.IsNullOrEmpty(info.EntryInfo.OriginUnit))
             {
@@ -2383,6 +2398,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var localInvDatas = new List<InvitationAIInfo>(); // 本地数据源(商邀资料)
             var aiTasks = new List<CountryAIPormptInfo>();    // 记录:国家 -> 需要补齐的数量
+
             #region 本地数据源(商邀资料)
 
             var datas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
@@ -2606,6 +2622,235 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             }));
         }
 
+        /// <summary>
+        /// 商邀资料AI 混元AI查询资料(SSE流式推送)
+        /// </summary>
+        [HttpPost]
+        public async Task InvitationAISearchStreamProgress([FromBody] InvitationAISearchDto dto)
+        {
+            // 强制关闭响应缓冲
+            var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>();
+            if (syncIOFeature != null) syncIOFeature.AllowSynchronousIO = true;
+
+            var response = HttpContext.Response;
+
+            // --- SSE 核心协议头配置 ---
+            response.Headers.Add("Content-Type", "text/event-stream");
+            response.Headers.Add("Cache-Control", "no-cache");
+            response.Headers.Add("Connection", "keep-alive");
+            response.Headers.Add("X-Accel-Buffering", "no");
+
+            // 定义流式推送匿名函数
+            async Task SendStep(int progress, string message, object data = null)
+            {
+                var settings = new JsonSerializerSettings
+                {
+                    ContractResolver = new CamelCasePropertyNamesContractResolver() // 强制小驼峰转换
+                };
+
+                var payload = JsonConvert.SerializeObject(new { progress, message, data }, settings);
+                var bytes = Encoding.UTF8.GetBytes($"data: {payload}\n\n");
+
+                await response.Body.WriteAsync(bytes, 0, bytes.Length);
+                await response.Body.FlushAsync(); // 立即清空缓冲区发送
+            }
+
+            try
+            {
+                await SendStep(5, "正在加载配置参数...");
+
+                #region 1. 参数与权限验证
+                var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
+                if (invAiInfo?.EntryInfo == null)
+                {
+                    await SendStep(-1, "未找到有效的关键字配置信息。");
+                    return;
+                }
+
+                var entryInfo = invAiInfo.EntryInfo;
+                var operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId)
+                    .Select(x => x.CnName).FirstAsync() ?? "-";
+                #endregion
+
+                await SendStep(20, "正在翻阅本地典籍,执行高性能并行解密...");
+
+                #region 2. 本地数据并行解密 (Performance Boost)
+                var rawLocalDatas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
+                    .Where(x => x.IsDel == 0)
+                    .ToListAsync();
+
+                // 使用 PLINQ 并行解密,极大提升解密密集型任务速度
+                var allDecrypted = rawLocalDatas.AsParallel().Select(item => new InvitationAIInfo
+                {
+                    Guid = Guid.NewGuid().ToString("N"),
+                    Source = 0,
+                    Region = AesEncryptionHelper.Decrypt(item.Country),
+                    NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
+                    Address = AesEncryptionHelper.Decrypt(item.Address),
+                    Scope = AesEncryptionHelper.Decrypt(item.Field),
+                    Contact = AesEncryptionHelper.Decrypt(item.Contact),
+                    Phone = AesEncryptionHelper.Decrypt(item.Tel),
+                    Email = AesEncryptionHelper.Decrypt(item.Email),
+                    OperatedAt = DateTime.Now,
+                    Operator = operatorName,
+                }).ToList();
+
+                // 筛选目标国家并计算 AI 缺口
+                var localInvDatas = new List<InvitationAIInfo>();
+                var aiTasks = new List<CountryAIPormptInfo>();
+                int targetPerCountry = entryInfo.NeedCount;
+
+                foreach (var countryName in entryInfo.TargetCountry)
+                {
+                    var countryMatched = allDecrypted.Where(x => x.Region == countryName).Take(targetPerCountry).ToList();
+                    localInvDatas.AddRange(countryMatched);
+
+                    int gap = targetPerCountry - countryMatched.Count;
+                    if (gap > 0) aiTasks.Add(new() { Country = countryName, Count = gap });
+                }
+                #endregion
+
+                #region 3. 混元 AI 远程炼金
+                var hunyuanAIInvDatas = new List<InvitationAIInfo>();
+                if (aiTasks.Any())
+                {
+                    await SendStep(60, $"Hunyuan AI 正在跨境深度检索 {aiTasks.Count} 个国家的邀请单位信息...");
+
+                    string question = BuildHunyuanPrompt(aiTasks, entryInfo); // 抽离 Prompt 构造
+                    string aiRawResponse = string.Empty;
+
+                    try
+                    {
+                        aiRawResponse = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(question);
+
+                        // 清理 Markdown 代码块包裹
+                        string cleanJson = aiRawResponse.Trim();
+                        if (cleanJson.StartsWith("```json")) cleanJson = cleanJson[7..^3].Trim();
+                        else if (cleanJson.StartsWith("```")) cleanJson = cleanJson[3..^3].Trim();
+
+                        var aiParsed = JsonConvert.DeserializeObject<List<InvitationAIInfo>>(cleanJson);
+                        if (aiParsed != null)
+                        {
+                            hunyuanAIInvDatas = aiParsed.Select(x => {
+                                x.Guid = Guid.NewGuid().ToString("N");
+                                x.Source = 1; // AI 来源
+                                x.Operator = operatorName;
+                                x.OperatedAt = DateTime.Now;
+                                return x;
+                            }).ToList();
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Hunyuan AI 接口调用或解析失败");
+                        await SendStep(60, "警告:Hunyuan AI 检索部分失败,将仅展示本地数据。");
+                    }
+                }
+                #endregion
+
+                await SendStep(90, "执行数据合并与持久化...");
+
+                #region 4. 数据合并与入库
+                var finalResult = localInvDatas.Concat(hunyuanAIInvDatas).ToList();
+                invAiInfo.AiCrawledDetails = finalResult;
+
+                var updateSuccess = await _sqlSugar.Updateable(invAiInfo).ExecuteCommandAsync() > 0;
+                if (!updateSuccess)
+                {
+                    await SendStep(-1, "数据库更新异常。");
+                    return;
+                }
+                #endregion
+
+                // 5. 最终推送:带上全量结果
+                await SendStep(100, "操作成功!资料已全部就绪。", new
+                {
+                    invAiInfo.Id,
+                    invAiInfo.InvName,
+                    AiCrawledDetails = finalResult.OrderByDescending(x => x.OperatedAt).ToList(),
+                    Entry = invAiInfo.EntryInfo
+                });
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "SSE 发生不可预知异常");
+                await SendStep(-1, $"SSE Error:{ex.Message}");
+            }
+            finally
+            {
+                // 确保最后一次冲刷缓冲区
+                await HttpContext.Response.Body.FlushAsync();
+
+                // 此时方法结束,ASP.NET Core 会自动处理连接关闭
+                _logger.LogInformation("SSE 通道已安全关闭");
+
+                // 直接完成响应流
+                await response.CompleteAsync();
+
+            }
+            return;
+        }
+
+        private string BuildHunyuanPrompt(List<CountryAIPormptInfo> tasks, EntryInfo entryInfo)
+        {
+            // 保持你原有的高质量 Prompt 结构
+            return  @$"# [SYSTEM_CONTEXT]
+- Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
+- Framework: S.P.A.R. (Situation-Problem-Action-Result)
+- Target Architecture: .NET 6 DTO Compatible Interface
+
+# [INPUT_CONFIG]
+- Tasks: {{{JsonConvert.SerializeObject(tasks)}}}
+- OriginUnit: {{{entryInfo.OriginUnit}}} 
+- Objective: {{{entryInfo.Objective}}}
+- OtherConstraints: {{{entryInfo.OtherConstraints}}}
+# [ROLE_DEFINITION]
+你是一位精通全球跨境经贸、海外园区政策及 Tasks 中的国家本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
+
+# [BUSINESS_LOGIC_CoT]
+在生成结果前,请严格执行以下思维链拆解:
+1. **ScopePartitioning**: 遍历 Tasks 中的每个国家。
+1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
+2. **ParityMatching**: 针对每个国家,检索境内业务闭环对等机构。(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
+3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
+4. **DataSynthesis**: 必须严格根据每个国家对应的 Count 生成数据条数。针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
+
+# [CONSTRAINTS_&_STANDARDS]
+- **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
+- **DataIntegrity**: 
+    - 总条数必须等于 Tasks 中所有 Count 的总和。
+    - 优先级:Core (核心机构) > Backup (关联替代机构)。
+- **ValidationRules**:
+    - `Phone`: 必须包含 Tasks 中的国家国际区号(如 +856, +66 等)。
+    - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
+    - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
+- **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
+
+# [INFORMATION_SCHEMA]
+请将结果填充至以下结构的 JSON 数组中:
+- Region:国家(必须与 Tasks 中的 Country 完全匹配)
+- NameCn: 单位名称(中文)
+- NameEn: 单位名称(英文)
+- Address: 详细地理位置(含省市区街道)
+- Scope: 经营范围(需强调其出口配额、生产能力或行业地位)
+- Contact: 联系人姓名及职务
+- Phone: 拨打全号(含区号)
+- Email: 商务联络邮箱
+- SiteUrl: 官方网站或权威社媒主页
+- PostUrl: 近一年内的商务动态/新闻链接
+- RecLevel: 推荐等级(枚举值:Core, Backup)
+- IntgAdvice: 对接深度建议(基于产业链交合、互补逻辑)
+
+# [OUTPUT_PROTOCOL]
+- 仅输出一个标准的 JSON Array 字符串。
+- 严禁任何 Markdown 说明文字、代码块之外的解释或开场白。
+- 确保 JSON 语法在 .NET `JsonSerializer.Deserialize` 下可直接解析。
+
+# [EXECUTION]
+根据以上配置,开始生成。"; ;
+        }
+    
         /// <summary>
         /// 商邀资料AI 设置词条
         /// </summary>
@@ -2661,11 +2906,12 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                     CreateUserId = dto.CurrUserId
                 };
 
-                var insert = await _sqlSugar.Insertable(dataInfo).ExecuteCommandAsync();
+                var insert = await _sqlSugar.Insertable(dataInfo).ExecuteReturnIdentityAsync();
                 if (insert < 1)
                 {
                     return Ok(JsonView(false, $"词条信息新增失败!"));
                 }
+                dataInfo.Id = insert;
             }
             else
             {
@@ -2690,6 +2936,70 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             }));
         }
 
+        /// <summary>
+        /// 商邀资料AI 设置复选框选中 批量
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        /// <summary>
+        /// 商邀资料AI 设置复选框选中 批量 (支持传入空数组进行重置)
+        /// </summary>
+        [HttpPost]
+        public async Task<IActionResult> InvitationAISetChecked([FromBody] InvitationAISetCheckedDto dto)
+        {
+            // 1. 基础参数校验 (注意:这里移除了对 Guids.Any() 的强校验,允许空集合)
+            if (dto.Id < 1 || dto.CurrUserId < 1 || dto.Guids == null)
+                return Ok(JsonView(false, "请求参数不完整"));
+
+            // 2. 获取主记录
+            var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+                .FirstAsync(x => x.IsDel == 0 && x.Id == dto.Id);
+
+            if (invAiInfo?.AiCrawledDetails == null || !invAiInfo.AiCrawledDetails.Any())
+                return Ok(JsonView(false, "数据不存在或集合为空"));
+
+            // 3. 准备更新所需数据
+            var guidSet = dto.Guids.ToHashSet();
+            var now = DateTime.Now;
+
+            // 获取操作人姓名
+            string operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.Id == dto.CurrUserId)
+                    .Select(x => x.CnName)
+                    .FirstAsync() ?? "-";
+
+            // 4. 单次遍历完成 重置 + 更新
+            // 如果 guidSet 为空,循环会将所有项的 IsChecked 置为 false
+            foreach (var item in invAiInfo.AiCrawledDetails)
+            {
+                if (guidSet.Contains(item.Guid))
+                {
+                    item.IsChecked = true;
+                }
+                else
+                {
+                    item.IsChecked = false;
+                }
+
+                item.OperatedAt = now;
+                item.Operator = operatorName;
+            }
+
+            // 5. 排序逻辑
+            // 即使是全量取消选中,也可以按最后操作时间排序,让最近变动的项在前
+            invAiInfo.AiCrawledDetails = invAiInfo.AiCrawledDetails
+                .OrderByDescending(x => x.OperatedAt)
+                .ToList();
+
+            // 6. 提交数据库
+            // 注意:SqlSugar 的 UpdateColumns 会序列化整个对象列表并覆盖数据库字段
+            await _sqlSugar.Updateable(invAiInfo)
+                .UpdateColumns(x => x.AiCrawledDetails)
+                .ExecuteCommandAsync();
+
+            return Ok(JsonView(true, guidSet.Any() ? "设置成功" : "已全部取消选中"));
+        }
+
         /// <summary>
         /// 商邀资料AI 保存
         /// </summary>
@@ -2716,6 +3026,14 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var editInfo = dto.AiCrawledDetail;
 
+            // 如果 Guid 为空,说明是新增数据,需要生成新的 Guid
+            if (string.IsNullOrEmpty(editInfo.Guid))
+            {
+                editInfo.Guid = Guid.NewGuid().ToString("N");
+                editInfo.Source = 2; // 标记为手动新增数据
+                editInfo.OperatedAt = DateTime.Now;
+            }
+
             var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
             editInfo.Operator = opUserName;
             editInfo.OperatedAt = DateTime.Now;
@@ -2769,6 +3087,78 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             }));
         }
 
+        /// <summary>
+        /// 商邀资料AI AI查询出的数据添加值商邀资料库
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        [ProducesResponseType(typeof(JsonView), StatusCodes.Status200OK)]
+        public async Task<IActionResult> InvitationAIInsertResource([FromBody] InvitationAIInsertResourceDto dto)
+        {
+            // 基础校验
+            if (dto.Id < 1 || dto.CurrUserId < 1 || dto.Guids == null || !dto.Guids.Any())
+                return Ok(JsonView(false, "参数不完整,请选择有效的单位数据"));
+
+            // 获取主记录
+            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI>()
+                .FirstAsync(x => x.IsDel == 0 && x.Id == dto.Id);
+
+            if (dataInfo?.AiCrawledDetails == null)
+                return Ok(JsonView(false, "当前数据信息不存在!"));
+
+            // 筛选出待转正的 AI 数据 (使用 HashSet 优化匹配)
+            var guidSet = dto.Guids.ToHashSet();
+            var targetAiDetails = dataInfo.AiCrawledDetails
+                .Where(x => x.Source == 1 && guidSet.Contains(x.Guid))
+                .ToList();
+
+            if (!targetAiDetails.Any())
+                return Ok(JsonView(false, "未找到符合条件的 AI 获取信息,无法重复添加或来源错误!"));
+
+            // 准备入库数据(批量构建)
+            var now = DateTime.Now;
+            var opUserName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+            var rawLocalDatas = targetAiDetails.Select(item => {
+                // 修改内存中的状态为本地数据
+                item.Source = 0;
+                item.OperatedAt = now;
+                item.Operator = opUserName;
+
+                return new Res_InvitationOfficialActivityData()
+                {
+                    // 批量加密操作
+                    Country = AesEncryptionHelper.Encrypt(item.Region),
+                    UnitName = AesEncryptionHelper.Encrypt(item.NameCn),
+                    Address = AesEncryptionHelper.Encrypt(item.Address),
+                    Field = AesEncryptionHelper.Encrypt(item.Scope),
+                    Contact = AesEncryptionHelper.Encrypt(item.Contact),
+                    Tel = AesEncryptionHelper.Encrypt(item.Phone),
+                    Email = AesEncryptionHelper.Encrypt(item.Email),
+                    UnitWeb = AesEncryptionHelper.Encrypt(item.SiteUrl),
+                    LastUpdateUserId = dto.CurrUserId,
+                    LastUpdateTime = now,
+                    CreateUserId = dto.CurrUserId,
+                    CreateTime = now
+                };
+            }).ToList();
+
+            // 开启事务执行双表更新
+            var result = await _sqlSugar.UseTranAsync(async () =>
+            {
+                // 插入本地资料库
+                await _sqlSugar.Insertable(rawLocalDatas).ExecuteCommandAsync();
+
+                // 更新 AI 主表的状态(全量覆盖 JSON 列)
+                await _sqlSugar.Updateable(dataInfo)
+                    .UpdateColumns(x => x.AiCrawledDetails)
+                    .ExecuteCommandAsync();
+            });
+
+            return Ok(result.IsSuccess
+                ? JsonView(true, "成功添加至资料库并更新来源状态")
+                : JsonView(false, $"操作失败:{result.ErrorMessage}"));
+        }
+
         /// <summary>
         /// 商邀资料AI 混元AI续写
         /// </summary>
@@ -2971,6 +3361,133 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             return Ok(JsonView(false, $"AI续写失败!"));
         }
 
+        /// <summary>
+        ///  商邀资料AI 混元AI续写(SSE流式推送)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task InvitationAICompleteTextStream([FromBody] InvitationAICompleteTextDto dto)
+        {
+            var response = HttpContext.Response;
+            response.Headers.Add("Content-Type", "text/event-stream");
+            response.Headers.Add("Cache-Control", "no-cache");
+            response.Headers.Add("Connection", "keep-alive");
+            response.Headers.Add("X-Accel-Buffering", "no"); // 禁用 Nginx 缓存
+
+            // 内部推送工具
+            async Task SendStep(int progress, string msg, object data = null)
+            {
+                var payload = JsonConvert.SerializeObject(new
+                {
+                    progress,
+                    message = msg,
+                    data
+                });
+                await response.WriteAsync($"data: {payload}\n\n");
+                await response.Body.FlushAsync();
+            }
+
+            try
+            {
+                // 初始化检查 (Progress: 5%)
+                var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
+                if (invAiInfo?.EntryInfo == null)
+                {
+                    await SendStep(-1, "请先设置关键字信息!");
+                    return;
+                }
+                await SendStep(10, "任务初始化完成,正在计算补齐缺口...");
+
+                // 任务拆解逻辑
+                var entryInfo = invAiInfo.EntryInfo;
+                var aiTasks = new List<CountryAIPormptInfo>();
+                int targetPerCountry = entryInfo.NeedCount;
+
+                foreach (var countryName in entryInfo.TargetCountry)
+                {
+                    var countryDataCount = invAiInfo.AiCrawledDetails.Count(x => x.Region == countryName);
+                    int aiNeedCount = targetPerCountry - countryDataCount;
+                    if (aiNeedCount > 0) aiTasks.Add(new() { Country = countryName, Count = aiNeedCount });
+                }
+
+                if (!aiTasks.Any())
+                {
+                    await SendStep(100, "数据已满额,无需续写", invAiInfo.AiCrawledDetails);
+                    return;
+                }
+
+                // 准备 AI Prompt (Progress: 20%)
+                var existingNames = new HashSet<string>(invAiInfo.AiCrawledDetails.Where(x => x.Source == 1).Select(x => x.NameCn));
+                string promptOther = $"请基于以下已存在的名称列表进行推荐,避免重复:{string.Join(", ", existingNames)}。{entryInfo.OtherConstraints}";
+                entryInfo.OtherConstraints = promptOther; // 将去重提示注入 entryInfo,确保 Prompt 构建时包含该信息
+
+                // 构建 Question 
+                string question = BuildHunyuanPrompt(aiTasks, entryInfo);
+
+                await SendStep(30, "Hunyuan AI 正在深度检索跨境商邀数据,请稍候...");
+
+                // 调用 AI (Progress: 30% - 80%)
+                // 注意:这里如果能换成流式接口(ChatCompletionsStream)会更好,
+                // 如果依然用非流式,这里会有一段较长的等待。
+                string aiRawResponse = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(question);
+
+                await SendStep(85, "数据已捕获,正在进行格式校验与去重...");
+
+                // 5. 解析与清洗数据
+                var hunyuanAIInvDatas = ProcessAIResponse(aiRawResponse);
+                string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+                foreach (var x in hunyuanAIInvDatas)
+                {
+                    x.Guid = Guid.NewGuid().ToString("N");
+                    x.Source = 1;
+                    x.Operator = operatorName;
+                    x.OperatedAt = DateTime.Now;
+                }
+
+                // 6. 数据库操作 (Progress: 95%)
+                invAiInfo.AiCrawledDetails.AddRange(hunyuanAIInvDatas);
+                invAiInfo.AiCrawledDetails = invAiInfo.AiCrawledDetails.OrderByDescending(x => x.OperatedAt).ToList();
+
+                var update = await _sqlSugar.Updateable(invAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+
+                if (update > 0)
+                {
+                    await SendStep(100, $"Hunyuan AI 续写成功!新增 {hunyuanAIInvDatas.Count} 条数据", invAiInfo.AiCrawledDetails);
+                }
+                else
+                {
+                    await SendStep(-1, "数据库更新失败");
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "SSE 续写异常");
+                await SendStep(-1, $"炼金炸炉:{ex.Message}");
+            }
+            finally
+            {
+                await response.Body.FlushAsync();
+                await response.CompleteAsync(); // 彻底关闭连接
+            }
+        }
+
+        private List<InvitationAIInfo> ProcessAIResponse(string response)
+        {
+            if (string.IsNullOrWhiteSpace(response)) return new List<InvitationAIInfo>();
+            string cleanJson = response.Trim();
+            if (cleanJson.Contains("```json"))
+            {
+                cleanJson = Regex.Match(cleanJson, @"```json([\s\S]*?)```").Groups[1].Value.Trim();
+            }
+            else if (cleanJson.Contains("```"))
+            {
+                cleanJson = Regex.Match(cleanJson, @"```([\s\S]*?)```").Groups[1].Value.Trim();
+            }
+            return JsonConvert.DeserializeObject<List<InvitationAIInfo>>(cleanJson) ?? new List<InvitationAIInfo>();
+        }
+
         /// <summary>
         /// 商邀资料AI 文件生成(基于混元AI已爬取数据进行格式化输出,供用户下载使用)
         /// </summary>
@@ -3422,6 +3939,209 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             return Ok(JsonView(true, msgSb.ToString()));
         }
 
+        /// <summary>
+        /// 商邀资料AI 生成邮件(SSE流式推送)
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task InvitationAIGenerateEmailStream([FromBody] InvitationAIGenerateEmailDto dto)
+        {
+            var response = HttpContext.Response;
+            response.Headers.Add("Content-Type", "text/event-stream");
+            response.Headers.Add("Cache-Control", "no-cache");
+            response.Headers.Add("Connection", "keep-alive");
+            response.Headers.Add("X-Accel-Buffering", "no");
+
+            async Task SendStep(int progress, string msg, object data = null)
+            {
+                var payload = JsonConvert.SerializeObject(new
+                {
+                    progress,
+                    message = msg,
+                    data,
+                    operatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
+                });
+                await response.WriteAsync($"data: {payload}\n\n");
+                await response.Body.FlushAsync();
+            }
+
+            try
+            {
+                // 1. 基础校验与资源加载 (Progress: 5%)
+                if (dto.Id < 1 || dto.Guids == null || !dto.Guids.Any())
+                {
+                    await SendStep(-1, "请求参数不完整,请选择单位");
+                    return;
+                }
+
+                var invAiInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.Id == dto.Id).FirstAsync();
+                if (invAiInfo?.AiCrawledDetails == null)
+                {
+                    await SendStep(-1, "基础商邀资料不存在");
+                    return;
+                }
+
+                await SendStep(10, "Hunyuan AI 正在分析考察团组背景与访问意图...");
+
+                // 2. 准备 AI 上下文数据 (Progress: 15%)
+                var clientInfoSources = invAiInfo.AiCrawledDetails.Where(x => dto.Guids.Contains(x.Guid)).ToList();
+                var clientInfosForAI = clientInfoSources.Select(x => new AICreateEmailInfo()
+                {
+                    Guid = x.Guid,
+                    NameCn = x.NameCn,
+                    Scope = x.Scope
+                }).ToList();
+
+                var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+                    .Where(x => x.IsDel == 0 && x.Id == invAiInfo.GroupId)
+                    .Select(x => new { x.TeamName, x.VisitPurpose, x.VisitDate })
+                    .FirstAsync();
+
+                string operatorName = await _sqlSugar.Queryable<Sys_Users>()
+                    .Where(x => x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
+
+                // 3. 构建 Prompt 并调用 AI (Progress: 25%)
+                await SendStep(30, $"Hunyuan AI 正在为 {clientInfosForAI.Count} 家单位撰写定制化正式邮件...");
+
+                // 此处沿用你原有的庞大 Prompt 逻辑
+                string pormpt = $@"
+# Role
+你是一位精通国际政企关系的【首席联络官】。你具备极强的行业分析能力,能通过 [SourceEntity] 的名称自动检索并推导其行政职能与行业地位,并以此撰写具有战略高度、语调优雅的正式商务邮件。
+
+# Intelligence Task: Source Profiling
+在生成邮件前,请先执行以下逻辑:
+1. **职能推导**:基于 [SourceEntity] 的名称,自动识别其在所属领域的具体行政职能与政策影响力。
+2. **战略对齐**:将其职能与全球大趋势(如:Sustainable Urbanization, Digital Transformation, Carbon Neutrality)挂钩,作为邮件第二段的叙事背景。
+
+# [RICH_TEXT_STANDARDS_FOR_QUILL]
+- **Prohibited Tags**: 绝对禁止包含 <html>, <body>, <head>, <!DOCTYPE>, <hr>, <style>。
+- **Whitelisted Tags**: 仅允许使用 <h3> (用于主题), <p> (用于段落), <ul>, <li>, <strong>, <br>。
+- **Structural Integrity**: 
+    - 严禁出现裸露文本,所有段落必须由 <p> 包裹。
+    - 列表必须使用标准 <ul><li> 嵌套结构。
+- **No Markdown**: 严禁在 Content 中出现 ###, **, --- 等 Markdown 符号。
+
+# Reference Model (Few-Shot Example)
+在生成时,请严格参考以下范例的【语气】、【五段式结构】和【外交辞令】:
+- Subject: Official Study Visit Inquiry – [Delegation Name] ([Date])
+- Para 1: ""On behalf of [SourceEntity], I am writing to formally propose...""
+- Para 2: ""As a pivotal megacity [AI根据Source地位补全]... we recognize [Target]’s leadership in...""
+- Para 3: ""We are particularly keen to explore... [基于Target经营范围推导的3个技术点]...""
+- Para 4: ""We propose a 2–3 day visit... during the week of [Logistics]...""
+- Para 5: ""Enclosed please find the [PascalCase_File]...""
+
+# Task
+根据 [TargetList] 中每个单位的【经营范围】,为 [SourceEntity] 生成独立的英文访问请求邮件。
+
+# Inputs
+- [SourceEntity]: [{invAiInfo.EntryInfo?.OriginUnit ?? ""}]
+- [VisitPurpose]: [{groupInfo?.VisitPurpose}]
+- [TargetList]: [{JsonConvert.SerializeObject(clientInfosForAI)}]
+- [Logistics]: [{groupInfo?.VisitDate.ToString("yyyy-MM-dd")}]
+
+# Execution Logic (Chain of Thought)
+1. **Scope-to-Focus Analysis**: 
+   - 深入分析每个 Target 的【经营范围】。
+   - 自动推导 3 个与 [VisitPurpose] 高度对齐的专业考察点(如:Policy Frameworks, Technical Standards, Operational Case Studies)。
+2. **Modular Drafting**: 
+   - 必须严格遵循参考范例的 5 段式逻辑。
+   - 针对不同职能属性(监管/技术/运营)动态微调邮件主题(Subject)。
+3. **No Personnel Reference**: 严禁提及“名单、人数、成员、审核中”等任何具体人员信息,保持机构对等对话的高度。
+
+# Constraints & Standards
+- **Tone**: Formal, Strategic, and Executive (庄重、具战略高度、执行力强)。
+- **Naming Protocol**: 附件引用统一使用 `JointVisitAgenda`, `StrategicInquiryBrief` (PascalCase)。
+- **Escaping**: 必须对 HTML 内部的所有双引号进行严格转义(\""),确保 JSON 字符串合法。
+- **Output Format**: 仅输出一个合规的 JSON 数组,不包含任何解释性文字。
+- **Field Mapping**:
+  - `Guid`: 原样保留。
+  - `NameCn`: 原样保留。
+  - `Scope`: 原样保留。
+  - `Subject`: 动态生成的英文主题。
+  - `Content`: 包含 QUILL的英文正文。
+
+# JSON Structure Template
+[
+  {{{{
+    ""Guid"": ""ID"",
+    ""NameCn"": ""Entity Name"",
+    ""Scope"": ""Original Scope Description"",
+    ""Subject"": ""Dynamic English Subject Line"",
+    ""Content"": ""<h3>Subject: ...</h3><p>Dear Leadership Team of <strong>[Target]</strong>,</p><p>On behalf of <strong>[SourceEntity]</strong>, I am writing to propose...</p><ul><li><strong>Focus Area:</strong> Technical analysis of...</li></ul><p>Sincerely,<br><strong>Office of the Chief Liaison</strong><br>[SourceEntity]</p>""
+  }}}}
+]";
+
+                // 调用 AI (此处为阻塞式等待 AI 结果,若混元支持 Stream 可进一步拆解)
+                string aiResponse = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(pormpt);
+
+                await SendStep(80, "邮件初稿已生成,正在进行 HTML 格式校验与转义处理...");
+
+                // 4. 解析结果 (Progress: 85%)
+                var hunyuanAIEmailDatas = new List<AICreateEmailInfo>();
+                if (!string.IsNullOrWhiteSpace(aiResponse))
+                {
+                    // 预处理:过滤 AI 可能返回的 Markdown 标记
+                    string cleanJson = aiResponse.Trim();
+                    if (cleanJson.StartsWith("```json"))
+                        cleanJson = cleanJson.Substring(7, cleanJson.Length - 10).Trim();
+                    else if (cleanJson.StartsWith("```"))
+                        cleanJson = cleanJson.Substring(3, cleanJson.Length - 6).Trim();
+
+                    try
+                    {
+                        // 解析并注入 Source 标识
+                        hunyuanAIEmailDatas = JsonConvert.DeserializeObject<List<AICreateEmailInfo>>(cleanJson);
+
+                    }
+                    catch (JsonException ex)
+                    {
+                        // 记录日志并考虑 fallback 策略
+                        _logger.LogError(ex, "Hunyuan AI 响应解析失败。原始数据:{Response}", aiResponse);
+                    }
+                }
+                if (hunyuanAIEmailDatas == null || !hunyuanAIEmailDatas.Any())
+                {
+                    await SendStep(-1, "AI 格式解析失败,请尝试重新生成");
+                    return;
+                }
+
+                // 5. 更新数据模型 (Progress: 90%)
+                foreach (var client in clientInfoSources)
+                {
+                    var aiEmail = hunyuanAIEmailDatas.FirstOrDefault(x => x.Guid == client.Guid);
+                    if (aiEmail != null)
+                    {
+                        client.EmailInfo.Status = 2; // 已生成
+                        client.EmailInfo.EmailTitle = aiEmail.Subject;
+                        client.EmailInfo.EmailContent = aiEmail.Content;
+                        client.EmailInfo.Operator = operatorName;
+                        client.EmailInfo.OperatedAt = DateTime.Now;
+                    }
+                }
+
+                // 6. 数据库持久化 (Progress: 95%)
+                // 采用增量更新策略,避免直接 Where 过滤掉其他未选中的数据
+                var update = await _sqlSugar.Updateable(invAiInfo).UpdateColumns(x => x.AiCrawledDetails).ExecuteCommandAsync();
+                if (update > 0)
+                {
+                    await SendStep(100, "邮件全部生成完毕并已存入团组资料库", invAiInfo.AiCrawledDetails);
+                }
+                else
+                {
+                    await SendStep(-1, "数据库写入失败,请联系管理员");
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "邮件生成异常");
+                await SendStep(-1, $"生成失败:{ex.Message}");
+            }
+            finally
+            {
+                await response.CompleteAsync();
+            }
+        }
+
         /// <summary>
         /// 商邀资料AI 邮件保存
         /// </summary>

+ 89 - 0
OASystem/OASystem.Api/OAMethodLib/HotmailEmail/HotmailEmailService.cs

@@ -0,0 +1,89 @@
+
+using MailKit.Net.Smtp;
+using MailKit.Security;
+using MimeKit;
+
+namespace OASystem.API.OAMethodLib.HotmailEmail
+{
+    /// <summary>
+    /// Hotemail 服务
+    /// </summary>
+    public class HotmailEmailService : IHotmailEmailService
+    {
+        private readonly IConfiguration _configuration;
+
+        public HotmailEmailService(IConfiguration configuration)
+        {
+            _configuration = configuration;
+        }
+
+        public async Task<bool> SendEmailAsync(string toEmail, string subject, string htmlContent)
+        {
+            // 从配置读取信息
+            var smtpServer = _configuration["HotEmailConfig:SmtpServer"];
+            var smtpPort = int.Parse(_configuration["HotEmailConfig:SmtpPort"]);
+            var senderEmail = _configuration["HotEmailConfig:SenderEmail"];
+            var senderName = _configuration["HotEmailConfig:SenderName"];
+            var appPassword = _configuration["HotEmailConfig:AppPassword"];
+
+            // 移除应用密码中的所有 "-" 分隔符
+            appPassword = appPassword?.Replace("-", "");
+
+            // 验证配置必填项
+            if (string.IsNullOrEmpty(smtpServer) || string.IsNullOrEmpty(senderEmail) || string.IsNullOrEmpty(appPassword))
+            {
+                Console.WriteLine("[Email Error]: 邮件配置不完整");
+                return false;
+            }
+
+            var message = new MimeMessage();
+            message.From.Add(new MailboxAddress(senderName, senderEmail));
+
+            // 校验收件人邮箱格式,避免无效地址报错
+            if (!MailboxAddress.TryParse(toEmail, out var recipient))
+            {
+                Console.WriteLine($"[Email Error]: 收件人邮箱格式错误 - {toEmail}");
+                return false;
+            }
+            message.To.Add(recipient);
+
+            message.Subject = subject;
+
+            var bodyBuilder = new BodyBuilder { HtmlBody = htmlContent };
+            message.Body = bodyBuilder.ToMessageBody();
+
+            using var client = new SmtpClient();
+            try
+            {
+                // 显式设置超时时间,避免连接超时
+                client.Timeout = 30000; // 30秒超时
+
+                // 连接服务器 (Hotmail/Outlook 必须使用 StartTls)
+                await client.ConnectAsync(smtpServer, smtpPort, SecureSocketOptions.StartTls);
+
+                // 身份验证
+                await client.AuthenticateAsync(senderEmail, appPassword);
+
+                // 执行发送
+                await client.SendAsync(message);
+                await client.DisconnectAsync(true);
+
+                Console.WriteLine($"[Email Success]: 邮件已发送至 {toEmail}");
+                return true;
+            }
+            catch (Exception ex)
+            {
+                // 打印完整异常链,定位真实错误
+                var errorMsg = $"[Email Error]: {ex.Message}";
+                var innerEx = ex.InnerException;
+                while (innerEx != null)
+                {
+                    errorMsg += $"\n[Inner Error]: {innerEx.Message}";
+                    innerEx = innerEx.InnerException;
+                }
+                Console.WriteLine(errorMsg);
+                return false;
+            }
+        }
+    }
+}

+ 7 - 0
OASystem/OASystem.Api/OAMethodLib/HotmailEmail/IHotmailEmailService.cs

@@ -0,0 +1,7 @@
+namespace OASystem.API.OAMethodLib.HotmailEmail
+{
+    public interface IHotmailEmailService
+    {
+        Task<bool> SendEmailAsync(string toEmail, string subject, string htmlContent);
+    }
+}

+ 1 - 0
OASystem/OASystem.Api/OASystem.API.csproj

@@ -54,6 +54,7 @@
     <PackageReference Include="Flurl.Http" Version="3.2.4" />
     <PackageReference Include="iTextSharp" Version="5.5.13.5" />
     <PackageReference Include="JsonDiffPatch.Net" Version="2.3.0" />
+    <PackageReference Include="MailKit" Version="4.15.1" />
     <PackageReference Include="Markdig" Version="0.33.0" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.11" />
     <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0" />

+ 5 - 0
OASystem/OASystem.Api/Program.cs

@@ -11,6 +11,7 @@ using OASystem.API.OAMethodLib.AMapApi;
 using OASystem.API.OAMethodLib.APNs;
 using OASystem.API.OAMethodLib.DeepSeekAPI;
 using OASystem.API.OAMethodLib.GenericSearch;
+using OASystem.API.OAMethodLib.HotmailEmail;
 using OASystem.API.OAMethodLib.Hub.Hubs;
 using OASystem.API.OAMethodLib.HunYuanAPI;
 using OASystem.API.OAMethodLib.JuHeAPI;
@@ -593,6 +594,10 @@ builder.Services.AddSignalR()
 builder.Services.TryAddSingleton(typeof(CommonService));
 #endregion
 
+#region hotmail
+builder.Services.AddTransient<IHotmailEmailService, HotmailEmailService>();
+#endregion
+
 var app = builder.Build();
 
 //// 1. 异常处理器应该在最早的位置(除了日志等)

+ 9 - 0
OASystem/OASystem.Api/appsettings.json

@@ -596,5 +596,14 @@
     "AppId": 100015480940,
     "Region": "ap-chengdu",
     "Version": "2023-09-01"
+  },
+   // 邮件发送配置
+  "HotEmailConfig": {
+    "SmtpServer": "smtp-mail.outlook.com",
+    "SmtpPort": 587,
+    "SenderEmail": "Roy.Lei.Atom@hotmail.com",
+    "SenderName": "Roy Lei",
+    "AppPassword": "mbjefaowzicprbfx"
+    //"AppPassword": "apirccbinnuditof"
   }
 }

+ 8 - 0
OASystem/OASystem.Domain/Dtos/Resource/InvitationAI.cs

@@ -29,6 +29,11 @@ namespace OASystem.Domain.Dtos.Resource
         public int CurrUserId { get; set; }
     }
 
+    public class InvitationAISetCheckedDto : InvitationAISearchDto
+    {
+        public List<string> Guids { get; set; }
+    }
+
     public class InvitationAISearchDto
     {
         public int Id { get; set; }
@@ -85,4 +90,7 @@ namespace OASystem.Domain.Dtos.Resource
 
     }
 
+    public class InvitationAIInsertResourceDto: InvitationAIGenerateEmailDto
+    { }
+
 }

+ 7 - 2
OASystem/OASystem.Domain/Entities/Resource/Res_InvitationAI.cs

@@ -48,9 +48,14 @@ namespace OASystem.Domain.Entities.Resource
         /// </summary>
         public string Guid { get; set; }
 
+        /// <summary>
+        /// 复选框是否选中
+        /// </summary>
+        public bool IsChecked { get; set; } = false;
+
         /// <summary>
         /// 数据来源
-        /// 0-本地数据;1-第三方AI接口数据
+        /// 0-本地数据;1-第三方AI接口数据;2-用户手动输入数据
         /// </summary>
         public int Source { get; set; } = 0;
 
@@ -152,7 +157,7 @@ namespace OASystem.Domain.Entities.Resource
         /// <summary>
         /// 操作时间
         /// </summary>
-        public DateTime OperatedAt { get; set; }
+        public DateTime OperatedAt { get; set; } = new DateTime();
         /// <summary>
         /// 操作人
         /// </summary>

+ 1 - 0
OASystem/OASystem.Domain/ViewModels/JsonView.cs

@@ -47,3 +47,4 @@ public class JsonView
     }
 
 }
+