using System.Globalization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Health.Infrastructure.Services; /// /// 百度云 SMS v3 短信发送客户端(HttpClient 直连 + BCE v1 签名) /// public sealed class BceSmsClient(HttpClient http, IConfiguration cfg, ILogger log) { private const string Path = "/api/v3/sendSms"; private const string ContentType = "application/json; charset=utf-8"; private readonly HttpClient _http = http; private readonly ILogger _log = log; private readonly string _ak = cfg["BAIDU_SMS_AK"] ?? ""; private readonly string _sk = cfg["BAIDU_SMS_SK"] ?? ""; private readonly string _host = cfg["BAIDU_SMS_HOST"] ?? "smsv3.bj.baidubce.com"; private readonly string _signatureId = cfg["BAIDU_SMS_SIGNATURE_ID"] ?? ""; private readonly string _templateId = cfg["BAIDU_SMS_TEMPLATE_ID"] ?? ""; public async Task SendAsync(string phone, string code, CancellationToken ct) { var payload = new { mobile = $"+86{phone}", template = _templateId, signatureId = _signatureId, contentVar = new { SMSvCode = code } }; var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(payload)); using var req = new HttpRequestMessage(HttpMethod.Post, Path) { Content = new ByteArrayContent(body) }; req.Content.Headers.TryAddWithoutValidation("Content-Type", ContentType); req.Headers.Host = _host; var now = DateTime.UtcNow; var timestamp = now.ToString("yyyy-MM-ddTHH:mm:ss'Z'", CultureInfo.InvariantCulture); req.Headers.Add("x-bce-date", timestamp); var auth = BceSigner.BuildAuthorization(_ak, _sk, "POST", Path, _host, now, body.Length, ContentType); req.Headers.TryAddWithoutValidation("Authorization", auth); using var resp = await _http.SendAsync(req, ct); var respBody = await resp.Content.ReadAsStringAsync(ct); if (!resp.IsSuccessStatusCode) { _log.LogError("百度短信发送失败 HTTP {Status}: {Body}", resp.StatusCode, respBody); return false; } using var doc = JsonDocument.Parse(respBody); var respCode = doc.RootElement.GetProperty("code").GetString(); if (respCode != "1000") { _log.LogError("百度短信发送失败 code={Code}: {Body}", respCode, respBody); return false; } return true; } }