Lyyyi преди 1 седмица
родител
ревизия
b680d75938

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

@@ -2,11 +2,13 @@ using Flurl.Http.Configuration;
 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.Domain.ViewModels.QiYeWeChat;
 using System.IdentityModel.Tokens.Jwt;
 using System.Text.Json;
 using static OASystem.API.OAMethodLib.Hotmail.HotmailService;
+using OASystem.RedisRepository;
 
 namespace OASystem.API.Controllers
 {
@@ -463,5 +465,190 @@ namespace OASystem.API.Controllers
 
         #endregion
 
+        #region Microsoft Graph 邮箱测试(仅访问令牌)
+
+        private const string GraphAccessTokenHeader = "X-Graph-Access-Token";
+
+        /// <summary>
+        /// 优先级:请求头 X-Graph-Access-Token → 查询 graphAccessToken → bodyToken(发信)。
+        /// </summary>
+        private string? ResolveGraphAccessToken(string? queryToken = null, string? bodyToken = null)
+        {
+            var header = Request.Headers[GraphAccessTokenHeader].FirstOrDefault();
+            if (!string.IsNullOrWhiteSpace(header))
+                return header.Trim();
+            if (!string.IsNullOrWhiteSpace(queryToken))
+                return queryToken.Trim();
+            if (!string.IsNullOrWhiteSpace(bodyToken))
+                return bodyToken.Trim();
+            return null;
+        }
+
+        /// <summary>
+        /// 查询当前用户 GET /v1.0/me。必须提供 Graph 访问令牌。
+        /// </summary>
+        [HttpGet("graph-mail/me")]
+        public async Task<IActionResult> GraphMailMe(
+            [FromQuery] string? graphAccessToken = null,
+            CancellationToken cancellationToken = default)
+        {
+            var bearer = ResolveGraphAccessToken(graphAccessToken);
+            if (string.IsNullOrWhiteSpace(bearer))
+                return Unauthorized(new { message = "必须提供 Microsoft Graph 访问令牌:请求头 X-Graph-Access-Token 或查询参数 graphAccessToken" });
+
+            try
+            {
+                var json = await _microsoftGraphMailboxService.GetMeRawJsonAsync(bearer, cancellationToken);
+                if (string.IsNullOrEmpty(json))
+                    return StatusCode(502, new { message = "Graph 返回空正文" });
+
+                return Content(json, "application/json");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Graph Mail /me 失败");
+                return StatusCode(500, new { message = ex.Message });
+            }
+        }
+
+        /// <summary>
+        /// 查询收件箱。必须提供 Graph 访问令牌(需 Mail.Read)。默认 sinceUtc 为 UTC 近 24 小时。
+        /// </summary>
+        /// <param name="sinceUtc">起始时间(UTC),ISO8601</param>
+        /// <param name="graphAccessToken">或使用请求头 X-Graph-Access-Token</param>
+        /// <param name="cancellationToken">取消标记</param>
+        [HttpGet("graph-mail/inbox")]
+        public async Task<IActionResult> GraphMailInbox(
+            [FromQuery] DateTime? sinceUtc = null,
+            [FromQuery] string? graphAccessToken = null,
+            CancellationToken cancellationToken = default)
+        {
+            var bearer = ResolveGraphAccessToken(graphAccessToken);
+            if (string.IsNullOrWhiteSpace(bearer))
+                return Unauthorized(new { message = "必须提供 Microsoft Graph 访问令牌:请求头 X-Graph-Access-Token 或查询参数 graphAccessToken" });
+
+            var since = sinceUtc ?? DateTime.UtcNow.AddHours(-24);
+
+            try
+            {
+                var json = await _microsoftGraphMailboxService.GetInboxMessagesJsonSinceAsync(since, bearer, cancellationToken);
+                if (string.IsNullOrEmpty(json))
+                    return StatusCode(502, new { message = "Graph 返回空正文" });
+
+                return Content(json, "application/json");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Graph Mail inbox 失败");
+                return StatusCode(500, new { message = ex.Message });
+            }
+        }
+
+        /// <summary>
+        /// Graph sendMail 纯文本。必须提供令牌(需 Mail.Send):头 / 查询 / Body.graphAccessToken。
+        /// </summary>
+        [HttpPost("graph-mail/send")]
+        public async Task<IActionResult> GraphMailSend(
+            [FromBody] GraphMailSendTestRequest request,
+            [FromQuery] string? graphAccessToken = null,
+            CancellationToken cancellationToken = default)
+        {
+            if (request == null || string.IsNullOrWhiteSpace(request.ToEmail))
+                return BadRequest(new { message = "ToEmail 不能为空" });
+
+            var bearer = ResolveGraphAccessToken(graphAccessToken, request.GraphAccessToken);
+            if (string.IsNullOrWhiteSpace(bearer))
+                return Unauthorized(new { message = "必须提供 Microsoft Graph 访问令牌:X-Graph-Access-Token、?graphAccessToken 或 Body.graphAccessToken" });
+
+            var subject = string.IsNullOrWhiteSpace(request.Subject)
+                ? $"OASystem Graph 测试邮件 {DateTime.Now:yyyy-MM-dd HH:mm:ss}"
+                : request.Subject!;
+
+            var body = request.Body ?? string.Empty;
+
+            try
+            {
+                await _microsoftGraphMailboxService.SendMailAsync(request.ToEmail.Trim(), subject, body, bearer, cancellationToken);
+                return Ok(new { ok = true, message = "sendMail 已提交", to = request.ToEmail.Trim(), subject });
+            }
+            catch (HttpRequestException ex)
+            {
+                return StatusCode(502, new { message = "Graph HTTP 错误", detail = ex.Message });
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Graph Mail send 失败");
+                return StatusCode(500, new { message = ex.Message });
+            }
+        }
+
+
+
+        public class EmailAuthRedisCache
+        {
+            public string? AccessToken { get; set; }
+            public string? HomeAccountId { get; set; }
+            public string? UserTokenCacheBase64 { get; set; }
+            public string? ClientId { get; set; }
+        }
+
+
+        /// <summary>
+        /// 从 Redis 读取 MSAL 缓存与 HomeAccountId,静默刷新 Graph access_token。
+        /// </summary>
+        [HttpGet("graph-mail/refresh-token")]
+        public async Task<IActionResult> RefreshAccessToken([FromQuery] string? redisKey = null)
+        {
+            var key = string.IsNullOrWhiteSpace(redisKey) ? "Email:AuthCache:345" : redisKey.Trim();
+
+            var redis = RedisFactory.CreateRedisRepository();
+            var json = await redis.StringGetRawAsync(key);
+            if (string.IsNullOrWhiteSpace(json))
+            {
+                return BadRequest(new { message = $"Redis 键 {key} 不存在或为空" });
+            }
+
+            EmailAuthRedisCache? cacheEntry;
+            try
+            {
+                cacheEntry = JsonConvert.DeserializeObject<EmailAuthRedisCache>(json);
+            }
+            catch (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 });
+            }
+
+            if (cacheEntry == null
+                || string.IsNullOrWhiteSpace(cacheEntry.UserTokenCacheBase64)
+                || string.IsNullOrWhiteSpace(cacheEntry.HomeAccountId))
+            {
+                return BadRequest(new { message = "JSON 中缺少 UserTokenCacheBase64 或 HomeAccountId" });
+            }
+
+            var accessToken = await _microsoftGraphMailboxService.RefreshAccessTokenAsync(
+                cacheEntry.ClientId,
+                "common",
+                new[] { "Mail.Read", "User.Read", "Mail.Send" },
+                cacheEntry.UserTokenCacheBase64,
+                cacheEntry.HomeAccountId);
+
+            return Ok(new { accessToken });
+        }
+
+        /// <summary>
+        /// Graph 发信测试请求体
+        /// </summary>
+        public class GraphMailSendTestRequest
+        {
+            public string ToEmail { get; set; } = string.Empty;
+            public string? Subject { get; set; }
+            public string? Body { get; set; }
+            /// <summary>Microsoft Graph 访问令牌(也可用请求头 X-Graph-Access-Token)</summary>
+            public string? GraphAccessToken { get; set; }
+        }
+
+        #endregion
+
     }
 }

