ProcessAndNotifySummary.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. using OASystem.API.OAMethodLib.DeepSeekAPI;
  2. using OASystem.API.OAMethodLib.Hotmail;
  3. using OASystem.API.OAMethodLib.QiYeWeChatAPI;
  4. using OASystem.Domain.ViewModels.QiYeWeChat;
  5. using System.Text.Json;
  6. using System.Text.Json.Serialization;
  7. using System.Web;
  8. using static OASystem.API.OAMethodLib.Hotmail.HotmailService;
  9. namespace OASystem.API.OAMethodLib.Quartz.Business
  10. {
  11. /// <summary>
  12. /// 获取hotmail邮件,自动总结邮件内容发送至企业微信邮件
  13. /// </summary>
  14. public static class ProcessAndNotifySummary
  15. {
  16. private static readonly SqlSugarClient _sqlSugar = AutofacIocManager.Instance.GetService<SqlSugarClient>();
  17. private static readonly IQiYeWeChatApiService _qiYeWeChatApiService = AutofacIocManager.Instance.GetService<IQiYeWeChatApiService>();
  18. private static readonly HotmailService _hotmailService = AutofacIocManager.Instance.GetService<HotmailService>();
  19. private static readonly IDeepSeekService _deepSeekService = AutofacIocManager.Instance.GetService<IDeepSeekService>();
  20. /// <summary>
  21. /// hotmail 邮件 汇总 发送企微邮件
  22. /// 时间范围 昨天
  23. /// </summary>
  24. public static async void ProcessAndNotifySummaryAsync()
  25. {
  26. var hotmailConfigs = await _hotmailService.GetUserMailConfigListAsync();
  27. if (hotmailConfigs == null || !hotmailConfigs.Any()) return;
  28. var hotmails = hotmailConfigs.Select(x => x.UserName).ToList();
  29. var userIds = hotmailConfigs.Select(x => x.UserId).ToList();
  30. var cstZone = CommonFun.GetCstZone();
  31. var nowInCst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cstZone);
  32. var yesterdayStart = nowInCst.Date.AddDays(-1);
  33. var yesterdayEnd = yesterdayStart.AddDays(3).AddTicks(-1);
  34. var emailInfos = await _hotmailService.GetMergedMessagesAsync(hotmails, yesterdayStart, yesterdayEnd);
  35. // 处理无邮件情况
  36. if (emailInfos == null || !emailInfos.Any())
  37. {
  38. await NotifyEmptyEmails(userIds);
  39. return;
  40. }
  41. // 1. 预处理:限制每封邮件正文长度,防止 Token 溢出
  42. foreach (var mail in emailInfos)
  43. {
  44. mail.Content = CleanHtmlToPlainText(mail.Content);
  45. }
  46. // 2. 调用 AI
  47. var question = BuildMailSummaryPrompt(emailInfos);
  48. var res = await _deepSeekService.ChatAsync(question);
  49. if (res.Success)
  50. {
  51. // 清洗 AI 可能带出的 Markdown 格式符
  52. string cleanJson = res.Answer.Trim();
  53. if (cleanJson.StartsWith("```json")) cleanJson = cleanJson.Replace("```json", "").Replace("```", "").Trim();
  54. try
  55. {
  56. var aiSummaryResults = JsonConvert.DeserializeObject<List<AiSummaryResult>>(cleanJson);
  57. var users = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && userIds.Contains(x.Id)).Select(x => new { x.Id, x.Email }).ToListAsync();
  58. foreach (var hotmailConfig in hotmailConfigs)
  59. {
  60. var qwEmail = users.FirstOrDefault(x => x.Id == hotmailConfig.UserId)?.Email;
  61. if (string.IsNullOrEmpty(qwEmail)) continue;
  62. if (hotmailConfig.UserName.Equals("925554512@qq.com"))
  63. {
  64. hotmailConfig.UserName = "Roy.Lei.Atom@hotmail.com";
  65. }
  66. // 获取 AI 为该账号生成的摘要
  67. var summary = aiSummaryResults?.FirstOrDefault(x => x.Recipient.Equals(hotmailConfig.UserName, StringComparison.OrdinalIgnoreCase));
  68. string finalSubject = $"{DateTime.Now:yyyy-MM-dd} - 邮件汇总";
  69. string finalBody = "AI 未能成功生成今日摘要。";
  70. if (summary != null)
  71. {
  72. finalSubject = $"[AI摘要] {summary.EmailSubject}";
  73. finalBody = summary.HtmlBody;
  74. }
  75. await _qiYeWeChatApiService.EmailSendAsync(new EmailRequestDto
  76. {
  77. ToEmails = new List<string> { qwEmail },
  78. Subject = finalSubject,
  79. Body = finalBody,
  80. });
  81. }
  82. }
  83. catch (Exception ex)
  84. {
  85. // 记录解析 JSON 失败日志
  86. }
  87. }
  88. }
  89. /// <summary>
  90. /// 纯正则实现:剔除 HTML 标签、样式和脚本,保留核心文本
  91. /// </summary>
  92. private static string CleanHtmlToPlainText(string? html)
  93. {
  94. if (string.IsNullOrEmpty(html)) return string.Empty;
  95. // 1. 剔除脚本 (Script) 和 样式 (Style) 及其内部内容
  96. html = Regex.Replace(html, @"<(script|style)[^>]*?>.*?</\1>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
  97. // 2. 剔除所有 HTML 标签
  98. html = Regex.Replace(html, @"<[^>]*>", " ");
  99. // 3. 将 HTML 实体字符转换回普通字符 (例如 &nbsp; -> 空格, &lt; -> <)
  100. html = HttpUtility.HtmlDecode(html);
  101. // 4. 清理多余的空白字符和重复换行
  102. html = Regex.Replace(html, @"\s+", " "); // 将多个空格/换行合并为一个空格
  103. html = Regex.Replace(html, @"(\n\s*){2,}", "\n"); // 压缩重复换行
  104. return html.Trim();
  105. }
  106. public static string BuildMailSummaryPrompt(List<MailDto> mailList)
  107. {
  108. var rawDataJson = System.Text.Json.JsonSerializer.Serialize(mailList, new JsonSerializerOptions { WriteIndented = false });
  109. return $@"
  110. # Role: 高级邮件情报官 (Senior Email Intelligence Officer)
  111. ## Task
  112. 分析提供的 {mailList.Count} 封原始邮件,排除垃圾内容,并为每个收件人生成 HTML 格式的每日摘要报告。
  113. ## Rule & Logic
  114. 1. **Filtering**: 严禁包含:自动订阅、社交媒体动态、验证码、退订链接邮件。
  115. 2. **Analysis**: 对同一收件人的邮件按业务逻辑归类(如:财务类、技术类、会议类)。
  116. 3. **HTML Requirements**:
  117. - 必须使用 `border-collapse: collapse; width: 100%;` 的表格。
  118. - 主题色使用蓝色 (#2563eb)。
  119. - 包含三部分:【核心概览】、【详情表格 (列: 时间, 主题, 来源, 摘要)】、【待办建议 (Action Items)】。
  120. - 所有 CSS 必须 Inline Style。
  121. ## HTML Format (Strict)
  122. - 仅允许使用: <h3> (标题), <p> (段落), <ul>/<li> (列表), <strong> (加粗), <hr> (分割线)。
  123. - 禁止使用: <table>, <div>, <span>, class, id。
  124. - 样式: 仅在 <h3> 中使用 style=""color:#2563eb""。
  125. ## Constraints
  126. - 输出必须是合法的 JSON 数组,严禁任何前言或后缀文字。
  127. - 属性名:Recipient, EmailSubject, HtmlBody。
  128. - HtmlBody 必须是完整的 HTML 字符串,注意 JSON 内部引号转义。
  129. ## Input Data (JSON)
  130. {rawDataJson}
  131. ";
  132. }
  133. private static async Task NotifyEmptyEmails(List<int> userIds)
  134. {
  135. var userEmails = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && userIds.Contains(x.Id)).Select(x => x.Email).ToListAsync();
  136. if (userEmails.Any())
  137. {
  138. await _qiYeWeChatApiService.EmailSendAsync(new EmailRequestDto
  139. {
  140. ToEmails = userEmails,
  141. Subject = $"{DateTime.Now:yyyy-MM-dd} - 邮件汇总",
  142. Body = "昨日暂未收到有效邮件。"
  143. });
  144. }
  145. }
  146. public class AiSummaryResult
  147. {
  148. public string Recipient { get; set; } = string.Empty;
  149. public string EmailSubject { get; set; } = string.Empty;
  150. public string HtmlBody { get; set; } = string.Empty;
  151. }
  152. }
  153. }