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:
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user