Просмотр исходного кода

Merge branch 'develop' of http://132.232.92.186:3000/XinXiBu/OA2023 into develop

yuanrf 1 месяц назад
Родитель
Сommit
3e9a478a5d

+ 134 - 0
OASystem/OASystem.Api/Controllers/AITestController.cs

@@ -0,0 +1,134 @@
+using Microsoft.AspNetCore.Mvc;
+using OASystem.API.OAMethodLib.HunYuanAPI;
+using OASystem.API.OAMethodLib.QiYeWeChatAPI;
+using OASystem.Domain.ViewModels.QiYeWeChat;
+using TencentCloud.Hunyuan.V20230901.Models;
+
+namespace OASystem.API.Controllers
+{
+    /// <summary>
+    /// AI测试控制器
+    /// </summary>
+    [Route("api/[controller]")]
+    public class AITestController : ControllerBase
+    {
+        private readonly IHunyuanService _hunyuanService;
+        private readonly ILogger<AITestController> _logger;
+        private readonly IQiYeWeChatApiService _qiYeWeChatApiService;
+
+        public AITestController(IHunyuanService hunyuanService, ILogger<AITestController> logger, IQiYeWeChatApiService qiYeWeChatApiService)
+        {
+            _hunyuanService = hunyuanService;
+            _logger = logger;
+            _qiYeWeChatApiService = qiYeWeChatApiService;
+        }
+
+        #region 企业微信发送邮件测试
+
+        /// <summary>
+        /// 企业微信发送邮件测试
+        /// </summary>
+        [HttpPost("sendEmail")]
+        public async Task<ActionResult<string>> SendEmail([FromForm] IFormFile[] feils)
+        {
+            try
+            {
+                var req = new EmailRequestDto() {
+                    ToEmails = new List<string> { "johnny.yang@pan-american-intl.com" },
+                    CcEmails = new List<string> { "Roy.lei@pan-american-intl.com" },
+                    BccEmails = new List<string> { "Roy.lei@pan-american-intl.com" },
+                    Subject = "测试邮件 - 来自企业微信API",
+                    Body = "这是一封通过企业微信API发送的测试邮件,包含附件。",
+                    Files = feils
+                };
+                var response = await _qiYeWeChatApiService.EmailSendAsync(req);
+                return Ok(response);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "调用企业微信邮件API失败。");
+                return StatusCode(500, new { Message = "调用企业微信邮件API失败,请检查配置或网络。", Detail = ex.Message });
+            }
+        }
+        #endregion
+
+        #region 混元 AI
+        /// <summary>
+        /// 基础对话示例
+        /// </summary>
+        [HttpPost("chat")]
+        public async Task<ActionResult<string>> BasicChat(string question)
+        {
+            try
+            {
+                var response = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(question);
+                return Ok(response);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "调用腾讯云混元API失败。");
+                return StatusCode(500, new { Message = "调用腾讯云API失败,请检查配置或网络。", Detail = ex.Message });
+            }
+        }
+
+        /// <summary>
+        /// 模拟“根据文件提问”的API端点
+        /// 注意:此示例中,文件内容通过请求体传入。
+        /// 实际场景中,文件内容可能来自用户上传并解析(如PDF、TXT解析为文本)后的结果。
+        /// </summary>
+        [HttpPost("ask-with-file")]
+        public async Task<ActionResult<string>> AskBasedOnFile([FromBody] AskWithFileRequest request)
+        {
+            if (string.IsNullOrEmpty(request.FileContent) || string.IsNullOrEmpty(request.Question))
+            {
+                return BadRequest(new { Message = "FileContent和Question字段不能为空。" });
+            }
+
+            try
+            {
+                var answer = await _hunyuanService.AskWithFileContextAsync(request.FileContent, request.Question, request.Model);
+                return Ok(answer);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "处理基于文件的提问失败。");
+                return StatusCode(500, new { Message = "处理请求失败。", Detail = ex.Message });
+            }
+        }
+
+        /// <summary>
+        /// 用于测试的GET端点,快速验证服务可用性(使用示例数据)
+        /// </summary>
+        [HttpGet("test-file-query")]
+        public async Task<ActionResult<string>> TestFileQuery()
+        {
+            // 示例文件内容和问题
+            var sampleFileContent = "在软件开发中,依赖注入(Dependency Injection)是一种设计模式,用于实现控制反转(Inversion of Control, IoC)。它允许在类外部创建依赖对象,并通过构造函数、属性或方法将其‘注入’到类中,从而降低类之间的耦合度。";
+            var sampleQuestion = "依赖注入的主要目的是什么?";
+            var model = "hunyuan-lite"; // 可使用 "hunyuan-pro" 等
+
+            try
+            {
+                var answer = await _hunyuanService.AskWithFileContextAsync(sampleFileContent, sampleQuestion, model);
+                return Ok($"测试成功。问题:'{sampleQuestion}'\n回答:{answer}");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "测试文件提问失败。");
+                return StatusCode(500, new { Message = "测试失败。", Detail = ex.Message });
+            }
+        }
+
+        /// <summary>
+        /// 用于“根据文件提问”的请求体
+        /// </summary>
+        public class AskWithFileRequest
+        {
+            public string FileContent { get; set; } = string.Empty;
+            public string Question { get; set; } = string.Empty;
+            public string Model { get; set; } = "hunyuan-lite";
+        }
+        #endregion
+
+    }
+}

+ 2 - 3
OASystem/OASystem.Api/Controllers/AuthController.cs

@@ -29,7 +29,7 @@ namespace OASystem.API.Controllers
         private readonly MessageRepository _message;
         private readonly SystemMenuPermissionRepository _sysMenuPermRep;
         private readonly MessageRepository _messageRep;
-        private readonly IQiYeWeChatApiService _qiYeWeChatApiServic;
+        private readonly IQiYeWeChatApiService _qiYeWeChatApiService;
         private readonly IHubContext<ChatHub, IChatClient> _hubContext;
         private readonly DeviceTokenRepository _deviceTokenRep;
 
@@ -62,7 +62,7 @@ namespace OASystem.API.Controllers
             _mapper = mapper;
             _message = message;
             _sysMenuPermRep = sysMenuPermRep;
-            _qiYeWeChatApiServic = qiYeWeChatApiService;
+            _qiYeWeChatApiService = qiYeWeChatApiService;
             _messageRep = messageRep;
             _deviceTokenRep = deviceRep;
             _hubContext = hubContext;
@@ -289,7 +289,6 @@ namespace OASystem.API.Controllers
             return Ok(JsonView(view));
         }
 
-
         /// <summary>
         /// 申请注册 数据Data
         /// </summary>

+ 7 - 0
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/DeepSeekModels.cs

@@ -168,6 +168,13 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
         [JsonPropertyName("messages")]
         public List<FileMessage> Messages { get; set; }
 
