|
|
@@ -402,94 +402,6 @@ namespace OASystem.API.Controllers
|
|
|
return StatusCode(200, "发送成功");
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- #region 微软 auth
|
|
|
-
|
|
|
- [HttpGet("auth/url")]
|
|
|
- public IActionResult GetAuthUrl()
|
|
|
- {
|
|
|
- var clientId = _config["AzureHotmail:ClientId"];
|
|
|
- var redirectUri = _config["AzureHotmail:RedirectUri"]; // 需在 Azure Portal 注册
|
|
|
- var scope = Uri.EscapeDataString("offline_access Mail.Read Mail.Send User.Read");
|
|
|
-
|
|
|
- var url = $"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&redirect_uri={redirectUri}&response_mode=query&scope={scope}&state=alchemist";
|
|
|
-
|
|
|
- return Ok(new { authUrl = url });
|
|
|
- }
|
|
|
-
|
|
|
- [HttpGet("auth/callback")]
|
|
|
- public async Task<IActionResult> HandleCallback([FromQuery] string code)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(code)) return BadRequest("授权码无效");
|
|
|
-
|
|
|
- // 1. 换取令牌
|
|
|
- var httpClient = _httpClientFactory.CreateClient();
|
|
|
- var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
|
|
|
- {
|
|
|
- { "client_id", _config["AzureHotmail:ClientId"] },
|
|
|
- { "client_secret", _config["AzureHotmail:ClientSecret"] },
|
|
|
- { "code", code },
|
|
|
- { "redirect_uri", _config["AzureHotmail:RedirectUri"] },
|
|
|
- { "grant_type", "authorization_code" }
|
|
|
- });
|
|
|
-
|
|
|
- var response = await httpClient.PostAsync("https://login.microsoftonline.com/common/oauth2/v2.0/token", tokenRequest);
|
|
|
- var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
|
|
-
|
|
|
- if (!response.IsSuccessStatusCode) return BadRequest(json.RootElement.ToString());
|
|
|
-
|
|
|
- var root = json.RootElement;
|
|
|
- var accessToken = root.GetProperty("access_token").GetString()!;
|
|
|
- var refreshToken = root.GetProperty("refresh_token").GetString()!;
|
|
|
- var expiresIn = root.GetProperty("expires_in").GetInt32();
|
|
|
-
|
|
|
- // 2. 自动识别账户身份 【核心重构】:不再手动解析 JWT,而是请求 Graph 的 /me 接口
|
|
|
- string userEmail = await GetEmailFromGraphApiAsync(accessToken);
|
|
|
-
|
|
|
-
|
|
|
- // 3. 炼金产物:构造并存入 Redis
|
|
|
- var userToken = new UserToken
|
|
|
- {
|
|
|
- Email = userEmail,
|
|
|
- AccessToken = accessToken,
|
|
|
- RefreshToken = refreshToken,
|
|
|
- ExpiresAt = DateTime.UtcNow.AddSeconds(expiresIn)
|
|
|
- };
|
|
|
-
|
|
|
- // 存入 Redis (使用我们之前的 RedisKeyPrefix: "MailAlchemy:Token:")
|
|
|
- var redisKey = $"MailAlchemy:Token:{userEmail}";
|
|
|
- await RedisRepository.RedisFactory.CreateRedisRepository().StringSetAsync<string>(redisKey, System.Text.Json.JsonSerializer.Serialize(userToken), TimeSpan.FromDays(90));
|
|
|
-
|
|
|
- return Ok(new
|
|
|
- {
|
|
|
- status = "Success",
|
|
|
- account = userEmail,
|
|
|
- message = "该个人账户已成功集成并启用分布式存储"
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- private async Task<string> GetEmailFromGraphApiAsync(string accessToken)
|
|
|
- {
|
|
|
- var httpClient = _httpClientFactory.CreateClient();
|
|
|
- // 使用 AccessToken 调用 Graph API 的个人信息接口
|
|
|
- httpClient.DefaultRequestHeaders.Authorization =
|
|
|
- new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
|
|
|
-
|
|
|
- var response = await httpClient.GetAsync("https://graph.microsoft.com/v1.0/me");
|
|
|
- if (!response.IsSuccessStatusCode)
|
|
|
- throw new Exception("无法通过 Graph API 获取用户信息");
|
|
|
-
|
|
|
- using var doc = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
|
|
- var root = doc.RootElement;
|
|
|
-
|
|
|
- // 个人账户优先取 mail,如果没有则取 userPrincipalName
|
|
|
- return root.GetProperty("mail").GetString()
|
|
|
- ?? root.GetProperty("userPrincipalName").GetString()
|
|
|
- ?? throw new Exception("未能获取有效的 Email 地址");
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
#region Microsoft Graph 邮箱测试(仅访问令牌)
|
|
|
|
|
|
private const string GraphAccessTokenHeader = "X-Graph-Access-Token";
|