send_code.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. package service
  2. import (
  3. // "apig-sdk/go/core"
  4. "bytes"
  5. "context"
  6. "crypto/sha256"
  7. "crypto/tls"
  8. "encoding/base64"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
  13. dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v4/client"
  14. util "github.com/alibabacloud-go/tea-utils/v2/service"
  15. "github.com/alibabacloud-go/tea/tea"
  16. "io/ioutil"
  17. "math/rand"
  18. "net/http"
  19. "net/url"
  20. "strings"
  21. "time"
  22. "youngee_b_api/consts"
  23. "youngee_b_api/db"
  24. "youngee_b_api/model/system_model"
  25. "youngee_b_api/redis"
  26. uuid "github.com/satori/go.uuid"
  27. "github.com/sirupsen/logrus"
  28. )
  29. var SendCode *sendCode
  30. // 无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
  31. const WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""
  32. // 无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
  33. const AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""
  34. func SendCodeInit(config *system_model.Session) {
  35. sendCode := new(sendCode)
  36. sendCode.sessionTTL = time.Duration(config.TTL) * time.Minute
  37. SendCode = sendCode
  38. }
  39. type sendCode struct {
  40. sessionTTL time.Duration
  41. }
  42. func (s *sendCode) GetCode(ctx context.Context) string {
  43. rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
  44. vcode := fmt.Sprintf("%06v", rnd.Int31n(1000000))
  45. return vcode
  46. }
  47. func (s *sendCode) SetSession(ctx context.Context, phone string, vcode string) error {
  48. err := redis.Set(ctx, s.getRedisKey(phone), vcode, s.sessionTTL)
  49. if err != nil {
  50. return err
  51. }
  52. return nil
  53. }
  54. func (s *sendCode) getRedisKey(key string) string {
  55. return fmt.Sprintf("%s%s", consts.SessionRedisPrefix, key)
  56. }
  57. func (s *sendCode) GetEmailByPhone(ctx context.Context, phone string) (string, error) {
  58. user, err := db.GetUserByPhone(ctx, phone)
  59. fmt.Println("send_code", user, err)
  60. if err != nil {
  61. return "", err
  62. } else if user == nil {
  63. // 账号不存在
  64. logrus.Debugf("[SendCode] sendcode fail,phone:%+v", phone)
  65. return "账号不存在", errors.New("sendcode fail")
  66. }
  67. return user.Email, nil
  68. }
  69. // func (s *sendCode) SendCode(ctx context.Context, phone string, vcode string) error {
  70. // signer := core.Signer{
  71. // Key: "9a9a78319abd43348b43ec59d23b44bb",
  72. // Secret: "ed588c47c681417fabe13c612dcfb046",
  73. // }
  74. // templateId := "JM1000345"
  75. // url := fmt.Sprintf("https://smssend.apistore.huaweicloud.com/sms/send?receive=" + phone + "&templateId=" + templateId + "&values=" + vcode)
  76. // fmt.Printf("url: %+v\n", url)
  77. // r, _ := http.NewRequest("POST", url,
  78. // ioutil.NopCloser(bytes.NewBuffer([]byte("foo=bar"))))
  79. // r.Header.Add("x-stage", "RELEASE")
  80. // signer.Sign(r)
  81. // fmt.Printf("request: %+v\n", r)
  82. // resp, err := http.DefaultClient.Do(r)
  83. // fmt.Printf("resp: %+v\n", resp)
  84. // if err != nil {
  85. // return err
  86. // } else {
  87. // body, _ := ioutil.ReadAll(resp.Body)
  88. // fmt.Printf("resp: %+v\n", body)
  89. // }
  90. // return nil
  91. // }
  92. func (s *sendCode) SendCodeT(ctx context.Context, phone string, code string) error {
  93. // 1. 创建客户端
  94. config := &openapi.Config{
  95. AccessKeyId: tea.String("LTAI5tDfTQeZ2ufN8mhsWQNA"),
  96. AccessKeySecret: tea.String("DKLd2hGbmJPcNki8p6VlU6mV2HXVvO"),
  97. Endpoint: tea.String("dysmsapi.aliyuncs.com"),
  98. }
  99. client, err := dysmsapi20170525.NewClient(config)
  100. if err != nil {
  101. return fmt.Errorf("创建客户端失败: %v", err)
  102. }
  103. // 2. 准备请求
  104. sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
  105. PhoneNumbers: tea.String(phone),
  106. SignName: tea.String("北京智子时空科技"),
  107. TemplateCode: tea.String("SMS_481650308"),
  108. TemplateParam: tea.String(fmt.Sprintf(`{"code":"%s"}`, code)),
  109. }
  110. // 3. 发送请求
  111. runtime := &util.RuntimeOptions{}
  112. resp, err := client.SendSmsWithOptions(sendSmsRequest, runtime)
  113. if err != nil {
  114. // 错误处理
  115. if sdkErr, ok := err.(*tea.SDKError); ok {
  116. var data interface{}
  117. if err := json.NewDecoder(strings.NewReader(tea.StringValue(sdkErr.Data))).Decode(&data); err == nil {
  118. if m, ok := data.(map[string]interface{}); ok {
  119. if recommend, exists := m["Recommend"]; exists {
  120. return fmt.Errorf("短信发送失败: %s, 建议: %v", tea.StringValue(sdkErr.Message), recommend)
  121. }
  122. }
  123. }
  124. return fmt.Errorf("短信发送失败: %s", tea.StringValue(sdkErr.Message))
  125. }
  126. return fmt.Errorf("短信发送失败: %v", err)
  127. }
  128. // 4. 响应
  129. fmt.Printf("短信发送成功,响应: %v\n", resp)
  130. return nil
  131. }
  132. func (s *sendCode) SendCode(ctx context.Context, phone string, vcode string) error {
  133. //必填,请参考"开发准备"获取如下数据,替换为实际值
  134. apiAddress := "https://smsapi.cn-south-1.myhuaweicloud.com:443/sms/batchSendSms/v1" //APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
  135. appKey := "NETTvTJJie9ax03v9K5T4DFB9EV6" //APP_Key
  136. appSecret := "txi9kXIrxW0dVNMyAulrJf7XFNP7" //APP_Secret
  137. sender := "8823022707732" //国内短信签名通道号或国际/港澳台短信通道号
  138. templateId := "7103cdd480d14d0aa8c68954a7dbeb6e" //模板ID
  139. //条件必填,国内短信关注,当templateId指定的模板类型为通用模板时生效且必填,必须是已审核通过的,与模板类型一致的签名名称
  140. //国际/港澳台短信不用关注该参数
  141. signature := "样叽" //签名名称
  142. //必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔
  143. receiver := "+86" + phone //短信接收人号码
  144. //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
  145. statusCallBack := ""
  146. /*
  147. * 选填,使用无变量模板时请赋空值 string templateParas = "";
  148. * 单变量模板示例:模板内容为"您的验证码是${1}"时,templateParas可填写为"[\"369751\"]"
  149. * 双变量模板示例:模板内容为"您有${1}件快递请到${2}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]"
  150. * 模板中的每个变量都必须赋值,且取值不能为空
  151. * 查看更多模板和变量规范:产品介绍>模板和变量规范
  152. */
  153. templateParas := "[\"" + vcode + "\"]" //模板变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。
  154. // templateParas := "[\"12345678\"]" //模板变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。
  155. body := buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, signature)
  156. headers := make(map[string]string)
  157. headers["Content-Type"] = "application/x-www-form-urlencoded"
  158. headers["Authorization"] = AUTH_HEADER_VALUE
  159. headers["X-WSSE"] = buildWsseHeader(appKey, appSecret)
  160. resp, err := post(apiAddress, []byte(body), headers)
  161. if err != nil {
  162. return err
  163. }
  164. fmt.Println(resp)
  165. return nil
  166. }
  167. /**
  168. * sender,receiver,templateId不能为空
  169. */
  170. func buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, signature string) string {
  171. param := "from=" + url.QueryEscape(sender) + "&to=" + url.QueryEscape(receiver) + "&templateId=" + url.QueryEscape(templateId)
  172. if templateParas != "" {
  173. param += "&templateParas=" + url.QueryEscape(templateParas)
  174. }
  175. if statusCallBack != "" {
  176. param += "&statusCallback=" + url.QueryEscape(statusCallBack)
  177. }
  178. if signature != "" {
  179. param += "&signature=" + url.QueryEscape(signature)
  180. }
  181. return param
  182. }
  183. func post(url string, param []byte, headers map[string]string) (string, error) {
  184. tr := &http.Transport{
  185. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  186. }
  187. client := &http.Client{Transport: tr}
  188. req, err := http.NewRequest("POST", url, bytes.NewBuffer(param))
  189. if err != nil {
  190. return "", err
  191. }
  192. for key, header := range headers {
  193. req.Header.Set(key, header)
  194. }
  195. resp, err := client.Do(req)
  196. defer resp.Body.Close()
  197. body, err := ioutil.ReadAll(resp.Body)
  198. if err != nil {
  199. return "", err
  200. }
  201. return string(body), nil
  202. }
  203. func buildWsseHeader(appKey, appSecret string) string {
  204. var cTime = time.Now().Format("2006-01-02T15:04:05Z")
  205. var nonce = uuid.NewV4().String()
  206. nonce = strings.ReplaceAll(nonce, "-", "")
  207. h := sha256.New()
  208. h.Write([]byte(nonce + cTime + appSecret))
  209. passwordDigestBase64Str := base64.StdEncoding.EncodeToString(h.Sum(nil))
  210. return fmt.Sprintf(WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, cTime)
  211. }