+        /// <summary>
+        /// SSE的形式以流式发送消息增量
+        /// 如果设置为 True,将会以 SSE(server-sent events)的形式以流式发送消息增量。消息流以 data: [DONE] 结尾。
+        /// </summary>
+        [JsonPropertyName("stream")]
+        public bool Stream { get; set; } = false;
+
         /// <summary>
         /// 温度参数
         /// </summary>

+ 8 - 1
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/DeepSeekService.cs

@@ -322,7 +322,13 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
         /// <summary>
         /// 使用进行聊天
         /// </summary>
-        public async Task<ApiResponse> ChatAsync(string question, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000)
+        /// <param name="question"></param>
+        /// <param name="model"></param>
+        /// <param name="stream"></param>
+        /// <param name="temperature"></param>
+        /// <param name="maxTokens"></param>
+        /// <returns></returns>
+        public async Task<ApiResponse> ChatAsync(string question, bool stream = false, string model = "deepseek-chat",  float temperature = 0.7f, int maxTokens = 4000)
         {
             try
             {
@@ -343,6 +349,7 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
                             Content = messageContent
                         }
                     },
+                    Stream = stream,
                     Temperature = temperature,
                     MaxTokens = maxTokens
                 };

+ 3 - 2
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/IDeepSeekService.cs

@@ -65,14 +65,15 @@
         Task<ApiResponse> ChatWithFilesAsync(List<string> fileIds, string question, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000);
 
         /// <summary>
-        /// 使用已上传的文件进行聊天
+        /// chat接口 - 直接提问(不使用文件上下文)
         /// </summary>
         /// <param name="question">问题</param>
         /// <param name="model">模型名称</param>
+        /// <param name="stream">是否流式输出</param>
         /// <param name="temperature">温度参数</param>
         /// <param name="maxTokens">最大token数</param>
         /// <returns>聊天响应</returns>
-        Task<ApiResponse> ChatAsync(string question, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000);
+        Task<ApiResponse> ChatAsync(string question, bool stream = false, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000);
 
         /// <summary>
         /// 等待文件处理完成

+ 96 - 0
OASystem/OASystem.Api/OAMethodLib/HunYuanAPI/HunyuanService.cs

@@ -0,0 +1,96 @@
+using TencentCloud.Hunyuan.V20230901;
+using TencentCloud.Hunyuan.V20230901.Models;
+
+namespace OASystem.API.OAMethodLib.HunYuanAPI
+{
+    public class HunyuanService : IHunyuanService
+    {
+        private readonly HunyuanClient _hunyuanClient;
+
+        /// <summary>
+        /// 构造函数注入配置好的HunyuanClient
+        /// </summary>
+        /// <param name="hunyuanClient"></param>
+        public HunyuanService(HunyuanClient hunyuanClient)
+        {
+            _hunyuanClient = hunyuanClient;
+        }
+
+        /// <inheritdoc />
+        public async Task<string> ChatCompletionsHunyuan_t1_latestAsync(string question)
+        {
+            var request = new ChatCompletionsRequest
+            {
+                Model = "hunyuan-t1-latest",
+                Messages = new Message[]
+                {
+                    new Message
+                    {
+                        Role = "user",
+                        Content = question
+                    }
+                },
+                Stream = false,
+                Temperature = 0.5f,
+                TopP = 1.0f,
+                // 其他参数根据需要设置
+            };
+
+            // 直接调用SDK方法
+            var response = await _hunyuanClient.ChatCompletions(request);
+
+            // 提取并返回模型生成的回答
+            // 注意:响应结构可能包含多个Choice,这里取第一个。
+            if (response.Choices != null && response.Choices.Length > 0)
+            {
+                return response.Choices[0].Message.Content?.Trim() ?? "模型未返回内容。";
+            }
+
+            return "模型未生成有效回答。";
+        }
+
+        public async Task<ChatCompletionsResponse> ChatCompletionsAsync(ChatCompletionsRequest request)
+        {
+            // 直接调用SDK方法
+            return await _hunyuanClient.ChatCompletions(request);
+        }
+
+        /// <inheritdoc />
+        public async Task<string> AskWithFileContextAsync(string fileContent, string question, string model = "hunyuan-lite")
+        {
+            // 1. 构建提示词:将文件内容作为上下文,与用户问题结合。
+            // 这是一个简单示例,实际可根据需求设计更复杂的Prompt。
+            string prompt = $"请根据以下文本内容回答问题。\n文本内容:{fileContent}\n问题:{question}";
+
+            // 2. 使用SDK自带实体构建请求
+            var request = new ChatCompletionsRequest
+            {
+                Model = model,
+                Messages = new Message[]
+                {
+                    new Message
+                    {
+                        Role = "user",
+                        Content = prompt
+                    }
+                },
+                // 可根据需要设置其他参数,如Stream, Temperature, TopP等
+                // Stream = false,
+                // Temperature = 0.5f,
+                // TopP = 1.0f,
+            };
+
+            // 3. 调用SDK方法
+            var response = await ChatCompletionsAsync(request);
+
+            // 4. 提取并返回模型生成的回答
+            // 注意:响应结构可能包含多个Choice,这里取第一个。
+            if (response.Choices != null && response.Choices.Length > 0)
+            {
+                return response.Choices[0].Message.Content?.Trim() ?? "模型未返回内容。";
+            }
+
+            return "模型未生成有效回答。";
+        }
+    }
+}

+ 35 - 0
OASystem/OASystem.Api/OAMethodLib/HunYuanAPI/IHunyuanService.cs

@@ -0,0 +1,35 @@
+using TencentCloud.Hunyuan.V20230901.Models;
+
+namespace OASystem.API.OAMethodLib.HunYuanAPI
+{
+    /// <summary>
+    /// 腾讯云混元大模型服务接口
+    /// </summary>
+    public interface IHunyuanService
+    {
+        /// <summary>
+        /// 发送聊天补全请求 - 使用"hunyuan-t1-latest"模型(基础对话)
+        /// </summary>
+        /// <param name="question">问题</param>
+        /// <returns>回答的具体信息</returns>
+        Task<string> ChatCompletionsHunyuan_t1_latestAsync(string question);
+
+        /// <summary>
+        /// 发送聊天补全请求(基础对话)
+        /// </summary>
+        /// <param name="request">问题</param>
+        /// <returns>SDK自带的响应实体</returns>
+        Task<ChatCompletionsResponse> ChatCompletionsAsync(ChatCompletionsRequest request);
+
+        /// <summary>
+        /// 模拟“根据文件内容提问”的流程
+        /// 注意:此方法假设您已通过其他方式(如上传、解析)获取了文件文本内容。
+        /// 本方法仅负责将文件内容作为上下文与问题拼接后,调用大模型。
+        /// </summary>
+        /// <param name="fileContent">已读取的文件文本内容</param>
+        /// <param name="question">针对文件内容提出的问题</param>
+        /// <param name="model">模型名称,默认使用"hunyuan-lite"</param>
+        /// <returns>大模型生成的回答</returns>
+        Task<string> AskWithFileContextAsync(string fileContent, string question, string model = "hunyuan-lite");
+    }
+}

