调整项目结构,factory只负责暴露方法,不实现业务细节

This commit is contained in:
2025-12-07 00:04:01 +08:00
parent b66f345281
commit 339920a940
23 changed files with 2165 additions and 1231 deletions

View File

@@ -16,59 +16,6 @@ import (
"git.toowon.com/jimmy/go-common/config"
)
// SMS 短信发送器
type SMS struct {
config *config.SMSConfig
}
// NewSMS 创建短信发送器
func NewSMS(cfg *config.SMSConfig) (*SMS, error) {
if cfg == nil {
return nil, fmt.Errorf("SMS config is nil")
}
if cfg.AccessKeyID == "" {
return nil, fmt.Errorf("AccessKeyID is required")
}
if cfg.AccessKeySecret == "" {
return nil, fmt.Errorf("AccessKeySecret is required")
}
if cfg.SignName == "" {
return nil, fmt.Errorf("SignName is required")
}
// 设置默认值
if cfg.Region == "" {
cfg.Region = "cn-hangzhou"
}
if cfg.Timeout == 0 {
cfg.Timeout = 10
}
return &SMS{
config: cfg,
}, nil
}
// SendRequest 发送短信请求
type SendRequest struct {
// PhoneNumbers 手机号列表
PhoneNumbers []string
// TemplateCode 模板代码(如果为空,使用配置中的模板代码)
TemplateCode string
// TemplateParam 模板参数可以是map或JSON字符串
// 如果是map会自动转换为JSON字符串
// 如果是string直接使用必须是有效的JSON字符串
TemplateParam interface{}
// SignName 签名(如果为空,使用配置中的签名)
SignName string
}
// SendResponse 发送短信响应
type SendResponse struct {
// RequestID 请求ID
@@ -84,48 +31,123 @@ type SendResponse struct {
BizID string `json:"BizId"`
}
// SendRaw 发送原始请求(允许外部完全控制请求参数)
// params: 请求参数map工具只负责添加必要的系统参数如签名、时间戳等并发送
func (s *SMS) SendRaw(params map[string]string) (*SendResponse, error) {
if params == nil {
params = make(map[string]string)
// SMS 短信发送器
type SMS struct {
config *config.SMSConfig
}
// NewSMS 创建短信发送器
func NewSMS(cfg *config.Config) *SMS {
if cfg == nil || cfg.SMS == nil {
return &SMS{config: nil}
}
return &SMS{config: cfg.SMS}
}
// getSMSConfig 获取短信配置(内部方法)
func (s *SMS) getSMSConfig() (*config.SMSConfig, error) {
if s.config == nil {
return nil, fmt.Errorf("SMS config is nil")
}
// 确保必要的系统参数存在
if params["Action"] == "" {
params["Action"] = "SendSms"
if s.config.AccessKeyID == "" {
return nil, fmt.Errorf("AccessKeyID is required")
}
if params["Version"] == "" {
params["Version"] = "2017-05-25"
if s.config.AccessKeySecret == "" {
return nil, fmt.Errorf("AccessKeySecret is required")
}
if params["RegionId"] == "" {
params["RegionId"] = s.config.Region
if s.config.SignName == "" {
return nil, fmt.Errorf("SignName is required")
}
if params["AccessKeyId"] == "" {
params["AccessKeyId"] = s.config.AccessKeyID
// 设置默认值
if s.config.Region == "" {
s.config.Region = "cn-hangzhou"
}
if params["Format"] == "" {
params["Format"] = "JSON"
if s.config.Timeout == 0 {
s.config.Timeout = 10
}
if params["SignatureMethod"] == "" {
params["SignatureMethod"] = "HMAC-SHA1"
return s.config, nil
}
// SendSMS 发送短信
// phoneNumbers: 手机号列表
// templateParam: 模板参数map或JSON字符串
// templateCode: 模板代码(可选,如果为空使用配置中的模板代码)
func (s *SMS) SendSMS(phoneNumbers []string, templateParam interface{}, templateCode ...string) (*SendResponse, error) {
cfg, err := s.getSMSConfig()
if err != nil {
return nil, err
}
if params["SignatureVersion"] == "" {
params["SignatureVersion"] = "1.0"
if len(phoneNumbers) == 0 {
return nil, fmt.Errorf("phone numbers are required")
}
if params["SignatureNonce"] == "" {
params["SignatureNonce"] = fmt.Sprint(time.Now().UnixNano())
// 使用配置中的模板代码(如果请求中未指定)
templateCodeValue := ""
if len(templateCode) > 0 && templateCode[0] != "" {
templateCodeValue = templateCode[0]
} else {
templateCodeValue = cfg.TemplateCode
}
if params["Timestamp"] == "" {
params["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
if templateCodeValue == "" {
return nil, fmt.Errorf("template code is required")
}
signName := cfg.SignName
// 处理模板参数
var templateParamJSON string
if templateParam != nil {
switch v := templateParam.(type) {
case string:
// 直接使用字符串必须是有效的JSON
templateParamJSON = v
case map[string]string:
// 转换为JSON字符串
paramBytes, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("failed to marshal template param: %w", err)
}
templateParamJSON = string(paramBytes)
default:
// 尝试JSON序列化
paramBytes, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("failed to marshal template param: %w", err)
}
templateParamJSON = string(paramBytes)
}
} else {
templateParamJSON = "{}"
}
// 构建请求参数
params := make(map[string]string)
params["Action"] = "SendSms"
params["Version"] = "2017-05-25"
params["RegionId"] = cfg.Region
params["AccessKeyId"] = cfg.AccessKeyID
params["Format"] = "JSON"
params["SignatureMethod"] = "HMAC-SHA1"
params["SignatureVersion"] = "1.0"
params["SignatureNonce"] = fmt.Sprint(time.Now().UnixNano())
params["Timestamp"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
params["PhoneNumbers"] = strings.Join(phoneNumbers, ",")
params["SignName"] = signName
params["TemplateCode"] = templateCodeValue
params["TemplateParam"] = templateParamJSON
// 计算签名
signature := s.calculateSignature(params, "POST")
signature := s.calculateSignature(params, "POST", cfg.AccessKeySecret)
params["Signature"] = signature
// 构建请求URL
endpoint := s.config.Endpoint
endpoint := cfg.Endpoint
if endpoint == "" {
endpoint = "https://dysmsapi.aliyuncs.com"
}
@@ -145,7 +167,7 @@ func (s *SMS) SendRaw(params map[string]string) (*SendResponse, error) {
httpReq.Header.Set("Accept", "application/json")
client := &http.Client{
Timeout: time.Duration(s.config.Timeout) * time.Second,
Timeout: time.Duration(cfg.Timeout) * time.Second,
}
resp, err := client.Do(httpReq)
@@ -174,70 +196,8 @@ func (s *SMS) SendRaw(params map[string]string) (*SendResponse, error) {
return &sendResp, nil
}
// Send 发送短信使用SendRequest结构
// 注意如果需要完全控制请求参数请使用SendRaw方法
func (s *SMS) Send(req *SendRequest) (*SendResponse, error) {
if req == nil {
return nil, fmt.Errorf("request is nil")
}
if len(req.PhoneNumbers) == 0 {
return nil, fmt.Errorf("phone numbers are required")
}
// 使用配置中的模板代码和签名(如果请求中未指定)
templateCode := req.TemplateCode
if templateCode == "" {
templateCode = s.config.TemplateCode
}
if templateCode == "" {
return nil, fmt.Errorf("template code is required")
}
signName := req.SignName
if signName == "" {
signName = s.config.SignName
}
// 处理模板参数
var templateParamJSON string
if req.TemplateParam != nil {
switch v := req.TemplateParam.(type) {
case string:
// 直接使用字符串必须是有效的JSON
templateParamJSON = v
case map[string]string:
// 转换为JSON字符串
paramBytes, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("failed to marshal template param: %w", err)
}
templateParamJSON = string(paramBytes)
default:
// 尝试JSON序列化
paramBytes, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("failed to marshal template param: %w", err)
}
templateParamJSON = string(paramBytes)
}
} else {
templateParamJSON = "{}"
}
// 构建请求参数
params := make(map[string]string)
params["PhoneNumbers"] = strings.Join(req.PhoneNumbers, ",")
params["SignName"] = signName
params["TemplateCode"] = templateCode
params["TemplateParam"] = templateParamJSON
// 使用SendRaw发送
return s.SendRaw(params)
}
// calculateSignature 计算签名
func (s *SMS) calculateSignature(params map[string]string, method string) string {
func (s *SMS) calculateSignature(params map[string]string, method, accessKeySecret string) string {
// 对参数进行排序
keys := make([]string, 0, len(params))
for k := range params {
@@ -260,31 +220,10 @@ func (s *SMS) calculateSignature(params map[string]string, method string) string
stringToSign := method + "&" + url.QueryEscape("/") + "&" + url.QueryEscape(queryString)
// 计算HMAC-SHA1签名
mac := hmac.New(sha1.New, []byte(s.config.AccessKeySecret+"&"))
mac := hmac.New(sha1.New, []byte(accessKeySecret+"&"))
mac.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return signature
}
// SendSimple 发送简单短信(便捷方法)
// phoneNumbers: 手机号列表
// templateParam: 模板参数
func (s *SMS) SendSimple(phoneNumbers []string, templateParam map[string]string) (*SendResponse, error) {
return s.Send(&SendRequest{
PhoneNumbers: phoneNumbers,
TemplateParam: templateParam,
})
}
// SendWithTemplate 使用指定模板发送短信(便捷方法)
// phoneNumbers: 手机号列表
// templateCode: 模板代码
// templateParam: 模板参数
func (s *SMS) SendWithTemplate(phoneNumbers []string, templateCode string, templateParam map[string]string) (*SendResponse, error) {
return s.Send(&SendRequest{
PhoneNumbers: phoneNumbers,
TemplateCode: templateCode,
TemplateParam: templateParam,
})
}