- 新增分销站点独立短信服务商配置(阿里云),登录验证码按 site_code/domain 解析专属通道,未启用或解析失败时回退主站默认通道 - 密钥经 crypto.Encryptor 加密落库,模型字段 json 序列化隔离,DTO 仅回传掩码 - provider 白名单校验,mock 仅在非生产环境放行;启用时强制校验必填凭据 - 配置读写/测试接口限管理员权限,测试发送增加 60s 频控(Redis,缺省进程内) - 解密失败记告警并优雅回退主站,避免把密文当明文密钥使用 - 两套前端新增短信配置入口、必填与手机号校验、发送冷却 - 新增迁移 000071 及加解密、白名单、频控、解析回退等单测
100 lines
2.9 KiB
Go
100 lines
2.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
smsprovider "hfb_sys/backend/internal/integrations/sms"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type recordProvider struct {
|
|
sentPhones []string
|
|
}
|
|
|
|
func (p *recordProvider) SendLoginCode(ctx context.Context, phone string, code string) error {
|
|
p.sentPhones = append(p.sentPhones, phone)
|
|
return nil
|
|
}
|
|
|
|
type mockResolver struct {
|
|
customProvider smsprovider.Provider
|
|
calledSites []string
|
|
}
|
|
|
|
func (m *mockResolver) ResolveSMSProvider(ctx context.Context, siteCode, domain string) (smsprovider.Provider, error) {
|
|
m.calledSites = append(m.calledSites, siteCode)
|
|
if siteCode == "site_custom" {
|
|
return m.customProvider, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func TestSendSMSCode_SiteResolutionAndFallback(t *testing.T) {
|
|
rdb := redis.NewClient(&redis.Options{
|
|
Addr: "127.0.0.1:16379",
|
|
DB: 14,
|
|
})
|
|
ctx := t.Context()
|
|
if err := rdb.Ping(ctx).Err(); err != nil {
|
|
t.Skipf("跳过测试:未检测到测试 Redis: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = rdb.FlushDB(context.Background()).Err()
|
|
_ = rdb.Close()
|
|
})
|
|
_ = rdb.FlushDB(ctx)
|
|
|
|
defaultProv := &recordProvider{}
|
|
customProv := &recordProvider{}
|
|
resolver := &mockResolver{customProvider: customProv}
|
|
|
|
svc := NewService(nil, rdb, nil, defaultProv, zap.NewNop())
|
|
svc.SetSiteSMSResolver(resolver)
|
|
|
|
// 1. 无分站标识 -> 使用默认主站 provider
|
|
if err := rdb.Set(ctx, "captcha:auth:sms:cap1", "ABCD", time.Minute).Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
phone1 := "13800138001"
|
|
err := svc.SendSMSCode(ctx, phone1, "cap1", "ABCD")
|
|
if err != nil {
|
|
t.Fatalf("SendSMSCode failed: %v", err)
|
|
}
|
|
if len(defaultProv.sentPhones) != 1 || defaultProv.sentPhones[0] != phone1 {
|
|
t.Fatalf("expected default provider to send, got %v", defaultProv.sentPhones)
|
|
}
|
|
if len(customProv.sentPhones) != 0 {
|
|
t.Fatalf("custom provider should not be called without site")
|
|
}
|
|
|
|
// 2. 携带自定义短信分站标识 -> 使用 custom provider
|
|
if err := rdb.Set(ctx, "captcha:auth:sms:cap2", "EFGH", time.Minute).Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
phone2 := "13800138002"
|
|
err = svc.SendSMSCode(ctx, phone2, "cap2", "EFGH", "site_custom")
|
|
if err != nil {
|
|
t.Fatalf("SendSMSCode with custom site failed: %v", err)
|
|
}
|
|
if len(customProv.sentPhones) != 1 || customProv.sentPhones[0] != phone2 {
|
|
t.Fatalf("expected custom provider to send, got %v", customProv.sentPhones)
|
|
}
|
|
|
|
// 3. 携带未配置短信分站标识 -> 优雅降级到 default provider
|
|
if err := rdb.Set(ctx, "captcha:auth:sms:cap3", "JKLM", time.Minute).Err(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
phone3 := "13800138003"
|
|
err = svc.SendSMSCode(ctx, phone3, "cap3", "JKLM", "site_fallback")
|
|
if err != nil {
|
|
t.Fatalf("SendSMSCode with fallback site failed: %v", err)
|
|
}
|
|
if len(defaultProv.sentPhones) != 2 || defaultProv.sentPhones[1] != phone3 {
|
|
t.Fatalf("expected default provider to handle fallback, got %v", defaultProv.sentPhones)
|
|
}
|
|
}
|