SendNotification([FromBody] EmailRequest request)
{
if (string.IsNullOrEmpty(request.Email))
return BadRequest("邮箱地址不能为空");
// 调用服务
bool isSent = await _hotmailEmailService.SendEmailAsync(
request.Email,
"OASystem 业务提醒",
$"您有一条新的待办事项:{request.Content}
"
);
if (isSent)
return Ok(new { code = 200, msg = "发送成功" });
return StatusCode(500, "邮件发送失败,请检查服务器配置或应用密码");
}
// 定义请求实体
public class EmailRequest
{
public string Email { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
}
#endregion
#region Microsoft Graph 邮箱测试(仅访问令牌)
private const string GraphAccessTokenHeader = "X-Graph-Access-Token";
///
/// 优先级:请求头 X-Graph-Access-Token → 查询 graphAccessToken → bodyToken(发信)。
///
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;
}
///
/// 查询当前用户 GET /v1.0/me。必须提供 Graph 访问令牌。
///
[HttpGet("graph-mail/me")]
public async Task 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 });
}
}
///
/// 查询收件箱。必须提供 Graph 访问令牌(需 Mail.Read)。默认 sinceUtc 为 UTC 近 24 小时。
///
/// 起始时间(UTC),ISO8601
/// 或使用请求头 X-Graph-Access-Token
/// 取消标记
[HttpGet("graph-mail/inbox")]
public async Task 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 });
}
}
///
/// Graph sendMail 纯文本。必须提供令牌(需 Mail.Send):头 / 查询 / Body.graphAccessToken。
///
[HttpPost("graph-mail/send")]
public async Task 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; }
}
///
/// 从 Redis 读取 MSAL 缓存与 HomeAccountId,静默刷新 Graph access_token。
///
[HttpGet("graph-mail/refresh-token")]
public async Task 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(json);
}
catch (JsonException ex)
{
_logger.LogWarning(ex, "Redis 键 {Key} 内容不是合法 JSON(应用 StringGetRawAsync + JSON,勿用 StringGetAsync,后者为 BinaryFormatter)", key);
return BadRequest(new { message = "Redis 值为 JSON 文本时须用 StringGetRawAsync 再反序列化;StringGetAsync 仅适用于 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 });
}
///
/// Graph 发信测试请求体
///
public class GraphMailSendTestRequest
{
public string ToEmail { get; set; } = string.Empty;
public string? Subject { get; set; }
public string? Body { get; set; }
/// Microsoft Graph 访问令牌(也可用请求头 X-Graph-Access-Token)
public string? GraphAccessToken { get; set; }
}
#endregion
}
}