send_code.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package service
  2. import (
  3. // "apig-sdk/go/core"
  4. "bytes"
  5. "context"
  6. "crypto/sha256"
  7. "crypto/tls"
  8. "encoding/base64"
  9. "fmt"
  10. "io/ioutil"
  11. "math/rand"
  12. "net/http"
  13. "net/url"
  14. "strings"
  15. "time"
  16. "youngee_m_api/consts"
  17. "youngee_m_api/model/system_model"
  18. "youngee_m_api/redis"
  19. uuid "github.com/satori/go.uuid"
  20. )
  21. var SendCode *sendCode
  22. // 无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
  23. const WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""
  24. // 无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
  25. const AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""
  26. func SendCodeInit(config *system_model.Session) {
  27. sendCode := new(sendCode)
  28. sendCode.sessionTTL = time.Duration(config.TTL) * time.Minute
  29. SendCode = sendCode
  30. }
  31. type sendCode struct {
  32. sessionTTL time.Duration
  33. }
  34. func (s *sendCode) GetCode(ctx context.Context) string {
  35. rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
  36. vcode := fmt.Sprintf("%06v", rnd.Int31n(1000000))
  37. return vcode
  38. }
  39. func (s *sendCode) SetSession(ctx context.Context, phone string, vcode string) error {
  40. fmt.Println("set session phone:", phone, vcode, s.sessionTTL)
  41. err := redis.Set(ctx, s.getRedisKey(phone), vcode, s.sessionTTL)
  42. if err != nil {
  43. return err
  44. }
  45. return nil
  46. }
  47. func (s *sendCode) getRedisKey(key string) string {
  48. return fmt.Sprintf("%s%s", consts.SessionRedisPrefix, key)
  49. }
  50. func (s *sendCode) SendCode(ctx context.Context, phone string, vcode string) error {
  51. //必填,请参考"开发准备"获取如下数据,替换为实际值
  52. apiAddress := "https://smsapi.cn-south-1.myhuaweicloud.com:443/sms/batchSendSms/v1" //APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
  53. appKey := "NETTvTJJie9ax03v9K5T4DFB9EV6" //APP_Key
  54. appSecret := "txi9kXIrxW0dVNMyAulrJf7XFNP7" //APP_Secret
  55. sender := "8823022707732" //国内短信签名通道号或国际/港澳台短信通道号
  56. templateId := "7103cdd480d14d0aa8c68954a7dbeb6e" //模板ID
  57. //条件必填,国内短信关注,当templateId指定的模板类型为通用模板时生效且必填,必须是已审核通过的,与模板类型一致的签名名称
  58. //国际/港澳台短信不用关注该参数
  59. signature := "样叽" //签名名称
  60. //必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔
  61. receiver := "+86" + phone //短信接收人号码
  62. //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
  63. statusCallBack := ""
  64. /*
  65. * 选填,使用无变量模板时请赋空值 string templateParas = "";
  66. * 单变量模板示例:模板内容为"您的验证码是${1}"时,templateParas可填写为"[\"369751\"]"
  67. * 双变量模板示例:模板内容为"您有${1}件快递请到${2}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]"
  68. * 模板中的每个变量都必须赋值,且取值不能为空
  69. * 查看更多模板和变量规范:产品介绍>模板和变量规范
  70. */
  71. templateParas := "[\"" + vcode + "\"]" //模板变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。
  72. // templateParas := "[\"12345678\"]" //模板变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。
  73. body := buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, signature)
  74. headers := make(map[string]string)
  75. headers["Content-Type"] = "application/x-www-form-urlencoded"
  76. headers["Authorization"] = AUTH_HEADER_VALUE
  77. headers["X-WSSE"] = buildWsseHeader(appKey, appSecret)
  78. resp, err := post(apiAddress, []byte(body), headers)
  79. if err != nil {
  80. return err
  81. }
  82. fmt.Println(resp)
  83. return nil
  84. }
  85. /**
  86. * sender,receiver,templateId不能为空
  87. */
  88. func buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, signature string) string {
  89. param := "from=" + url.QueryEscape(sender) + "&to=" + url.QueryEscape(receiver) + "&templateId=" + url.QueryEscape(templateId)
  90. if templateParas != "" {
  91. param += "&templateParas=" + url.QueryEscape(templateParas)
  92. }
  93. if statusCallBack != "" {
  94. param += "&statusCallback=" + url.QueryEscape(statusCallBack)
  95. }
  96. if signature != "" {
  97. param += "&signature=" + url.QueryEscape(signature)
  98. }
  99. return param
  100. }
  101. func post(url string, param []byte, headers map[string]string) (string, error) {
  102. tr := &http.Transport{
  103. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  104. }
  105. client := &http.Client{Transport: tr}
  106. req, err := http.NewRequest("POST", url, bytes.NewBuffer(param))
  107. if err != nil {
  108. return "", err
  109. }
  110. for key, header := range headers {
  111. req.Header.Set(key, header)
  112. }
  113. resp, err := client.Do(req)
  114. defer resp.Body.Close()
  115. body, err := ioutil.ReadAll(resp.Body)
  116. if err != nil {
  117. return "", err
  118. }
  119. return string(body), nil
  120. }
  121. func buildWsseHeader(appKey, appSecret string) string {
  122. var cTime = time.Now().Format("2006-01-02T15:04:05Z")
  123. var nonce = uuid.NewV4().String()
  124. nonce = strings.ReplaceAll(nonce, "-", "")
  125. h := sha256.New()
  126. h.Write([]byte(nonce + cTime + appSecret))
  127. passwordDigestBase64Str := base64.StdEncoding.EncodeToString(h.Sum(nil))
  128. return fmt.Sprintf(WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, cTime)
  129. }