+ 45 - 0
OASystem/OASystem.Api/OAMethodLib/MicrosoftGraphMailbox/GraphMailSendDtos.cs

@@ -0,0 +1,45 @@
+using System.Text.Json.Serialization;
+
+namespace OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
+
+public class GraphSendMailRequest
+{
+    [JsonPropertyName("message")]
+    public GraphSendMailMessage Message { get; set; } = new();
+
+    [JsonPropertyName("saveToSentItems")]
+    public bool SaveToSentItems { get; set; } = true;
+}
+
+public class GraphSendMailMessage
+{
+    [JsonPropertyName("subject")]
+    public string Subject { get; set; } = string.Empty;
+
+    [JsonPropertyName("body")]
+    public GraphSendMailBody Body { get; set; } = new();
+
+    [JsonPropertyName("toRecipients")]
+    public List<GraphSendMailRecipient> ToRecipients { get; set; } = new();
+}
+
+public class GraphSendMailBody
+{
+    [JsonPropertyName("contentType")]
+    public string ContentType { get; set; } = "Text";
+
+    [JsonPropertyName("content")]
+    public string Content { get; set; } = string.Empty;
+}
+
+public class GraphSendMailRecipient
+{
+    [JsonPropertyName("emailAddress")]
+    public GraphSendMailEmail EmailAddress { get; set; } = new();
+}
+
+public class GraphSendMailEmail
+{
+    [JsonPropertyName("address")]
+    public string Address { get; set; } = string.Empty;
+}

