feat: 启用百度云 SMS 真实验证码发送

- SmsService: 从开发桩改为调用 BceSmsClient.SendAsync 真实发送
- AuthService.SendCodeAsync: 加 60 秒冷却 + 发送结果检查(失败返回 40010)
- Program.cs: 注册 BceSmsClient 到 DI(AddHttpClient + 10s 超时)
- auth_tests.cs: 加 CreateSmsService helper 适配构造注入

移动号实测可收到验证码,联通号运营商环节待报备
This commit is contained in:
MingNian
2026-07-28 15:01:00 +08:00
parent 32aae40b12
commit 25a4078688
4 changed files with 49 additions and 20 deletions

View File

@@ -20,6 +20,17 @@ public sealed class AuthService(
public async Task<AuthResult> SendCodeAsync(string phone, bool revealDevelopmentCode, CancellationToken ct)
{
// 60 秒冷却AdminPhone 豁免)
if (phone != AdminPhone)
{
var last = await _db.VerificationCodes
.Where(x => x.Phone == phone)
.OrderByDescending(x => x.CreatedAt)
.FirstOrDefaultAsync(ct);
if (last != null && DateTime.UtcNow - last.CreatedAt < TimeSpan.FromSeconds(60))
return Error(40009, "请求过于频繁,请 1 分钟后重试");
}
var code = phone == AdminPhone ? AdminSmsCode : _sms.GenerateCode();
await _db.VerificationCodes.AddAsync(new VerificationCode
{
@@ -27,7 +38,12 @@ public sealed class AuthService(
ExpiresAt = DateTime.UtcNow.AddMinutes(5),
}, ct);
await _db.SaveChangesAsync(ct);
if (phone != AdminPhone) await _sms.SendCodeAsync(phone, code);
if (phone != AdminPhone)
{
var sent = await _sms.SendCodeAsync(phone, code);
if (!sent) return Error(40010, "短信发送失败,请稍后重试");
}
return new AuthResult(0, new { success = true, devCode = revealDevelopmentCode ? code : null });
}

View File

@@ -1,26 +1,27 @@
using Microsoft.Extensions.Logging;
namespace Health.Infrastructure.Services;
/// <summary>
/// 短信验证码服务(开发阶段直接返回成功
/// 短信验证码服务(调用百度云 SMS v3 真实发送
/// </summary>
public sealed class SmsService
public sealed class SmsService(BceSmsClient bce, ILogger<SmsService> log)
{
/// <summary>
/// 发送验证码(开发阶段不做真实发送)
/// </summary>
public Task<bool> SendCodeAsync(string phone, string code)
private readonly BceSmsClient _bce = bce;
private readonly ILogger<SmsService> _log = log;
public async Task<bool> SendCodeAsync(string phone, string code)
{
// 开发阶段:直接在控制台输出,不做真实发送
Console.WriteLine($"[SMS DEV] 发送验证码到 {phone}: {code}");
return Task.FromResult(true);
try
{
return await _bce.SendAsync(phone, code, CancellationToken.None);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_log.LogError(ex, "百度短信发送异常 phone={Phone}", phone);
return false;
}
}
/// <summary>
/// 生成 6 位随机数字验证码
/// </summary>
public string GenerateCode()
{
// Next(min, max) 的 max 是 exclusive 的,所以用 1000000 保证 6 位
return Random.Shared.Next(100000, 1000000).ToString();
}
public string GenerateCode() => Random.Shared.Next(100000, 1000000).ToString();
}

View File

@@ -108,10 +108,17 @@ builder.Services.AddAuthorization();
// ---- 业务服务 ----
builder.Services.AddSingleton<JwtProvider>();
builder.Services.AddHttpClient<BceSmsClient>(client =>
{
var host = builder.Configuration["BAIDU_SMS_HOST"] ?? "smsv3.bj.baidubce.com";
client.BaseAddress = new Uri($"https://{host}");
client.Timeout = TimeSpan.FromSeconds(10);
});
builder.Services.AddSingleton<SmsService>();
builder.Services.AddSingleton<AppleTokenValidator>(); // Apple Sign-In JWT 验证
builder.Services.AddSingleton<PromptManager>();
builder.Services.AddScoped<IAiWriteConfirmationStore, EfAiWriteConfirmationStore>();
builder.Services.AddScoped<IAiEntryDraftStore, EfAiEntryDraftStore>();
builder.Services.AddScoped<IAiToolExecutionService, AiToolExecutionService>();
builder.Services.AddScoped<IAdminService, AdminService>();
builder.Services.AddScoped<IAuthService, AuthService>();

View File

@@ -5,6 +5,7 @@ using Health.Infrastructure.Data;
using Health.Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
namespace Health.Tests;
@@ -32,12 +33,16 @@ public class AuthTests
return new ConfigurationBuilder().AddInMemoryCollection(settings).Build();
}
private static SmsService CreateSmsService() =>
new(new BceSmsClient(new HttpClient(), new ConfigurationBuilder().Build(), NullLogger<BceSmsClient>.Instance),
NullLogger<SmsService>.Instance);
[Fact]
public async Task SendSms_Should_Create_VerificationCode()
{
// Arrange
using var db = CreateDbContext();
var sms = new SmsService();
var sms = CreateSmsService();
// Act
var code = sms.GenerateCode();
@@ -148,7 +153,7 @@ public class AuthTests
var service = new AuthService(
db,
jwt,
new SmsService(),
CreateSmsService(),
new AppleTokenValidator(config));
var result = await service.RefreshAsync(oldRefresh, CancellationToken.None);