Pārlūkot izejas kodu

hotmail收邮件自动任务

Lyyyi 1 nedēļu atpakaļ
vecāks
revīzija
b8cda12764

+ 29 - 4
OASystem/OASystem.Api/Controllers/AITestController.cs

@@ -1,14 +1,16 @@
 using Flurl.Http.Configuration;
+using Microsoft.Extensions.Options;
 using OASystem.API.OAMethodLib.DoubaoAPI;
 using OASystem.API.OAMethodLib.Hotmail;
 using OASystem.API.OAMethodLib.HunYuanAPI;
 using OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
 using OASystem.API.OAMethodLib.QiYeWeChatAPI;
+using OASystem.API.OAMethodLib.Quartz.Business;
 using OASystem.Domain.ViewModels.QiYeWeChat;
+using OASystem.RedisRepository;
 using System.IdentityModel.Tokens.Jwt;
 using System.Text.Json;
 using static OASystem.API.OAMethodLib.Hotmail.HotmailService;
-using OASystem.RedisRepository;
 
 namespace OASystem.API.Controllers
 {
@@ -25,8 +27,20 @@ namespace OASystem.API.Controllers
         private readonly IQiYeWeChatApiService _qiYeWeChatApiService;
         private readonly System.Net.Http.IHttpClientFactory _httpClientFactory;
         private readonly HotmailService _hotmailService;
-
-        public AITestController(IHunyuanService hunyuanService, IDoubaoService doubaoService, ILogger<AITestController> logger, IQiYeWeChatApiService qiYeWeChatApiService, HotmailService hotmailService, System.Net.Http.IHttpClientFactory httpClientFactory, IConfiguration config)
+        private readonly IMicrosoftGraphMailboxService _microsoftGraphMailboxService;
+        private readonly IOptionsMonitor<MicrosoftGraphMailboxOptions> _microsoftGraphMailboxOptions;
+
+        public AITestController(
+            IHunyuanService hunyuanService, 
+            IDoubaoService doubaoService, 
+            ILogger<AITestController> logger, 
+            IQiYeWeChatApiService qiYeWeChatApiService, 
+            HotmailService hotmailService, 
+            System.Net.Http.IHttpClientFactory httpClientFactory, 
+            IConfiguration config,
+            IMicrosoftGraphMailboxService microsoftGraphMailboxService,
+            IOptionsMonitor<MicrosoftGraphMailboxOptions> microsoftGraphMailboxOptions
+            )
         {
             _hunyuanService = hunyuanService;
             _doubaoService = doubaoService;
@@ -35,6 +49,8 @@ namespace OASystem.API.Controllers
             _hotmailService = hotmailService;
             _httpClientFactory = httpClientFactory;
             _config = config;
+            _microsoftGraphMailboxService = microsoftGraphMailboxService;
+            _microsoftGraphMailboxOptions = microsoftGraphMailboxOptions;
         }
 
         #region 企业微信发送邮件测试
@@ -375,7 +391,16 @@ namespace OASystem.API.Controllers
             return StatusCode(200, res);
         }
 
+        /// <summary>
+        /// hotmail 定时发送邮件 汇总 测试
+        /// </summary>
+        [HttpPost("hotmailSummarySeedQW")]
+        public async Task<ActionResult<string>> HotmailSummary()
+        {
+            ProcessAndNotifySummary.ProcessAndNotifySummaryAsync();
 
+            return StatusCode(200, "发送成功");
+        }
 
 
         #region 微软 auth
@@ -613,7 +638,7 @@ namespace OASystem.API.Controllers
             {
                 cacheEntry = JsonConvert.DeserializeObject<EmailAuthRedisCache>(json);
             }