+ 29 - 0
OASystem/OASystem.Api/OAMethodLib/MicrosoftGraphMailbox/IMicrosoftGraphMailboxService.cs

@@ -0,0 +1,29 @@
+namespace OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
+
+public interface IMicrosoftGraphMailboxService
+{
+    /// <summary>
+    /// GET /me 原始 JSON(使用调用方提供的 Graph 访问令牌)。
+    /// </summary>
+    Task<string?> GetMeRawJsonAsync(string graphAccessToken, CancellationToken cancellationToken = default);
+
+    /// <summary>
+    /// 拉取收件箱中 receivedDateTime &gt;= startUtc 的邮件列表 JSON。
+    /// </summary>
+    Task<string?> GetInboxMessagesJsonSinceAsync(DateTime startUtc, string graphAccessToken, CancellationToken cancellationToken = default);
+
+    /// <summary>
+    /// POST /me/sendMail 发送纯文本邮件。
+    /// </summary>
+    Task SendMailAsync(string toEmail, string subject, string textBody, string graphAccessToken, CancellationToken cancellationToken = default);
+
+    /// <summary>
+    /// 刷新 access_token
+    /// </summary>
+    Task<string> RefreshAccessTokenAsync(string clientId,
+        string tenant,
+        string[] scopes,
+        string tokenCacheBase64,   // 从Redis拿到的缓存(Base64字符串)
+        string homeAccountId       // 用户标识(建议存这个)
+        );
+}

+ 14 - 0
OASystem/OASystem.Api/OAMethodLib/MicrosoftGraphMailbox/MicrosoftGraphMailboxOptions.cs

@@ -0,0 +1,14 @@
+namespace OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
+
+/// <summary>
+/// Microsoft Graph 邮箱 API 相关配置(调用方自行获取并传入访问令牌)。
+/// </summary>
+public class MicrosoftGraphMailboxOptions
+{
+    public const string SectionName = "MicrosoftGraphMailbox";
+
+    /// <summary>
+    /// 收件箱列表查询单次返回的最大邮件条数($top)。
+    /// </summary>
+    public int TopMessages { get; set; } = 50;
+}

+ 168 - 0
OASystem/OASystem.Api/OAMethodLib/MicrosoftGraphMailbox/MicrosoftGraphMailboxService.cs