+ 7 - 0
OASystem/OASystem.Api/OAMethodLib/QiYeWeChatAPI/IQiYeWeChatApiService.cs

@@ -40,6 +40,13 @@ namespace OASystem.API.OAMethodLib.QiYeWeChatAPI
         /// <returns></returns>
         Task<QYWX_UserInfosView> GetUserInfosAsync();
 
+        /// <summary>
+        /// 发送邮件
+        /// </summary>
+        /// <param name="dto">发送邮件的请求对象</param>
+        /// <returns></returns>
+        Task<EmailResult> EmailSendAsync(EmailRequestDto dto);
+
         /// <summary>
         /// 获取企业所有打卡规则
         /// </summary>

+ 138 - 7
OASystem/OASystem.Api/OAMethodLib/QiYeWeChatAPI/QiYeWeChatApiService.cs

@@ -1,7 +1,8 @@
-using EyeSoft.Extensions;
+using EyeSoft.Extensions;
 using OASystem.Domain.Dtos.QiYeWeChat;
 using OASystem.Domain.ViewModels.QiYeWeChat;
 using System.Diagnostics;
+using System.Net.Mail;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
@@ -19,12 +20,30 @@ namespace OASystem.API.OAMethodLib.QiYeWeChatAPI
         //private readonly string Email_AgentId = "1000004";   //E-Mail Id
         //private readonly string Approve_AgentId = "3010040";     //审批 Id
         //private readonly string GroupStatus_AgentId = "1000008"; //OA通知 Id
-        private readonly string PersonnelAssistant_Corpsecret = "ig--IJd6TxWDMJ1wT4e-RDRcRX12v5GjB359DNATwJ4"; //人事助手凭证密钥
-        private readonly string PunchCard_Corpsecret = "Xhrl37GOqlAjsu0VzUSJECaJdjzkDXQLbvrzRsZQb8M";          //打卡凭证密钥
-        private readonly string Email_Corpsecret = "NA1zbJM15GmgjPYwDOqz59dIo1Wnug-MbU107MeUemc"; //E-Mail 凭证密钥
-        private readonly string AddressBook_Corpsecret = "Y1tnjh7j-BvbqAytAoXZPUbmDR6dqLTL6mXtc6PZ7fo";  //通讯录同步 凭证密钥
-        private readonly string Approve_Corpsecret = "k_Jo69Jw9Hqg_in-Rypbs30PNbxOYa1t4e-dxYuT-kw";  //审批 凭证密钥
-        private readonly string GroupStatus_Corpsecret = "7J_ST3jTPzbZpFwl7ttToTVufjEx6O2wuApvKHxt2Ak"; //OA通知Secret
+        /// <summary>
+        /// 人事助手凭证密钥
+        /// </summary>
+        private readonly string PersonnelAssistant_Corpsecret = "ig--IJd6TxWDMJ1wT4e-RDRcRX12v5GjB359DNATwJ4"; // 人事助手凭证密钥
+        /// <summary>
+        /// 打卡凭证密钥
+        /// </summary>
+        private readonly string PunchCard_Corpsecret = "Xhrl37GOqlAjsu0VzUSJECaJdjzkDXQLbvrzRsZQb8M";          // 打卡凭证密钥
+        /// <summary>
+        /// E-Mail 凭证密钥
+        /// </summary>
+        private readonly string Email_Corpsecret = "NA1zbJM15GmgjPYwDOqz59dIo1Wnug-MbU107MeUemc";              // E-Mail 凭证密钥
+        /// <summary>
+        /// 通讯录同步 凭证密钥
+        /// </summary>
+        private readonly string AddressBook_Corpsecret = "Y1tnjh7j-BvbqAytAoXZPUbmDR6dqLTL6mXtc6PZ7fo";        // 通讯录同步 凭证密钥
+        /// <summary>
+        /// 审批 凭证密钥
+        /// </summary>
+        private readonly string Approve_Corpsecret = "k_Jo69Jw9Hqg_in-Rypbs30PNbxOYa1t4e-dxYuT-kw";            // 审批 凭证密钥
+        /// <summary>
+        /// OA通知Secret
+        /// </summary>
+        private readonly string GroupStatus_Corpsecret = "7J_ST3jTPzbZpFwl7ttToTVufjEx6O2wuApvKHxt2Ak";        // OA通知Secret
         private readonly string Journal_Corpsecret = "l2lZbyLfyickVLdgi2bwVTevE-0UuZWpbNPrCbNhst0"; //汇报 凭证密钥
 
         private readonly DateTime _1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);
@@ -367,6 +386,118 @@ namespace OASystem.API.OAMethodLib.QiYeWeChatAPI
         }
         #endregion
 