-            catch (JsonException ex)
+            catch (System.Text.Json.JsonException ex)
             {
                 _logger.LogWarning(ex, "Redis 键 {Key} 内容不是合法 JSON(应用 StringGetRawAsync + JSON,勿用 StringGetAsync<T>,后者为 BinaryFormatter)", key);
                 return BadRequest(new { message = "Redis 值为 JSON 文本时须用 StringGetRawAsync 再反序列化;StringGetAsync<T> 仅适用于 BinaryFormatter 写入的数据", detail = ex.Message });

+ 62 - 15
OASystem/OASystem.Api/OAMethodLib/Hotmail/HotmailService.cs

@@ -14,7 +14,7 @@ namespace OASystem.API.OAMethodLib.Hotmail
     {
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly IConfiguration _config;
-        private  readonly SqlSugarClient _sqlSugar;
+        private readonly SqlSugarClient _sqlSugar;
         private const string RedisKeyPrefix = "MailAlchemy:Token:";
 
         public HotmailService(IHttpClientFactory httpClientFactory, IConfiguration config, SqlSugarClient sqlSugar)
@@ -123,7 +123,6 @@ namespace OASystem.API.OAMethodLib.Hotmail
 
                     return response?.Value?.Select(m => new MailDto
                     {
-                      
                         MessageId = m.Id,
                         Subject = m.Subject,
                         Content  = m.Body?.Content,
@@ -200,10 +199,25 @@ namespace OASystem.API.OAMethodLib.Hotmail
         }
 
         /// <summary>
-        /// 获取邮箱配置信息
+        /// 获取邮箱配置信息 - single
         /// </summary>
         /// <returns></returns>
         public async Task<HotmailConfig?> GetUserMailConfig(int userId) 
+        {
+            var allConfigs = await GetUserMailConfigListAsync();
+
+            if (allConfigs == null || !allConfigs.Any()) return null;
+
+            var userConfig = allConfigs.FirstOrDefault(x => x.UserId == userId);
+
+            return userConfig;
+        }
+
+        /// <summary>
+        /// 获取邮箱配置信息 - ALL
+        /// </summary>
+        /// <returns></returns>
+        public async Task<List<HotmailConfig>?> GetUserMailConfigListAsync()
         {
             var remark = await _sqlSugar.Queryable<Sys_SetData>()
                 .Where(x => x.IsDel == 0 && x.Id == 1555 && x.STid == 137)
@@ -214,8 +228,7 @@ namespace OASystem.API.OAMethodLib.Hotmail
             try
             {
                 var allConfigs = JsonConvert.DeserializeObject<List<HotmailConfig>>(remark);
-                var userConfig = allConfigs?.FirstOrDefault(x => x.UserId == userId);
-                return userConfig;
+                return allConfigs;
             }
             catch (Exception)
             {
@@ -273,9 +286,6 @@ namespace OASystem.API.OAMethodLib.Hotmail
             return newToken;
         }
 
-
-       
-
         /// <summary>
         /// 静态 Token 提供者辅助类
         /// </summary>
@@ -353,15 +363,52 @@ namespace OASystem.API.OAMethodLib.Hotmail
             public DateTime ExpiresAt { get; set; }
         }
 
+        /// <summary>
+        /// 邮件请求对象
+        /// </summary>
         public class MailDto
         {
-            [JsonPropertyName("messageId")] public string? MessageId { get; set; }
-            [JsonPropertyName("subject")] public string? Subject { get; set; }
-            [JsonPropertyName("from")] public string? From { get; set; }
-            [JsonPropertyName("to")] public string? To { get; set; }
-            [JsonPropertyName("content")] public string? Content { get; set; }
-            [JsonPropertyName("receivedTime")] public DateTimeOffset? ReceivedTime { get; set; }
-            [JsonPropertyName("source")] public string? Source { get; set; }
+            /// <summary>
+            /// 邮件唯一标识符 (UID/Message-ID)
+            /// </summary>
+            [JsonPropertyName("messageId")]
+            public string? MessageId { get; set; }
+
+            /// <summary>
+            /// 邮件主题
+            /// </summary>
+            [JsonPropertyName("subject")]
+            public string? Subject { get; set; }
+
+            /// <summary>
+            /// 发件人地址 (e.g. "sender@example.com")
+            /// </summary>
+            [JsonPropertyName("from")]
+            public string? From { get; set; }
+
+            /// <summary>
+            /// 收件人地址
+            /// </summary>
+            [JsonPropertyName("to")]
+            public string? To { get; set; }
+
+            /// <summary>
+            /// 邮件正文内容 (HTML 或纯文本)
+            /// </summary>
+            [JsonPropertyName("content")]
+            public string? Content { get; set; }
+
+            /// <summary>
+            /// 接收时间 - 使用 DateTimeOffset 以确保跨时区准确性
+            /// </summary>
+            [JsonPropertyName("receivedTime")]
+            public DateTimeOffset? ReceivedTime { get; set; }
+
+            /// <summary>
+            /// 数据来源标识 (用于区分不同配置源或采集渠道,如 "Hotmail", "Gmail", "Sys_SetData")
+            /// </summary>
+            [JsonPropertyName("source")]
+            public string? Source { get; set; } = "Hotmail";
         }
         #endregion
     }

+ 186 - 0
OASystem/OASystem.Api/OAMethodLib/Quartz/Business/ProcessAndNotifySummary.cs

@@ -0,0 +1,186 @@
+using OASystem.API.OAMethodLib.DeepSeekAPI;
+using OASystem.API.OAMethodLib.Hotmail;
+using OASystem.API.OAMethodLib.QiYeWeChatAPI;
+using OASystem.Domain.ViewModels.QiYeWeChat;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Web;
+using static OASystem.API.OAMethodLib.Hotmail.HotmailService;
+
+namespace OASystem.API.OAMethodLib.Quartz.Business
+{
+    /// <summary>
+    /// 获取hotmail邮件,自动总结邮件内容发送至企业微信邮件
+    /// </summary>
+    public static class ProcessAndNotifySummary
+    {
+        private static readonly SqlSugarClient _sqlSugar = AutofacIocManager.Instance.GetService<SqlSugarClient>();
+        private static readonly IQiYeWeChatApiService _qiYeWeChatApiService = AutofacIocManager.Instance.GetService<IQiYeWeChatApiService>();
+        private static readonly HotmailService _hotmailService = AutofacIocManager.Instance.GetService<HotmailService>();
+        private static readonly IDeepSeekService _deepSeekService = AutofacIocManager.Instance.GetService<IDeepSeekService>();
+
+        /// <summary>
+        /// hotmail 邮件 汇总 发送企微邮件
+        /// 时间范围 昨天
+        /// </summary>
+        public static async void ProcessAndNotifySummaryAsync()
+        {
+            var hotmailConfigs = await _hotmailService.GetUserMailConfigListAsync();
+            if (hotmailConfigs == null || !hotmailConfigs.Any()) return;
+
+            var hotmails = hotmailConfigs.Select(x => x.UserName).ToList();
+            var userIds = hotmailConfigs.Select(x => x.UserId).ToList();
+
+            var cstZone = CommonFun.GetCstZone();
+            var nowInCst = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cstZone);
+            var yesterdayStart = nowInCst.Date.AddDays(-1);
+            var yesterdayEnd = yesterdayStart.AddDays(3).AddTicks(-1);
+
+            var emailInfos = await _hotmailService.GetMergedMessagesAsync(hotmails, yesterdayStart, yesterdayEnd);
+
+            // 处理无邮件情况
+            if (emailInfos == null || !emailInfos.Any())
+            {
+                await NotifyEmptyEmails(userIds);
+                return;
+            }
+
+            // 1. 预处理:限制每封邮件正文长度,防止 Token 溢出
+            foreach (var mail in emailInfos)
+            {
+                mail.Content = CleanHtmlToPlainText(mail.Content);
+            }
+
+            // 2. 调用 AI
+            var question = BuildMailSummaryPrompt(emailInfos);
+            var res = await _deepSeekService.ChatAsync(question);
+
+            if (res.Success)
+            {
+                // 清洗 AI 可能带出的 Markdown 格式符
+                string cleanJson = res.Answer.Trim();
+                if (cleanJson.StartsWith("```json")) cleanJson = cleanJson.Replace("```json", "").Replace("```", "").Trim();
+
+                try
+                {
+                    var aiSummaryResults = JsonConvert.DeserializeObject<List<AiSummaryResult>>(cleanJson);
+                    var users = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && userIds.Contains(x.Id)).Select(x => new { x.Id, x.Email }).ToListAsync();
+
+                    foreach (var hotmailConfig in hotmailConfigs)
+                    {
+                        var qwEmail = users.FirstOrDefault(x => x.Id == hotmailConfig.UserId)?.Email;
+                        if (string.IsNullOrEmpty(qwEmail)) continue;
+
+                        if (hotmailConfig.UserName.Equals("925554512@qq.com"))
+                        {
+                            hotmailConfig.UserName = "Roy.Lei.Atom@hotmail.com";
+                        }
+
+                        // 获取 AI 为该账号生成的摘要
+                        var summary = aiSummaryResults?.FirstOrDefault(x => x.Recipient.Equals(hotmailConfig.UserName, StringComparison.OrdinalIgnoreCase));
+
+                        string finalSubject = $"{DateTime.Now:yyyy-MM-dd} - 邮件汇总";
+                        string finalBody = "AI 未能成功生成今日摘要。";
+
+                        if (summary != null)
+                        {
+                            finalSubject = $"[AI摘要] {summary.EmailSubject}";
+                            finalBody = summary.HtmlBody;
+                        }
+
+                        await _qiYeWeChatApiService.EmailSendAsync(new EmailRequestDto
+                        {
+                            ToEmails = new List<string> { qwEmail },
+                            Subject = finalSubject,
+                            Body = finalBody,
+                        });
+                    }
+                }
+                catch (Exception ex)
+                {
+                    // 记录解析 JSON 失败日志
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// 纯正则实现:剔除 HTML 标签、样式和脚本,保留核心文本
+        /// </summary>
+        private static string CleanHtmlToPlainText(string? html)
+        {
+            if (string.IsNullOrEmpty(html)) return string.Empty;
+
+            // 1. 剔除脚本 (Script) 和 样式 (Style) 及其内部内容
+            html = Regex.Replace(html, @"<(script|style)[^>]*?>.*?</\1>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);
+
+            // 2. 剔除所有 HTML 标签
+            html = Regex.Replace(html, @"<[^>]*>", " ");
+
+            // 3. 将 HTML 实体字符转换回普通字符 (例如 &nbsp; -> 空格, &lt; -> <)
+            html = HttpUtility.HtmlDecode(html);
+
+            // 4. 清理多余的空白字符和重复换行
+            html = Regex.Replace(html, @"\s+", " "); // 将多个空格/换行合并为一个空格
+            html = Regex.Replace(html, @"(\n\s*){2,}", "\n"); // 压缩重复换行
+
+            return html.Trim();
+        }
+
+
+        public static string BuildMailSummaryPrompt(List<MailDto> mailList)
+        {
+            var rawDataJson = System.Text.Json.JsonSerializer.Serialize(mailList, new JsonSerializerOptions { WriteIndented = false });
+
+            return $@"
+# Role: 高级邮件情报官 (Senior Email Intelligence Officer)
+
+## Task
+分析提供的 {mailList.Count} 封原始邮件,排除垃圾内容,并为每个收件人生成 HTML 格式的每日摘要报告。
+
+## Rule & Logic
+1. **Filtering**: 严禁包含:自动订阅、社交媒体动态、验证码、退订链接邮件。
+2. **Analysis**: 对同一收件人的邮件按业务逻辑归类(如:财务类、技术类、会议类)。
+3. **HTML Requirements**: 
+   - 必须使用 `border-collapse: collapse; width: 100%;` 的表格。
+   - 主题色使用蓝色 (#2563eb)。
+   - 包含三部分:【核心概览】、【详情表格 (列: 时间, 主题, 来源, 摘要)】、【待办建议 (Action Items)】。
+   - 所有 CSS 必须 Inline Style。
+
+## HTML Format (Strict)
+- 仅允许使用: <h3> (标题), <p> (段落), <ul>/<li> (列表), <strong> (加粗), <hr> (分割线)。
+- 禁止使用: <table>, <div>, <span>, class, id。
+- 样式: 仅在 <h3> 中使用 style=""color:#2563eb""。
+
+## Constraints
+- 输出必须是合法的 JSON 数组,严禁任何前言或后缀文字。
+- 属性名:Recipient, EmailSubject, HtmlBody。
+- HtmlBody 必须是完整的 HTML 字符串,注意 JSON 内部引号转义。
+
+## Input Data (JSON)
+{rawDataJson}
+";
+        }
+
+        private static async Task NotifyEmptyEmails(List<int> userIds)
+        {
+            var userEmails = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && userIds.Contains(x.Id)).Select(x => x.Email).ToListAsync();
+            if (userEmails.Any())
+            {
+                await _qiYeWeChatApiService.EmailSendAsync(new EmailRequestDto
+                {
+                    ToEmails = userEmails,
+                    Subject = $"{DateTime.Now:yyyy-MM-dd} - 邮件汇总",
+                    Body = "昨日暂未收到有效邮件。"
+                });
+            }
+        }
+
+        public class AiSummaryResult
+        {
+            public string Recipient { get; set; } = string.Empty;
+            public string EmailSubject { get; set; } = string.Empty;
+            public string HtmlBody { get; set; } = string.Empty;
+        }
+    }
+}

+ 26 - 0
OASystem/OASystem.Api/OAMethodLib/Quartz/Jobs/ProcessAndNotifySummaryJob.cs

@@ -0,0 +1,26 @@
+using OASystem.API.OAMethodLib.Quartz.Business;
+using Quartz;
+using QuzrtzJob.Factory;
+
+namespace OASystem.API.OAMethodLib.Quartz.Jobs
+{
+    /// <summary>
+    /// hotmail邮件汇总 发送
+    /// </summary>
+    public class ProcessAndNotifySummaryJob : IJob
+    {
+        private readonly ILogger<ProcessAndNotifySummaryJob> _logger;
+        public ProcessAndNotifySummaryJob(ILogger<ProcessAndNotifySummaryJob> logger)
+        {
+            _logger = logger;
+        }
+
+        public Task Execute(IJobExecutionContext context)
+        {
+            _logger.LogInformation("hotmail汇总发送邮件 " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
+            //在此处编写任务业务代码
+            ProcessAndNotifySummary.ProcessAndNotifySummaryAsync();
+            return Task.CompletedTask;
+        }
+    }
+}

+ 8 - 0
OASystem/OASystem.Api/OAMethodLib/Quartz/QuartzFactory.cs

@@ -50,17 +50,23 @@ namespace QuzrtzJob.Factory
                 .WithCronSchedule("0 0 5 1 * ?") // 每月1号5点执行
                 .Build();
 
+            var hotmailSummaryTrigger = TriggerBuilder.Create()
+                .WithCronSchedule("0 0 8 * * ?") // 每月1号5点执行
+                .Build();
+
             //5、创建任务
             var jobDetail = JobBuilder.Create<ALiYunPostMessageJob>().WithIdentity("job1", "group").Build();
             var taskJobDetail = JobBuilder.Create<TaskJob>().WithIdentity("job2", "group").Build();
             var taskMsgJobDetail = JobBuilder.Create<TaskNewsFeedJob>().WithIdentity("job3", "group").Build();
             var performanceJobDetail = JobBuilder.Create<PerformanceJob>().WithIdentity("job5", "group").Build();
+            var hotmailSummaryJobDetail = JobBuilder.Create<ProcessAndNotifySummaryJob>().WithIdentity("job8", "group").Build();
 
             //6、将触发器和任务器绑定到调度器中
             await _scheduler.ScheduleJob(jobDetail, trigger);
             await _scheduler.ScheduleJob(taskJobDetail, taskTrigger);
             await _scheduler.ScheduleJob(taskMsgJobDetail, taskMsgTrigger);
             await _scheduler.ScheduleJob(performanceJobDetail, performanceTrigger);
+            await _scheduler.ScheduleJob(hotmailSummaryJobDetail, hotmailSummaryTrigger);
 
             // 币种信息 每天 凌晨零点更新
             await CreateAndScheduleJob<GroupTeamCurrencyJob>("job4", "group", CreateTrigger("0 0 0 * * ?"));
@@ -71,6 +77,8 @@ namespace QuzrtzJob.Factory
             // 每周五下午4点执行
             await CreateAndScheduleJob<WeeklyFridayJob>("job7", "group", CreateTrigger("0 0 16 ? * FRI"));
 
+            // hotmail 邮件汇总 每天早上八点
+
             return await Task.FromResult("将触发器和任务器绑定到调度器中完成");
         }
 

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

@@ -30,7 +30,6 @@ using TencentCloud.Common.Profile;
 using TencentCloud.Hunyuan.V20230901;
 using static OASystem.API.Middlewares.RateLimitMiddleware;
 using OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
-using OASystem.API.OAMethodLib.HotmailEmail;
 
 Console.Title = $"FMGJ OASystem Server";
 var builder = WebApplication.CreateBuilder(args);