@@ -0,0 +1,168 @@
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.Options;
+using Microsoft.Identity.Client;
+
+namespace OASystem.API.OAMethodLib.MicrosoftGraphMailbox;
+
+public class MicrosoftGraphMailboxService : IMicrosoftGraphMailboxService
+{
+    private readonly IHttpClientFactory _httpClientFactory;
+    private readonly IOptionsMonitor<MicrosoftGraphMailboxOptions> _options;
+    private readonly ILogger<MicrosoftGraphMailboxService> _logger;
+
+    private const string HttpClientName = "MicrosoftGraph";
+
+    public MicrosoftGraphMailboxService(
+        IHttpClientFactory httpClientFactory,
+        IOptionsMonitor<MicrosoftGraphMailboxOptions> options,
+        ILogger<MicrosoftGraphMailboxService> logger)
+    {
+        _httpClientFactory = httpClientFactory;
+        _options = options;
+        _logger = logger;
+    }
+
+    public async Task<string?> GetMeRawJsonAsync(string graphAccessToken, CancellationToken cancellationToken = default)
+    {
+        using var client = CreateAuthenticatedClient(graphAccessToken);
+        using var response = await client.GetAsync("me", cancellationToken).ConfigureAwait(false);
+        var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+        if (!response.IsSuccessStatusCode)
+            _logger.LogWarning("Graph GET /me 失败: {Status} {Body}", (int)response.StatusCode, body);
+        return body;
+    }
+
+    public async Task<string?> GetInboxMessagesJsonSinceAsync(DateTime startUtc, string graphAccessToken, CancellationToken cancellationToken = default)
+    {
+        var opt = _options.CurrentValue;
+        var startTime = startUtc.ToString("o");
+        var url =
+            "me/mailFolders/inbox/messages" +
+            "?$select=id,subject,from,receivedDateTime,bodyPreview,conversationId" +
+            $"&$filter=receivedDateTime ge {startTime}" +
+            "&$orderby=receivedDateTime desc" +
+            $"&$top={opt.TopMessages}";
+
+        using var client = CreateAuthenticatedClient(graphAccessToken);
+        using var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false);
+        var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+        if (!response.IsSuccessStatusCode)
+            _logger.LogWarning("Graph 拉取收件箱失败: {Status} {Body}", (int)response.StatusCode, body);
+        return body;
+    }
+
+    public async Task SendMailAsync(string toEmail, string subject, string textBody, string graphAccessToken, CancellationToken cancellationToken = default)
+    {
+        var payload = new GraphSendMailRequest
+        {
+            Message = new GraphSendMailMessage
+            {
+                Subject = subject,
+                Body = new GraphSendMailBody
+                {
+                    ContentType = "Text",
+                    Content = textBody
+                },
+                ToRecipients = new List<GraphSendMailRecipient>
+                {
+                    new()
+                    {
+                        EmailAddress = new GraphSendMailEmail { Address = toEmail }
+                    }
+                }
+            },
+            SaveToSentItems = true
+        };
+
+        var json = System.Text.Json.JsonSerializer.Serialize(payload, new JsonSerializerOptions
+        {
+            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+        });
+
+        using var client = CreateAuthenticatedClient(graphAccessToken);
+        using var content = new StringContent(json, Encoding.UTF8, "application/json");
+        using var response = await client.PostAsync("me/sendMail", content, cancellationToken).ConfigureAwait(false);
+        var responseText = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+
+        if (!response.IsSuccessStatusCode)
+        {
+            _logger.LogError("Graph sendMail 失败: {Status} {Body}", (int)response.StatusCode, responseText);
+            response.EnsureSuccessStatusCode();
+        }
+    }
+
+    private HttpClient CreateAuthenticatedClient(string graphAccessToken)
+    {
+        if (string.IsNullOrWhiteSpace(graphAccessToken))
+            throw new ArgumentException("Graph 访问令牌不能为空。", nameof(graphAccessToken));
+
+        var client = _httpClientFactory.CreateClient(HttpClientName);
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("Bearer", graphAccessToken.Trim());
+        return client;
+    }
+
+
+    public async Task<string> RefreshAccessTokenAsync(
+        string clientId,
+        string tenant,
+        string[] scopes,
+        string tokenCacheBase64,   // 从Redis拿到的缓存(Base64字符串)
+        string homeAccountId       // 用户标识(建议存这个)
+    )
+    {
+        // 1️⃣ 创建 MSAL 应用
+        var app = PublicClientApplicationBuilder
+            .Create(clientId)
+            .WithAuthority($"https://login.microsoftonline.com/{tenant}")
+            .WithDefaultRedirectUri()
+            .Build();
+
+        // 2️⃣ 恢复 TokenCache
+        if (!string.IsNullOrEmpty(tokenCacheBase64))
+        {
+            var cacheBytes = Convert.FromBase64String(tokenCacheBase64);
+
+            app.UserTokenCache.SetBeforeAccess(args =>
+            {
+                args.TokenCache.DeserializeMsalV3(cacheBytes, false);
+            });
+
+            app.UserTokenCache.SetAfterAccess(args =>
+            {
+
+            });
+        }
+        else
+        {
+            throw new InvalidOperationException("TokenCache 为空,无法刷新");
+        }
+
+        // 3️⃣ 找到对应用户
+        var accounts = await app.GetAccountsAsync();
+        var account = accounts.FirstOrDefault(a =>
+            string.Equals(a.HomeAccountId?.Identifier, homeAccountId, StringComparison.Ordinal));
+
+        if (account == null)
+        {
+            throw new InvalidOperationException("未找到匹配的用户账号,需要重新登录");
+        }
+
+        // 4️⃣ 刷新(核心)
+        try
+        {
+            var result = await app
+                .AcquireTokenSilent(scopes, account)
+                .ExecuteAsync();
+
+            return result.AccessToken;
+        }
+        catch (MsalUiRequiredException ex)
+        {
+            throw new InvalidOperationException("刷新失败,需要重新登录", ex);
+        }
+    }
+}

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

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>