+        #region 邮件
+
+        /// <summary>
+        /// 发送邮件
+        /// </summary>
+        /// <param name="dto">发送邮件的请求对象</param>
+        /// <returns></returns>
+        public async Task<EmailResult> EmailSendAsync(EmailRequestDto dto)
+        {
+            EmailResult emailResult = new();
+
+            // 参数验证
+            if (dto.ToEmails == null) return new EmailResult() { errcode = -1, errmsg = "收件人邮箱列表不能为空" };
+            if (string.IsNullOrEmpty(dto.Subject)) return new EmailResult() { errcode = -1, errmsg = "邮件主题不能为空" };
+            if (string.IsNullOrEmpty(dto.Body)) return new EmailResult() { errcode = -1, errmsg = "邮件内容不能为空" };
+
+            // 当前内容大小
+            long currentSize = GetUtf8ByteSize(dto.Body);
+            // 文件处理
+            var attachments = new List<Domain.ViewModels.QiYeWeChat.Attachment>();
+            var files = dto.Files;
+            if (files != null && files.Length > 0)
+            {
+                if (files.Length > 200) return new EmailResult() { errcode = -1, errmsg = "附件个数不能超过200个" };
+
+                foreach (var file in files)
+                {
+                    if (file == null || file.Length == 0)
+                    {
+                        return new EmailResult() { errcode = -1, errmsg = "文件不能为空" };
+                    }
+
+                    string base64String = string.Empty;
+                    using (var memoryStream = new MemoryStream())
+                    {
+                        await file.CopyToAsync(memoryStream);
+                        var bytes = memoryStream.ToArray();
+                        base64String = Convert.ToBase64String(bytes);
+                    }
+
+                    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file.FileName);
+                    var extension = Path.GetExtension(file.FileName);
+                    var feilName = $"{fileNameWithoutExtension}{extension}";
+
+                    currentSize += GetUtf8ByteSize(base64String);
+                    attachments.Add(new Domain.ViewModels.QiYeWeChat.Attachment()
+                    {
+                        file_name = feilName,
+                        content = base64String
+                    });
+                }
+            }
+
+            // 限制大小
+            long maxSizeBytes = 50 * 1024 * 1024; // 50MB
+
+            if (currentSize > maxSizeBytes)
+            {
+                return new EmailResult() { errcode = -1, errmsg = "邮件内容和附件总大小不能超过50MB" };
+            }
+
+            Access_TokenView access_Token = await GetTokenAsync(3);
+            if (access_Token.errcode != 0)
+            {
+                emailResult.errcode = access_Token.errcode;
+                emailResult.errmsg = string.Format("【企业微信】【获取邮件列表】【Token】【Msg】{0}", access_Token.errmsg);
+            }
+            string url = string.Format("/cgi-bin/exmail/app/compose_send?access_token={0}", access_Token.access_token);
+
+            var reqData = new
+            {
+                to = new
+                {
+                    emails = dto.ToEmails
+                },
+                cc = new
+                {
+                    emails = dto.CcEmails
+                },
+                bcc = new
+                {
+                    emails = dto.BccEmails
+                },
+                subject = dto.Subject,
+                content = dto.Body,
+                attachment_list = attachments
+            };
+
+            var reqContent = new StringContent(System.Text.Json.JsonSerializer.Serialize(reqData), Encoding.UTF8, "application/json");
+            var create_Req = await _httpClient.PostAsync(url, reqContent);
+            var stringResponse = await create_Req.Content.ReadAsStringAsync();
+            emailResult = System.Text.Json.JsonSerializer.Deserialize<EmailResult>(stringResponse,
+                new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
+            return emailResult;
+        }
+
+        /// <summary>
+        /// 计算字符串的字节大小(UTF-8编码)
+        /// </summary>
+        private static long GetUtf8ByteSize(string text)
+        {
+            if (string.IsNullOrEmpty(text))
+            {
+                return 0;
+            }
+
+            return Encoding.UTF8.GetByteCount(text);
+        }
+
+
+        #endregion
+
         #region 打卡
 
         /// <summary>

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

@@ -71,7 +71,7 @@
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
     <PackageReference Include="System.Data.OleDb" Version="8.0.0" />
     <PackageReference Include="System.Security.Permissions" Version="6.0.1" />
-    <PackageReference Include="TencentCloudSDK.Common" Version="3.0.734" />
+    <PackageReference Include="TencentCloudSDK.Hunyuan" Version="3.0.1378" />
     <PackageReference Include="TencentCloudSDK.Ocr" Version="3.0.734" />
     <PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
     <PackageReference Include="UAParser" Version="3.1.47" />

+ 161 - 115
OASystem/OASystem.Api/Program.cs

@@ -1,8 +1,10 @@
+using Microsoft.AspNetCore.DataProtection;
 using Microsoft.AspNetCore.Http.Connections;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.ResponseCompression;
 using Microsoft.AspNetCore.Server.Kestrel.Core;
 using Microsoft.Extensions.DependencyInjection.Extensions;
+using NPOI.POIFS.Crypt;
 using OASystem.API.Middlewares;
 using OASystem.API.OAMethodLib;
 using OASystem.API.OAMethodLib.AMapApi;
@@ -10,6 +12,7 @@ using OASystem.API.OAMethodLib.APNs;
 using OASystem.API.OAMethodLib.DeepSeekAPI;
 using OASystem.API.OAMethodLib.GenericSearch;
 using OASystem.API.OAMethodLib.Hub.Hubs;
+using OASystem.API.OAMethodLib.HunYuanAPI;
 using OASystem.API.OAMethodLib.JuHeAPI;
 using OASystem.API.OAMethodLib.QiYeWeChatAPI;
 using OASystem.API.OAMethodLib.Quartz.Jobs;
@@ -21,13 +24,16 @@ using Quartz.Spi;
 using QuzrtzJob.Factory;
 using Serilog.Events;
 using System.IO.Compression;
+using TencentCloud.Common;
+using TencentCloud.Common.Profile;
+using TencentCloud.Hunyuan.V20230901;
 using static OASystem.API.Middlewares.RateLimitMiddleware;
 
 Console.Title = $"FMGJ OASystem Server";
 var builder = WebApplication.CreateBuilder(args);
 var basePath = AppContext.BaseDirectory;
 
-//寮曞叆閰嶇疆鏂囦欢
+//引入配置文件
 var _config = new ConfigurationBuilder()
                  .SetBasePath(basePath)
                  .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
@@ -36,10 +42,10 @@ var _config = new ConfigurationBuilder()
                  .Build();
 builder.Services.AddSingleton(new AppSettingsHelper(_config));
 
-//设置请求参数可不填
+//设置请求参数可不填
 builder.Services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);
 
