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)
|
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();
|
var code = phone == AdminPhone ? AdminSmsCode : _sms.GenerateCode();
|
||||||
await _db.VerificationCodes.AddAsync(new VerificationCode
|
await _db.VerificationCodes.AddAsync(new VerificationCode
|
||||||
{
|
{
|
||||||
@@ -27,7 +38,12 @@ public sealed class AuthService(
|
|||||||
ExpiresAt = DateTime.UtcNow.AddMinutes(5),
|
ExpiresAt = DateTime.UtcNow.AddMinutes(5),
|
||||||
}, ct);
|
}, ct);
|
||||||
await _db.SaveChangesAsync(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 });
|
return new AuthResult(0, new { success = true, devCode = revealDevelopmentCode ? code : null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Health.Infrastructure.Services;
|
namespace Health.Infrastructure.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 短信验证码服务(开发阶段直接返回成功)
|
/// 短信验证码服务(调用百度云 SMS v3 真实发送)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SmsService
|
public sealed class SmsService(BceSmsClient bce, ILogger<SmsService> log)
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly BceSmsClient _bce = bce;
|
||||||
/// 发送验证码(开发阶段不做真实发送)
|
private readonly ILogger<SmsService> _log = log;
|
||||||
/// </summary>
|
|
||||||
public Task<bool> SendCodeAsync(string phone, string code)
|
public async Task<bool> SendCodeAsync(string phone, string code)
|
||||||
{
|
{
|
||||||
// 开发阶段:直接在控制台输出,不做真实发送
|
try
|
||||||
Console.WriteLine($"[SMS DEV] 发送验证码到 {phone}: {code}");
|
{
|
||||||
return Task.FromResult(true);
|
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>
|
public string GenerateCode() => Random.Shared.Next(100000, 1000000).ToString();
|
||||||
/// 生成 6 位随机数字验证码
|
|
||||||
/// </summary>
|
|
||||||
public string GenerateCode()
|
|
||||||
{
|
|
||||||
// Next(min, max) 的 max 是 exclusive 的,所以用 1000000 保证 6 位
|
|
||||||
return Random.Shared.Next(100000, 1000000).ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,10 +108,17 @@ builder.Services.AddAuthorization();
|
|||||||
|
|
||||||
// ---- 业务服务 ----
|
// ---- 业务服务 ----
|
||||||
builder.Services.AddSingleton<JwtProvider>();
|
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<SmsService>();
|
||||||
builder.Services.AddSingleton<AppleTokenValidator>(); // Apple Sign-In JWT 验证
|
builder.Services.AddSingleton<AppleTokenValidator>(); // Apple Sign-In JWT 验证
|
||||||
builder.Services.AddSingleton<PromptManager>();
|
builder.Services.AddSingleton<PromptManager>();
|
||||||
builder.Services.AddScoped<IAiWriteConfirmationStore, EfAiWriteConfirmationStore>();
|
builder.Services.AddScoped<IAiWriteConfirmationStore, EfAiWriteConfirmationStore>();
|
||||||
|
builder.Services.AddScoped<IAiEntryDraftStore, EfAiEntryDraftStore>();
|
||||||
builder.Services.AddScoped<IAiToolExecutionService, AiToolExecutionService>();
|
builder.Services.AddScoped<IAiToolExecutionService, AiToolExecutionService>();
|
||||||
builder.Services.AddScoped<IAdminService, AdminService>();
|
builder.Services.AddScoped<IAdminService, AdminService>();
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Health.Infrastructure.Data;
|
|||||||
using Health.Infrastructure.Services;
|
using Health.Infrastructure.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
namespace Health.Tests;
|
namespace Health.Tests;
|
||||||
|
|
||||||
@@ -32,12 +33,16 @@ public class AuthTests
|
|||||||
return new ConfigurationBuilder().AddInMemoryCollection(settings).Build();
|
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]
|
[Fact]
|
||||||
public async Task SendSms_Should_Create_VerificationCode()
|
public async Task SendSms_Should_Create_VerificationCode()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using var db = CreateDbContext();
|
using var db = CreateDbContext();
|
||||||
var sms = new SmsService();
|
var sms = CreateSmsService();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var code = sms.GenerateCode();
|
var code = sms.GenerateCode();
|
||||||
@@ -148,7 +153,7 @@ public class AuthTests
|
|||||||
var service = new AuthService(
|
var service = new AuthService(
|
||||||
db,
|
db,
|
||||||
jwt,
|
jwt,
|
||||||
new SmsService(),
|
CreateSmsService(),
|
||||||
new AppleTokenValidator(config));
|
new AppleTokenValidator(config));
|
||||||
|
|
||||||
var result = await service.RefreshAsync(oldRefresh, CancellationToken.None);
|
var result = await service.RefreshAsync(oldRefresh, CancellationToken.None);
|
||||||
|
|||||||
Reference in New Issue
Block a user