You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

175 lines
4.0 KiB

package sms
import (
"errors"
"fmt"
"strings"
"yunyan/lego/utils/mapstructure"
)
// 短信服务商。Provider 留空按腾讯云处理(兼容加入阿里云之前已落库/落 yaml 的配置)。
const (
ProviderTencent = "tencent"
ProviderAliyun = "aliyun"
)
// 阿里云默认值:地域与模板参数名。
const (
defaultAliRegionId = "cn-hangzhou"
defaultAliTemplateParam = "code"
)
type Option func(*Options)
type Options struct {
Provider string // tencent(默认) | aliyun
// 腾讯云
AppId string
SecretId string
SecretKey string
// 阿里云
AccessKeyId string
AccessKeySecret string
RegionId string // 默认 cn-hangzhou;国际站按需 ap-southeast-1
TemplateParamName string // 模板变量名,默认 code(对应模板里的 ${code})
// 两家共用
SignName string
Template1 string // 国内模板(腾讯 TemplateId / 阿里 TemplateCode)
Template2 string // 国际/港澳台模板
}
func SetProvider(v string) Option {
return func(o *Options) {
o.Provider = v
}
}
func SetAppId(v string) Option {
return func(o *Options) {
o.AppId = v
}
}
func SetSecretId(v string) Option {
return func(o *Options) {
o.SecretId = v
}
}
func KeySecretKey(v string) Option {
return func(o *Options) {
o.SecretKey = v
}
}
func SetAccessKeyId(v string) Option {
return func(o *Options) {
o.AccessKeyId = v
}
}
func SetAccessKeySecret(v string) Option {
return func(o *Options) {
o.AccessKeySecret = v
}
}
func SetRegionId(v string) Option {
return func(o *Options) {
o.RegionId = v
}
}
func SetTemplateParamName(v string) Option {
return func(o *Options) {
o.TemplateParamName = v
}
}
func SetSignName(v string) Option {
return func(o *Options) {
o.SignName = v
}
}
func SetTemplate1(v string) Option {
return func(o *Options) {
o.Template1 = v
}
}
func SetTemplate2(v string) Option {
return func(o *Options) {
o.Template2 = v
}
}
func newOptionsByOption(opts ...Option) (Options, error) {
options := Options{}
for _, o := range opts {
o(&options)
}
options.normalize()
return options, options.validate()
}
// provider 归一化后的服务商(空 → 腾讯云)。
func (o Options) provider() string {
if p := strings.ToLower(strings.TrimSpace(o.Provider)); p != "" {
return p
}
return ProviderTencent
}
// normalize 填充可留空字段的默认值,并把服务商归一化成小写。
func (o *Options) normalize() {
o.Provider = o.provider()
if o.Provider == ProviderAliyun {
if o.RegionId == "" {
o.RegionId = defaultAliRegionId
}
if o.TemplateParamName == "" {
o.TemplateParamName = defaultAliTemplateParam
}
}
}
// validate 校验发短信必需字段,按服务商分别校验。
func (o Options) validate() error {
switch o.provider() {
case ProviderTencent:
if o.SecretId == "" {
return errors.New("sms 配置缺失: SecretId 为空(腾讯云)")
}
if o.SecretKey == "" {
return errors.New("sms 配置缺失: SecretKey 为空(腾讯云)")
}
case ProviderAliyun:
if o.AccessKeyId == "" {
return errors.New("sms 配置缺失: AccessKeyId 为空(阿里云)")
}
if o.AccessKeySecret == "" {
return errors.New("sms 配置缺失: AccessKeySecret 为空(阿里云)")
}
if o.SignName == "" {
return errors.New("sms 配置缺失: SignName 为空(阿里云签名必填)")
}
default:
return fmt.Errorf("sms 配置错误: 未知服务商 Provider=%q(可选 %s / %s)", o.Provider, ProviderTencent, ProviderAliyun)
}
return nil
}
// newOptionsChecked 解析 map 配置并校验,不合法时返回 error 而非 panic。
// 启动(OnInit)与运行时热重载(Reload)共用:配置缺失只应让本子系统降级/本次重载失败,不该打挂进程。
func newOptionsChecked(config map[string]interface{}, opts ...Option) (Options, error) {
options := Options{}
if config != nil {
mapstructure.Decode(config, &options)
}
for _, o := range opts {
o(&options)
}
options.normalize()
return options, options.validate()
}