+ 112 - 96
OASystem/OASystem.Api/Program.cs

@@ -29,12 +29,14 @@ using TencentCloud.Common;
 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);
 var basePath = AppContext.BaseDirectory;
 
-//引入配置文件
+//寮曞叆閰嶇疆鏂囦欢
 var _config = new ConfigurationBuilder()
                  .SetBasePath(basePath)
                  .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
@@ -43,10 +45,10 @@ var _config = new ConfigurationBuilder()
                  .Build();
 builder.Services.AddSingleton(new AppSettingsHelper(_config));
 
-//设置请求参数可不填
+//鐠佸墽鐤嗙拠閿嬬湴閸欏倹鏆熼崣锟芥稉宥咃綖
 builder.Services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);
 
-//设置请求参数错误默认返回格式
+//璁剧疆璇锋眰鍙傛暟閿欒��榛樿�よ繑鍥炴牸寮�
 builder.Services.AddControllers()
     .ConfigureApiBehaviorOptions(options =>
     {
@@ -75,26 +77,26 @@ builder.Services.AddControllersWithViews();
 builder.Services.AddControllers()
     .AddJsonOptions(options =>
     {
-        //空字段不响应Response
+        //缁屽搫鐡у▓鍏哥瑝閸濆秴绨睷esponse
         //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 娣诲姞闄愭祦涓�闂翠欢鏈嶅姟娉ㄥ唽
 
-// 添加内存缓存(限流需要)
+// 濞h�濮為崘鍛�摠缂傛挸鐡ㄩ敍鍫ユ�濞翠線娓剁憰渚婄礆
 builder.Services.AddMemoryCache();
 
-// 配置限流设置
+// 閰嶇疆闄愭祦璁剧疆
 builder.Services.Configure<RateLimitConfig>(
     builder.Configuration.GetSection("RateLimiting"));
 #endregion
@@ -120,14 +122,14 @@ builder.Services.AddCors(options =>
     //policy.AddPolicy("Cors", opt => opt
     //        //.SetIsOriginAllowed(origin =>
     //        //{
-    //        //    // 定义允许的来源列表
+    //        //    // 瀹氫箟鍏佽�哥殑鏉ユ簮鍒楄〃
     //        //    var allowedOrigins = new List<string>
     //        //        {
     //        //           "http://132.232.92.186:9002",
     //        //           "http://oa.pan-american-intl.com:4399"
     //        //        };
 
-    //        //    // 检查请求的来源是否在允许的列表中
+    //        //    // 妫€鏌ヨ�锋眰鐨勬潵婧愭槸鍚﹀湪鍏佽�哥殑鍒楄〃涓�
     //        //    return allowedOrigins.Contains(origin);
     //        //})
 
@@ -149,7 +151,7 @@ builder.Services.AddCors(options =>
 });
 #endregion
 
-#region 上传文件 
+#region 娑撳﹣绱堕弬鍥︽� 
 builder.Services.AddCors(policy =>
 {
     policy.AddPolicy("Cors", opt => opt
@@ -175,15 +177,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
 
@@ -206,38 +208,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) 
+                //閹笛嗭拷灞界暚娴滃棗褰叉禒銉ㄧ翻閸戠癄QL閹笛嗭拷灞炬�闂傦拷 (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参数多的,调试使用
+            //閼惧嘲褰囬弮鐘插棘閺佺櫇QL鐎佃�鈧�嗗厴閺堝�濂栭崫宥忕礉閻楃懓鍩嗘径褏娈慡QL閸欏倹鏆熸径姘辨畱閿涘矁鐨熺拠鏇氬▏閻�拷
             //UtilMethods.GetSqlString(DbType.SqlServer, exp.sql, exp.parameters);
 
         };
-        //修改SQL和参数的值
+        //娣囷拷閺€绛峇L閸滃苯寮�弫鎵�畱閸婏拷
         db.Aop.OnExecutingChangeSql = (sql, pars) =>
         {
             //sql=newsql
-            //foreach(var p in pars) //修改
+            //foreach(var p in pars) //淇�鏀�
             return new KeyValuePair<string, SugarParameter[]>(sql, pars);
         };
     }
@@ -248,27 +250,27 @@ builder.Services.AddScoped(options =>
 
 #endregion
 
-//#region Identity 配置
+//#region Identity 閰嶇疆
 //builder.Services.AddDataProtection();
-////不要用 AddIdentity , AddIdentity 是于MVC框架中的
+////涓嶈�佺敤 AddIdentity 锛� AddIdentity 鏄�浜嶮VC妗嗘灦涓�鐨�
 //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())
 {
@@ -278,11 +280,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);
@@ -311,7 +313,7 @@ if (AppSettingsHelper.Get("UseSwagger").ToBool())
 }
 #endregion
 
-#region 添加校验
+#region 濞h�濮為弽锟犵崣
 
 builder.Services.AddTransient<OASystemAuthentication>();
 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
@@ -326,7 +328,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
             ValidAudience = "OASystem.com",
             ValidIssuer = "OASystem.com",
             IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JwtSecurityKey"])),
-            ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
+            ClockSkew = TimeSpan.FromSeconds(30), //杩囨湡鏃堕棿瀹归敊鍊硷紝瑙e喅鏈嶅姟鍣ㄧ��鏃堕棿涓嶅悓姝ラ棶棰橈紙绉掞級
             RequireExpirationTime = true,
         };
         options.Events = new JwtBearerEvents
@@ -334,7 +336,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
             OnMessageReceived = context =>
             {
                 var path = context.HttpContext.Request.Path;
-                //如果是signalr请求,需要将token转存,否则JWT获取不到token。OPTIONS请求需要过滤到,因为OPTIONS请求获取不到Token,用NGINX过滤掉OPTION请求.
+                //濡傛灉鏄痵ignalr璇锋眰锛岄渶瑕佸皢token杞�瀛橈紝鍚﹀垯JWT鑾峰彇涓嶅埌token銆侽PTIONS璇锋眰闇€瑕佽繃婊ゅ埌锛屽洜涓篛PTIONS璇锋眰鑾峰彇涓嶅埌Token锛岀敤NGINX杩囨护鎺塐PTION璇锋眰.
                 if (path.StartsWithSegments("/ChatHub"))
                 {
                     string accessToken = context.Request.Query["access_token"].ToString();
@@ -351,10 +353,10 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
 
 #endregion
 
-#region 初始化日志
+#region 閸掓繂锟藉�瀵查弮銉ョ箶
 var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
 Log.Logger = new LoggerConfiguration()
-        //不记录定时访问API
+        //娑撳秷锟芥澘缍嶇€规碍妞傜拋鍧楁6API
         .Filter.ByIncludingOnly(logEvent =>
         {
             if (logEvent.Properties.TryGetValue("RequestPath", out var pathValue))
@@ -374,20 +376,20 @@ Log.Logger = new LoggerConfiguration()
 
 // 
 
-#region 出入境费用明细 专用记录器
+#region 鍑哄叆澧冭垂鐢ㄦ槑缁� 涓撶敤璁板綍鍣�
 
-// 指定磁盘绝对路径(示例:D盘的AppLogs文件夹)
+// 閹稿洤鐣剧壕浣烘磸缂佹繂锟界�鐭惧�鍕剁礄缁€杞扮伐閿涙�閻╂�娈慉ppLogs閺傚洣娆㈡径鐧哥礆
 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;
 }
 
@@ -398,20 +400,20 @@ var eec_TextLogger = new LoggerConfiguration()
 
 #endregion
 
-#region 团组步骤操作 专用记录器
+#region 閸ャ垻绮嶅�銉╋拷銈嗘惙娴o拷 娑撴挾鏁ょ拋鏉跨秿閸o拷
 
-// 指定磁盘绝对路径(示例:D盘的AppLogs文件夹)
+// 閹稿洤鐣剧壕浣烘磸缂佹繂锟界�鐭惧�鍕剁礄缁€杞扮伐閿涙�閻╂�娈慉ppLogs閺傚洣娆㈡径鐧哥礆
 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;
 }
 
@@ -422,20 +424,20 @@ var groupStepOP_TextLogger = new LoggerConfiguration()
 
 #endregion
 
-#region 任务分配操作 专用记录器
+#region 浠诲姟鍒嗛厤鎿嶄綔 涓撶敤璁板綍鍣�
 
-// 指定磁盘绝对路径(示例:D盘的AppLogs文件夹)
+// 閹稿洤鐣剧壕浣烘磸缂佹繂锟界�鐭惧�鍕剁礄缁€杞扮伐閿涙�閻╂�娈慉ppLogs閺傚洣娆㈡径鐧哥礆
 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;
 }
 
@@ -446,7 +448,7 @@ var task_TextLogger = new LoggerConfiguration()
 
 #endregion
 
-// 配置Serilog为Log;
+// 闁板秶鐤哠erilog娑撶瘱og;
 builder.Host.UseSerilog();
 
 builder.Services.AddSingleton<ITextFileLogger>(new TextFileLogger(eec_TextLogger));
@@ -454,7 +456,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 =>
 {
@@ -480,29 +482,29 @@ builder.Services.AddScoped<IMapper, Mapper>();
 
 #endregion
 
-#region DeepSeek AI 服务
+#region DeepSeek AI 鏈嶅姟
 
-// 配置HTTP客户端(DeepSeek 长耗时调用,默认 10 分钟)
+// 閰嶇疆HTTP瀹㈡埛绔�锛圖eepSeek 闀胯€楁椂璋冪敤锛岄粯璁� 10 鍒嗛挓锛�
 builder.Services.AddHttpClient<IDeepSeekService, DeepSeekService>(client =>
     client.Timeout = TimeSpan.FromMinutes(10));
 
 #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>();
@@ -510,17 +512,17 @@ builder.Services.AddHttpClient("PublicQiYeWeChatApi", c => c.BaseAddress = new U
 
 #endregion
 
-#region 混元API
+#region 濞e嘲鍘揂PI
 
-// 从配置中读取腾讯云密钥信息(请确保appsettings.json中有对应配置)
+// 娴犲酣鍘ょ純锟芥稉锟界拠璇插絿閼垫崘锟斤拷娴滄垵鐦戦柦銉や繆閹�拷閿涘牐锟介�鈥樻穱婕歱psettings.json娑擄拷閺堝�锟界懓绨查柊宥囩枂閿涳拷
 var secretId = builder.Configuration["TencentCloud:SecretId"];
 var secretKey = builder.Configuration["TencentCloud:SecretKey"];
 var region = builder.Configuration["TencentCloud:Region"] ?? "ap-guangzhou";
 
-// 配置HttpClientFactory(SDK内部会用到)
+// 閰嶇疆HttpClientFactory锛圫DK鍐呴儴浼氱敤鍒帮級
 builder.Services.AddHttpClient();
 
-// 注册腾讯云Hunyuan Client为Singleton(推荐)
+// 濞夈劌鍞介懙鎹愶拷锟芥禍鎱攗nyuan Client娑撶癄ingleton閿涘牊甯归懡鎰剁礆
 builder.Services.AddSingleton(provider =>
 {
     Credential cred = new Credential
@@ -533,17 +535,17 @@ builder.Services.AddSingleton(provider =>
     HttpProfile httpProfile = new HttpProfile
     {
         Endpoint = "hunyuan.tencentcloudapi.com",
-        Timeout = 60 * 10,  // 单位秒
+        Timeout = 60 * 10,  // 鍗曚綅绉�
     };
     clientProfile.HttpProfile = httpProfile;
 
     return new HunyuanClient(cred, region, clientProfile);
 });
 
-// 注册自定义服务接口及其实现为Scoped生命周期
+// 濞夈劌鍞介懛锟界€规矮绠熼張宥呭�閹恒儱褰涢崣濠傚従鐎圭偟骞囨稉绡燾oped閻㈢喎鎳¢崨銊︽埂
 builder.Services.AddScoped<IHunyuanService, HunyuanService>();
 
-// 注册混元服务
+// 娉ㄥ唽娣峰厓鏈嶅姟
 //builder.Services.AddHttpClient<IHunyuanService, HunyuanService>(client =>
 //{
 //    client.BaseAddress = new Uri("https://hunyuan.ap-chengdu.tencentcloudapi.com/");
@@ -553,17 +555,17 @@ builder.Services.AddScoped<IHunyuanService, HunyuanService>();
 //builder.Services.Configure<HunyuanApiSettings>(builder.Configuration.GetSection("HunyuanApiSettings"));
 #endregion
 
-#region 有道API 服务
+#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
@@ -598,52 +600,66 @@ builder.Services.TryAddSingleton(typeof(CommonService));
 builder.Services.AddSingleton<HotmailService>();
 #endregion
 
+#region Microsoft Graph 閭�绠辨湇鍔�
+
+builder.Services.Configure<MicrosoftGraphMailboxOptions>(
+    builder.Configuration.GetSection(MicrosoftGraphMailboxOptions.SectionName));
+builder.Services.AddHttpClient("MicrosoftGraph", c =>
+{
+    c.BaseAddress = new Uri("https://graph.microsoft.com/v1.0/");
+    c.Timeout = TimeSpan.FromMinutes(2);
+});
+
+builder.Services.AddSingleton<IMicrosoftGraphMailboxService, MicrosoftGraphMailboxService>();
+
+#endregion
+
 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;
 
-        // 对健康检查等端点使用更低级别
+        // 鐎电懓浠存惔閿嬶拷鈧�弻銉х搼缁旓拷閻愰€涘▏閻€劍娲挎担搴i獓閸掞拷
         if (httpContext.Request.Path.StartsWithSegments("/health"))
             return LogEventLevel.Debug;
 
         return LogEventLevel.Information;
     };
 
-    // 丰富日志上下文
+    // 涓板瘜鏃ュ織涓婁笅鏂�
     options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
     {
-        // 获取客户端IP(处理代理情况)
+        // 鑾峰彇瀹㈡埛绔疘P锛堝�勭悊浠g悊鎯呭喌锛�
         var ipAddress = CommonFun.GetClientIpAddress(httpContext);
         var userAgent = CommonFun.DetectOS(httpContext.Request.Headers.UserAgent.ToString());
 
-        // 添加IP和其他有用信息到日志上下文
+        // 濞h�濮濱P閸滃苯鍙炬禒鏍ㄦ箒閻€劋淇婇幁锟介崚鐗堟)韫囨ぞ绗傛稉瀣�瀮
         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);
@@ -669,28 +685,28 @@ app.UseCors("Cors");  //Cors
 
 //app.UseMiddleware<FixedPromptMiddleware>();
 
-// 定义允许API的访问时间段
+// 鐎规矮绠熼崗浣斤拷绐�I閻ㄥ嫯锟藉潡妫堕弮鍫曟?濞堬拷
 //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 閸氾拷閻⑩暞waggerUI
 if (AppSettingsHelper.Get("UseSwagger").ToBool())
 {
     app.UseSwagger();
@@ -705,15 +721,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
+//閼惧嘲褰囩€圭懓娅掓稉锟介惃鍑瞮artzFactory
 var quartz = app.Services.GetRequiredService<QuartzFactory>();
 app.Lifetime.ApplicationStarted.Register(async () =>
 {
@@ -722,7 +738,7 @@ app.Lifetime.ApplicationStarted.Register(async () =>
 
 app.Lifetime.ApplicationStopped.Register(() =>
 {
-    //Quzrtz关闭方法
+    //Quzrtz閸忔娊妫撮弬瑙勭《
     //quartz.Stop();
 });
 

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

@@ -173,7 +173,6 @@
   // 商邀资料 AI 文件路径配置
   "InvitationAIAssistBasePath": "D:/FTP/File/OA2023/Office/Word/InvitationAIAssist/",
   "InvitationAIAssistFtpPath": "Office/Word/InvitationAIAssist/",
-
   "CTableCorrelationPageDatas": [
     {
       "CTableId": 76, //CtableId 酒店预订

+ 6 - 1
OASystem/OASystem.RedisRepository/RedisAsyncHelper/IRedisHelper.cs

@@ -1,4 +1,4 @@
-using StackExchange.Redis;
+using StackExchange.Redis;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -49,6 +49,11 @@ namespace OASystem.RedisRepository.RedisAsyncHelper
         /// <returns></returns>
         Task<T> StringGetAsync<T>(string key);
 
+        /// <summary>
+        /// 读取 string 键的原始文本(UTF-8)。不做 BinaryFormatter 反序列化,适用于存 JSON 等纯文本。
+        /// </summary>
+        Task<string?> StringGetRawAsync(string key);
+
         #endregion
 
         #region Redis数据类型—Hash

+ 9 - 1
OASystem/OASystem.RedisRepository/RedisAsyncHelper/RedisStringHelperAsync.cs

@@ -1,4 +1,4 @@
-using OASystem.RedisRepository.CommonHelper;
+using OASystem.RedisRepository.CommonHelper;
 using OASystem.RedisRepository.Config;
 using StackExchange.Redis;
 using System;
@@ -60,6 +60,14 @@ namespace OASystem.RedisRepository.RedisAsyncHelper
             return SerializeHelper.Deserialize<T>(await _client.StringGetAsync(key, CommandFlags.PreferSlave));
         }
 
+        public async Task<string?> StringGetRawAsync(string key)
+        {
+            var v = await _client.StringGetAsync(key, CommandFlags.PreferSlave);
+            if (!v.HasValue)
+                return null;
+            return v.ToString();
+        }
+
         #endregion
 
     }