-//璁剧疆璇锋眰鍙傛暟閿欒�榛樿�杩斿洖鏍煎紡
+//设置请求参数错误默认返回格式
 builder.Services.AddControllers()
     .ConfigureApiBehaviorOptions(options =>
     {
@@ -68,26 +74,26 @@ builder.Services.AddControllersWithViews();
 builder.Services.AddControllers()
     .AddJsonOptions(options =>
     {
-        //绌哄瓧娈典笉鍝嶅簲Response
+        //空字段不响应Response
         //options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
 
         options.JsonSerializerOptions.Converters.Add(new NullJsonConverter());
 
-        //时间格式化响应
+        //时间格式化响应
         options.JsonSerializerOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss"));
 
-        //decimal 鍥涗綅灏忔暟
-        //options.JsonSerializerOptions.Converters.Add(new DecimalConverter(_decimalPlaces)); // 将保留小数位数参数传递给自定义序列化器
+        //decimal 四位小数
+        //options.JsonSerializerOptions.Converters.Add(new DecimalConverter(_decimalPlaces)); // 将保留小数位数参数传递给自定义序列化器
     });
 
 builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
 
-#region 添加限流中间件服务注册
+#region 添加限流中间件服务注册
 
-// 娣诲姞鍐呭瓨缂撳瓨锛堥檺娴侀渶瑕侊級
+// 添加内存缓存(限流需要)
 builder.Services.AddMemoryCache();
 
-// 閰嶇疆闄愭祦璁剧疆
+// 配置限流设置
 builder.Services.Configure<RateLimitConfig>(
     builder.Configuration.GetSection("RateLimiting"));
 #endregion
@@ -119,14 +125,14 @@ builder.Services.AddCors(policy =>
     policy.AddPolicy("Cors", opt => opt
             .SetIsOriginAllowed(origin =>
             {
-                // 定义允许的来源列表
+                // 定义允许的来源列表
                 var allowedOrigins = new List<string>
                     {
                        "http://132.232.92.186:9002"
 
                     };
 
-                // 检查请求的来源是否在允许的列表中
+                // 检查请求的来源是否在允许的列表中
                 return allowedOrigins.Contains(origin);
             })
 
@@ -138,7 +144,7 @@ builder.Services.AddCors(policy =>
 });
 #endregion
 
-#region 涓婁紶鏂囦欢 
+#region 上传文件 
 builder.Services.AddCors(policy =>
 {
     policy.AddPolicy("Cors", opt => opt
@@ -164,15 +170,15 @@ builder.Services.Configure<KestrelServerOptions>(options =>
 
 #endregion
 
-#region 鎺ュ彛鍒嗙粍
+#region 接口分组
 var groups = new List<Tuple<string, string>>
 {
-    //new Tuple<string, string>("Group1","鍒嗙粍涓€"),
-    //new Tuple<string, string>("Group2","分组二")
+    //new Tuple<string, string>("Group1","分组一"),
+    //new Tuple<string, string>("Group2","分组二")
 };
 #endregion
 
-#region 注入数据库
+#region 注入数据库
 
 #region old
 
@@ -195,38 +201,38 @@ builder.Services.AddScoped(options =>
     }
     , db =>
     {
-        // SQL执行完
+        // SQL执行完
         db.Aop.OnLogExecuted = (sql, pars) =>
         {
-            // 超过1秒
+            // 超过1秒
             if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
             {
                 var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
-                //鎵ц�瀹屼簡鍙�互杈撳嚭SQL鎵ц�鏃堕棿 (OnLogExecutedDelegate) 
+                //执行完了可以输出SQL执行时间 (OnLogExecutedDelegate) 
                 Console.WriteLine("NowTime:" + DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"));
                 Console.WriteLine("MethodName:" + FirstMethodName);
                 Console.WriteLine("ElapsedTime:" + db.Ado.SqlExecutionTime.ToString());
                 Console.WriteLine("ExecuteSQL:" + sql);
             }
         };
-        //SQL执行前
+        //SQL执行前
         db.Aop.OnLogExecuting = (sql, pars) =>
         {
         };
-        //SQL鎶ラ敊
+        //SQL报错
         db.Aop.OnError = (exp) =>
         {
-            //鑾峰彇鍘熺敓SQL鎺ㄨ崘 5.1.4.63  鎬ц兘OK
+            //获取原生SQL推荐 5.1.4.63  性能OK
             //UtilMethods.GetNativeSql(exp.Sql, exp.Parametres);
-            //获取无参数SQL对性能有影响,特别大的SQL参数多的,调试使用
+            //获取无参数SQL对性能有影响,特别大的SQL参数多的,调试使用
             //UtilMethods.GetSqlString(DbType.SqlServer, exp.sql, exp.parameters);
 
         };
-        //修改SQL和参数的值
+        //修改SQL和参数的值
         db.Aop.OnExecutingChangeSql = (sql, pars) =>
         {
             //sql=newsql
-            //foreach(var p in pars) //淇�敼
+            //foreach(var p in pars) //修改
             return new KeyValuePair<string, SugarParameter[]>(sql, pars);
         };
     }
@@ -237,27 +243,27 @@ builder.Services.AddScoped(options =>
 
 #endregion
 
-//#region Identity 閰嶇疆
+//#region Identity 配置
 //builder.Services.AddDataProtection();
-////不要用 AddIdentity , AddIdentity 是于MVC框架中的
+////不要用 AddIdentity , AddIdentity 是于MVC框架中的
 //builder.Services.AddIdentityCore<User>(opt =>
 //{
-//    opt.Password.RequireDigit = false; //鏁板瓧
-//    opt.Password.RequireLowercase = false;//灏忓啓瀛楁瘝
-//    opt.Password.RequireNonAlphanumeric = false;//特殊符号 例如 ¥#@! 
-//    opt.Password.RequireUppercase = false; //澶у啓瀛楁瘝
-//    opt.Password.RequiredLength = 6;//瀵嗙爜闀垮害 6 
-//    opt.Password.RequiredUniqueChars = 1;//鐩稿悓瀛楃�鍙�互鍑虹幇鍑犳�
-//    opt.Lockout.MaxFailedAccessAttempts = 5; //鍏佽�鏈€澶氳緭鍏ヤ簲娆$敤鎴峰悕/瀵嗙爜閿欒�
-//    opt.Lockout.DefaultLockoutTimeSpan = new TimeSpan(0, 5, 0);//锁定五分钟
-//    opt.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; // 修改密码使用邮件【验证码模式】
+//    opt.Password.RequireDigit = false; //数字
+//    opt.Password.RequireLowercase = false;//小写字母
+//    opt.Password.RequireNonAlphanumeric = false;//特殊符号 例如 ¥#@! 
+//    opt.Password.RequireUppercase = false; //大写字母
+//    opt.Password.RequiredLength = 6;//密码长度 6 
+//    opt.Password.RequiredUniqueChars = 1;//相同字符可以出现几次
+//    opt.Lockout.MaxFailedAccessAttempts = 5; //允许最多输入五次用户名/密码错误
+//    opt.Lockout.DefaultLockoutTimeSpan = new TimeSpan(0, 5, 0);//锁定五分钟
+//    opt.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; // 修改密码使用邮件【验证码模式】
 //    opt.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;  //// 
 //});
 //var idBuilder = new IdentityBuilder(typeof(User), typeof(UserRole), services);
 //idBuilder.AddEntityFrameworkStores<swapDbContext>().AddDefaultTokenProviders().AddRoleManager<RoleManager<UserRole>>().AddUserManager<UserManager<User>>();
 //#endregion
 
-#region 娉ㄥ叆Swagger娉ㄩ噴(鍚�敤)
+#region 注入Swagger注释(启用)
 
 if (AppSettingsHelper.Get("UseSwagger").ToBool())
 {
@@ -267,11 +273,11 @@ if (AppSettingsHelper.Get("UseSwagger").ToBool())
         {
             Version = "v1",
             Title = "Api",
-            Description = "Api鎺ュ彛鏂囨。"
+            Description = "Api接口文档"
         });
         foreach (var item in groups)
         {
-            a.SwaggerDoc(item.Item1, new OpenApiInfo { Version = item.Item1, Title = item.Item2, Description = $"{item.Item2}鎺ュ彛鏂囨。" });
+            a.SwaggerDoc(item.Item1, new OpenApiInfo { Version = item.Item1, Title = item.Item2, Description = $"{item.Item2}接口文档" });
         }
         a.DocumentFilter<SwaggerApi>();
         a.IncludeXmlComments(Path.Combine(basePath, "OASystem.Api.xml"), true);
@@ -300,50 +306,50 @@ if (AppSettingsHelper.Get("UseSwagger").ToBool())
 }
 #endregion
 
-#region 娣诲姞鏍¢獙
+#region 添加校验
 
 builder.Services.AddTransient<OASystemAuthentication>();
 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
     .AddJwtBearer(options =>
+    {
+        options.TokenValidationParameters = new TokenValidationParameters
         {
-            options.TokenValidationParameters = new TokenValidationParameters
-            {
-                ValidateIssuer = true,
-                ValidateAudience = true,
-                ValidateLifetime = true,
-                ValidateIssuerSigningKey = true,
-                ValidAudience = "OASystem.com",
-                ValidIssuer = "OASystem.com",
-                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JwtSecurityKey"])),
-                ClockSkew = TimeSpan.FromSeconds(30), //杩囨湡鏃堕棿瀹归敊鍊硷紝瑙e喅鏈嶅姟鍣ㄧ�鏃堕棿涓嶅悓姝ラ棶棰橈紙绉掞級
-                RequireExpirationTime = true,
-            };
-            options.Events = new JwtBearerEvents
+            ValidateIssuer = true,
+            ValidateAudience = true,
+            ValidateLifetime = true,
+            ValidateIssuerSigningKey = true,
+            ValidAudience = "OASystem.com",
+            ValidIssuer = "OASystem.com",
+            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JwtSecurityKey"])),
+            ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
+            RequireExpirationTime = true,
+        };
+        options.Events = new JwtBearerEvents
+        {
+            OnMessageReceived = context =>
             {
-                OnMessageReceived = context =>
+                var path = context.HttpContext.Request.Path;
+                //如果是signalr请求,需要将token转存,否则JWT获取不到token。OPTIONS请求需要过滤到,因为OPTIONS请求获取不到Token,用NGINX过滤掉OPTION请求.
+                if (path.StartsWithSegments("/ChatHub"))
                 {
-                    var path = context.HttpContext.Request.Path;
-                    //濡傛灉鏄痵ignalr璇锋眰锛岄渶瑕佸皢token杞�瓨锛屽惁鍒橨WT鑾峰彇涓嶅埌token銆侽PTIONS璇锋眰闇€瑕佽繃婊ゅ埌锛屽洜涓篛PTIONS璇锋眰鑾峰彇涓嶅埌Token锛岀敤NGINX杩囨护鎺塐PTION璇锋眰.
-                    if (path.StartsWithSegments("/ChatHub"))
+                    string accessToken = context.Request.Query["access_token"].ToString();
+                    if (string.IsNullOrWhiteSpace(accessToken))
                     {
-                        string accessToken = context.Request.Query["access_token"].ToString();
-                        if (string.IsNullOrWhiteSpace(accessToken))
-                        {
-                            accessToken = context.Request.Headers["Authorization"].ToString();
-                        }
-                        context.Token = accessToken.Replace("Bearer ", "").Trim();
+                        accessToken = context.Request.Headers["Authorization"].ToString();
                     }
-                    return Task.CompletedTask;
+                    context.Token = accessToken.Replace("Bearer ", "").Trim();
                 }
-            };
-        });
+                return Task.CompletedTask;
+            }
+        };
+    });
 
 #endregion
 
-#region 初始化日志
+#region 初始化日志
 var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
 Log.Logger = new LoggerConfiguration()
-        //涓嶈�褰曞畾鏃惰�闂瓵PI
+        //不记录定时访问API
         .Filter.ByIncludingOnly(logEvent =>
         {
             if (logEvent.Properties.TryGetValue("RequestPath", out var pathValue))
@@ -363,20 +369,20 @@ Log.Logger = new LoggerConfiguration()
 
 // 
 
-#region 出入境费用明细 专用记录器
+#region 出入境费用明细 专用记录器
 
-// 鎸囧畾纾佺洏缁濆�璺�緞锛堢ず渚嬶細D鐩樼殑AppLogs鏂囦欢澶癸級
+// 指定磁盘绝对路径(示例:D盘的AppLogs文件夹)
 var logDirectory = @"D:\OASystem\Logs\EnterExitCost";
 
-// 自动创建目录(如果不存在)
+// 自动创建目录(如果不存在)
 try
 {
     Directory.CreateDirectory(logDirectory);
-    Log.Information($"日志目录已创建/确认存在: {logDirectory}");
+    Log.Information($"日志目录已创建/确认存在: {logDirectory}");
 }
 catch (Exception ex)
 {
-    Log.Fatal($"鏃犳硶鍒涘缓鏃ュ織鐩�綍 {logDirectory}: {ex.Message}");
+    Log.Fatal($"无法创建日志目录 {logDirectory}: {ex.Message}");
     throw;
 }
 
@@ -387,20 +393,20 @@ var eec_TextLogger = new LoggerConfiguration()
 
 #endregion
 
-#region 团组步骤操作 专用记录器
+#region 团组步骤操作 专用记录器
 
-// 鎸囧畾纾佺洏缁濆�璺�緞锛堢ず渚嬶細D鐩樼殑AppLogs鏂囦欢澶癸級
+// 指定磁盘绝对路径(示例:D盘的AppLogs文件夹)
 var groupLogDir = @"D:\OASystem\Logs\GroupStepOP";
 
-// 自动创建目录(如果不存在)
+// 自动创建目录(如果不存在)
 try
 {
     Directory.CreateDirectory(groupLogDir);
-    Log.Information($"日志目录已创建/确认存在: {groupLogDir}");
+    Log.Information($"日志目录已创建/确认存在: {groupLogDir}");
 }
 catch (Exception ex)
 {
-    Log.Fatal($"鏃犳硶鍒涘缓鏃ュ織鐩�綍 {groupLogDir}: {ex.Message}");
+    Log.Fatal($"无法创建日志目录 {groupLogDir}: {ex.Message}");
     throw;
 }
 
@@ -411,20 +417,20 @@ var groupStepOP_TextLogger = new LoggerConfiguration()
 
 #endregion
 
-#region 任务分配操作 专用记录器
+#region 任务分配操作 专用记录器
 
-// 鎸囧畾纾佺洏缁濆�璺�緞锛堢ず渚嬶細D鐩樼殑AppLogs鏂囦欢澶癸級
+// 指定磁盘绝对路径(示例:D盘的AppLogs文件夹)
 var taskLogDir = @"D:\OASystem\Logs\TaskAllocation";
 
-// 自动创建目录(如果不存在)
+// 自动创建目录(如果不存在)
 try
 {
     Directory.CreateDirectory(taskLogDir);
-    Log.Information($"日志目录已创建/确认存在: {taskLogDir}");
+    Log.Information($"日志目录已创建/确认存在: {taskLogDir}");
 }
 catch (Exception ex)
 {
-    Log.Fatal($"鏃犳硶鍒涘缓鏃ュ織鐩�綍 {taskLogDir}: {ex.Message}");
+    Log.Fatal($"无法创建日志目录 {taskLogDir}: {ex.Message}");
     throw;
 }
 
@@ -435,7 +441,7 @@ var task_TextLogger = new LoggerConfiguration()
 
 #endregion
 
-// 閰嶇疆Serilog涓篖og;
+// 配置Serilog为Log;
 builder.Host.UseSerilog();
 
 builder.Services.AddSingleton<ITextFileLogger>(new TextFileLogger(eec_TextLogger));
@@ -443,7 +449,7 @@ builder.Services.AddSingleton<IGroupTextFileLogger>(new GroupTextFileLogger(grou
 builder.Services.AddSingleton<ITaskTextFileLogger>(new TaskTextFileLogger(task_TextLogger));
 #endregion
 
-#region 寮曞叆娉ㄥ唽Autofac Module
+#region 引入注册Autofac Module
 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
 var hostBuilder = builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
 {
@@ -469,28 +475,28 @@ builder.Services.AddScoped<IMapper, Mapper>();
 
 #endregion
 
-#region DeepSeek AI 鏈嶅姟
+#region DeepSeek AI 服务
 
-// 配置HTTP客户端
+// 配置HTTP客户端
 builder.Services.AddHttpClient<IDeepSeekService, DeepSeekService>();
 
 #endregion
 
-#region Doubao API 鏈嶅姟
+#region Doubao API 服务
 var doubaoSetting = builder.Configuration.GetSection("DouBao").Get<OASystem.API.OAMethodLib.DoubaoAPI.DoubaoSetting>();
 builder.Services.AddSingleton(doubaoSetting);
 builder.Services.AddHttpClient("Doubao", c => c.BaseAddress = new Uri(doubaoSetting.BaseAddress));
 builder.Services.AddScoped<OASystem.API.OAMethodLib.DoubaoAPI.IDoubaoService, OASystem.API.OAMethodLib.DoubaoAPI.DoubaoService>();
 #endregion
 
-#region 鑱氬悎API 鏈嶅姟
+#region 聚合API 服务
 builder.Services.AddControllersWithViews();
 builder.Services.AddSingleton<IJuHeApiService, JuHeApiService>();
 builder.Services.AddHttpClient("PublicJuHeApi", c => c.BaseAddress = new Uri("http://web.juhe.cn"));
 builder.Services.AddHttpClient("PublicJuHeTranslateApi", c => c.BaseAddress = new Uri("http://apis.juhe.cn"));
 #endregion
 
-#region 浼佷笟寰�俊API 鏈嶅姟
+#region 企业微信API 服务
 
 builder.Services.AddControllersWithViews();
 builder.Services.AddSingleton<IQiYeWeChatApiService, QiYeWeChatApiService>();
@@ -498,17 +504,59 @@ builder.Services.AddHttpClient("PublicQiYeWeChatApi", c => c.BaseAddress = new U
 
 #endregion
 
-#region 鏈夐亾API 鏈嶅姟
+#region 混元API
+
+// 从配置中读取腾讯云密钥信息(请确保appsettings.json中有对应配置)
+var secretId = builder.Configuration["TencentCloud:SecretId"];
+var secretKey = builder.Configuration["TencentCloud:SecretKey"];
+var region = builder.Configuration["TencentCloud:Region"] ?? "ap-guangzhou";
+
+// 配置HttpClientFactory(SDK内部会用到)
+builder.Services.AddHttpClient();
+
+// 注册腾讯云Hunyuan Client为Singleton(推荐)
+builder.Services.AddSingleton(provider =>
+{
+    Credential cred = new Credential
+    {
+        SecretId = secretId,
+        SecretKey = secretKey
+    };
+
+    ClientProfile clientProfile = new ClientProfile();
+    HttpProfile httpProfile = new HttpProfile
+    {
+        Endpoint = "hunyuan.tencentcloudapi.com"
+    };
+    clientProfile.HttpProfile = httpProfile;
+
+    return new HunyuanClient(cred, region, clientProfile);
+});
+
+// 注册自定义服务接口及其实现为Scoped生命周期
+builder.Services.AddScoped<IHunyuanService, HunyuanService>();
+
+// 注册混元服务
+//builder.Services.AddHttpClient<IHunyuanService, HunyuanService>(client =>
+//{
+//    client.BaseAddress = new Uri("https://hunyuan.ap-chengdu.tencentcloudapi.com/");
+//    client.Timeout = TimeSpan.FromSeconds(60);
+//});
+
+//builder.Services.Configure<HunyuanApiSettings>(builder.Configuration.GetSection("HunyuanApiSettings"));
+#endregion
+
+#region 有道API 服务
 //builder.Services.AddControllersWithViews();
 //builder.Services.AddSingleton<IYouDaoApiService, YouDaoApiService>();
 //builder.Services.AddHttpClient("PublicYouDaoApi", c => c.BaseAddress = new Uri("https://openapi.youdao.com"));
 #endregion
 
-#region 楂樺痉鍦板浘API 鏈嶅姟
+#region 高德地图API 服务
 builder.Services.AddHttpClient<GeocodeService>();
 #endregion
 
-#region 閫氱敤鎼滅储鏈嶅姟
+#region 通用搜索服务
 builder.Services.AddScoped(typeof(DynamicSearchService<>));
 
 #endregion
@@ -540,50 +588,50 @@ builder.Services.TryAddSingleton(typeof(CommonService));
 
 var app = builder.Build();
 
-//// 1. 异常处理器应该在最早的位置(除了日志等)
+//// 1. 异常处理器应该在最早的位置(除了日志等)
 //app.UseExceptionHandler(new ExceptionHandlerOptions
 //{
 //    ExceptionHandlingPath = "/Home/Error", 
 //    AllowStatusCode404Response = true
 //});
 
-//鑷�畾涔夊紓甯镐腑闂翠欢
+//自定义异常中间件
 //app.UseMiddleware<ExceptionHandlingMiddleware>();
 
-//serilog鏃ュ織 璇锋眰涓�棿绠¢亾
+//serilog日志 请求中间管道
 app.UseSerilogRequestLogging(options =>
 {
     //options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} from {ClientIP} (UA: {UserAgent}, Referer: {Referer}) - {StatusCode} in {Elapsed} ms";
 
     options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} from {ClientIP} (UA: {UserAgent}) - {StatusCode} in {Elapsed} ms";
 
-    // 自定义日志级别
+    // 自定义日志级别
     options.GetLevel = (httpContext, elapsed, ex) =>
     {
         if (ex != null) return LogEventLevel.Error;
         if (httpContext.Response.StatusCode > 499) return LogEventLevel.Error;
 
-        // 瀵瑰仴搴锋�鏌ョ瓑绔�偣浣跨敤鏇翠綆绾у埆
+        // 对健康检查等端点使用更低级别
         if (httpContext.Request.Path.StartsWithSegments("/health"))
             return LogEventLevel.Debug;
 
         return LogEventLevel.Information;
     };
 
-    // 丰富日志上下文
+    // 丰富日志上下文
     options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
     {
-        // 鑾峰彇瀹㈡埛绔疘P锛堝�鐞嗕唬鐞嗘儏鍐碉級
+        // 获取客户端IP(处理代理情况)
         var ipAddress = CommonFun.GetClientIpAddress(httpContext);
         var userAgent = CommonFun.DetectOS(httpContext.Request.Headers.UserAgent.ToString());
 
-        // 添加IP和其他有用信息到日志上下文
+        // 添加IP和其他有用信息到日志上下文
         diagnosticContext.Set("ClientIP", ipAddress);
         diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
         diagnosticContext.Set("UserAgent", userAgent);
         diagnosticContext.Set("Referer", httpContext.Request.Headers.Referer.ToString());
 
-        // 瀵逛簬API璇锋眰娣诲姞棰濆�淇℃伅
+        // 对于API请求添加额外信息
         if (httpContext.Request.Path.StartsWithSegments("/api"))
         {
             diagnosticContext.Set("RequestContentType", httpContext.Request.ContentType);
@@ -609,28 +657,28 @@ app.UseCors("Cors");  //Cors
 
 //app.UseMiddleware<FixedPromptMiddleware>();
 
-// 瀹氫箟鍏佽�API鐨勮�闂�椂闂存�
+// 定义允许API的访问时间段
 //var startTime = DateTime.Parse(_config["ApiAccessTime:StartTime"]);
 //var endTime = DateTime.Parse(_config["ApiAccessTime:EndTime"]);
 //app.UseMiddleware<TimeRestrictionMiddleware>(startTime, endTime);
 
-//鎸囧畾API鎿嶄綔璁板綍淇℃伅
+//指定API操作记录信息
 app.UseMiddleware<RecordAPIOperationMiddleware>();
 
-app.UseAuthentication(); // 璁よ瘉
+app.UseAuthentication(); // 认证
 
 app.UseMiddleware<RateLimitMiddleware>();
 
-app.UseAuthorization();  // 鎺堟潈
+app.UseAuthorization();  // 授权
 
 app.UseWhen(context =>
     context.Request.Path.StartsWithSegments("/api/MarketCustomerResources/QueryNewClientData"),
     branch => branch.UseResponseCompression());
 
-// 鎺堟潈璺�緞
+// 授权路径
 //app.MapGet("generatetoken", c => c.Response.WriteAsync(JWTBearer.GenerateToken(c)));
 
-#region 鍚�敤swaggerUI
+#region 启用swaggerUI
 if (AppSettingsHelper.Get("UseSwagger").ToBool())
 {
     app.UseSwagger();
@@ -645,15 +693,15 @@ if (AppSettingsHelper.Get("UseSwagger").ToBool())
         c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
         c.DefaultModelsExpandDepth(-1);
 
-        //c.EnableFilter();// 娣诲姞鎼滅储鍔熻兘
-        //c.EnableDeepLinking(); // 鍚�敤娣卞害閾炬帴
+        //c.EnableFilter();// 添加搜索功能
+        //c.EnableDeepLinking(); // 启用深度链接
     });
 }
 #endregion
 
 #region Quartz
 
-//鑾峰彇瀹瑰櫒涓�殑QuartzFactory
+//获取容器中的QuartzFactory
 var quartz = app.Services.GetRequiredService<QuartzFactory>();
 app.Lifetime.ApplicationStarted.Register(async () =>
 {
@@ -662,7 +710,7 @@ app.Lifetime.ApplicationStarted.Register(async () =>
 
 app.Lifetime.ApplicationStopped.Register(() =>
 {
-    //Quzrtz鍏抽棴鏂规硶
+    //Quzrtz关闭方法
     //quartz.Stop();
 });
 
@@ -683,6 +731,4 @@ app.MapControllerRoute(
     name: "default",
     pattern: "{controller=Home}/{action=Index}/{id?}");
 
-
-
 app.Run();

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

@@ -580,5 +580,13 @@
       //}
     ]
   },
-  "AllowedHosts": "*"
+  "AllowedHosts": "*",
+  // 混元API 配置
+  "TencentCloud": {
+    "SecretId": "AKIDISQCm9K9wYDsT8hMuIVXO2WouugFpMlF",
+    "SecretKey": "PfleOvELH3dz9g4OWWKwLYXIp9F7mDUY",
+    "AppId": 100015480940,
+    "Region": "ap-chengdu",
+    "Version": "2023-09-01"
+  }
 }

+ 56 - 1
OASystem/OASystem.Domain/ViewModels/QiYeWeChat/CheckInView.cs

@@ -1,4 +1,5 @@
-using System;
+using Microsoft.AspNetCore.Http;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -92,6 +93,54 @@ namespace OASystem.Domain.ViewModels.QiYeWeChat
 
     #endregion
 
+    #region 邮件相关
+
+    public class EmailRequestDto
+    {
+        /// <summary>
+        /// 收件人邮箱地址列表
+        /// </summary>
+        public List<string> ToEmails { get; set; }
+
+        /// <summary>
+        /// 抄送人邮箱地址列表
+        /// </summary>
+        public List<string> CcEmails { get; set; }
+
+        /// <summary>
+        /// 密送人邮箱地址列表
+        /// </summary>
+        public List<string> BccEmails { get; set; }
+
+        /// <summary>
+        /// 邮件主题
+        /// </summary>
+        public string Subject { get; set; }
+
+        /// <summary>
+        /// 邮件正文内容
+        /// </summary>
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 附件列表
+        /// </summary>
+        public IFormFile[] Files { get; set; }
+    }
+
+    public class Attachment
+    {
+        /// <summary>
+        /// 文件名
+        /// </summary>
+        public string file_name { get; set; }
+        /// <summary>
+        /// 文件内容(base64编码),所有附件加正文的大小不允许超过50M, 且附件个数不能超过200个
+        /// </summary>
+        public string content { get; set; }
+    }
+    #endregion
+
     /// <summary>
     /// 企业微信 API
     /// 打卡月数据
@@ -336,4 +385,10 @@ namespace OASystem.Domain.ViewModels.QiYeWeChat
         地点异常 = 5, // 地点异常
         设备异常 = 6 // 设备异常
     }
+
+    public class EmailResult
+    {
+        public int errcode { get; set; }
+        public string errmsg { get; set; }
+    }
 }