wechatHelper.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package wechat
  2. import (
  3. "context"
  4. "crypto/md5"
  5. "dsbqj-admin/pkg/cache"
  6. "dsbqj-admin/pkg/logger"
  7. "dsbqj-admin/pkg/util"
  8. "encoding/hex"
  9. "fmt"
  10. "github.com/goccy/go-json"
  11. "github.com/silenceper/wechat"
  12. wechatCtx "github.com/silenceper/wechat/context"
  13. "os"
  14. "sort"
  15. "strconv"
  16. "strings"
  17. "sync/atomic"
  18. "time"
  19. wechatCache "dsbqj-admin/pkg/cache/wechat"
  20. )
  21. var GOpenid OpenidMgr
  22. type OpenidMgr struct {
  23. V atomic.Value //值
  24. Over int64 //过期时间
  25. }
  26. type WechatHelper struct {
  27. ctx context.Context
  28. Wechat *wechat.Wechat
  29. AppId string
  30. AppSecret string
  31. }
  32. const (
  33. subscribeUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token="
  34. accessTokenURL = "http://fx.wumiao666.com/cp/getaccesstoken"
  35. )
  36. type SignResult struct {
  37. Param string
  38. Line string
  39. Sign string
  40. }
  41. func AppSign(params map[string]string, key string) SignResult {
  42. var builder strings.Builder
  43. // 1. 排序 key
  44. keys := make([]string, 0, len(params))
  45. for k := range params {
  46. keys = append(keys, k)
  47. }
  48. sort.Strings(keys)
  49. // 2. 拼接 key=value&
  50. for _, k := range keys {
  51. builder.WriteString(k)
  52. builder.WriteString("=")
  53. builder.WriteString(params[k])
  54. builder.WriteString("&")
  55. }
  56. strForWX := builder.String()
  57. str := strings.TrimSuffix(strForWX, "&")
  58. line := str + key
  59. sign := md5Hex(line)
  60. return SignResult{
  61. Param: str,
  62. Line: line,
  63. Sign: strings.ToLower(sign),
  64. }
  65. }
  66. func md5Hex(s string) string {
  67. sum := md5.Sum([]byte(s))
  68. return hex.EncodeToString(sum[:])
  69. }
  70. type TokenData struct {
  71. AccessToken string `json:"accessToken"`
  72. ExpiresSecond *int64 `json:"expiresSecond"`
  73. }
  74. type ResAccessToken struct {
  75. Msg string `json:"msg"`
  76. Code string `json:"code"`
  77. Data *TokenData `json:"data"`
  78. }
  79. type ResAccessToken2 struct {
  80. Msg string `json:"msg"`
  81. Code string `json:"code"`
  82. Data map[string]interface{} `json:"data"`
  83. }
  84. func GetAccessTokenFromServer(ctx *wechatCtx.Context) (resAccessToken ResAccessToken, err error) {
  85. var params = map[string]string{}
  86. params["platformId"] = os.Getenv("SDK_PLATFORMID")
  87. params["appId"] = os.Getenv("SDK_APPID")
  88. params["wxAppId"] = os.Getenv("WECHAT_APPID")
  89. params["clearCache"] = "1"
  90. params["requestTime"] = strconv.Itoa(int(util.GetNowSecond()))
  91. res := AppSign(params, os.Getenv("SDK_APPKEY"))
  92. url := fmt.Sprintf("%s?%s&sign=%s", accessTokenURL, res.Param, res.Sign)
  93. var body []byte
  94. body, err = util.HTTPGet(url)
  95. if err != nil {
  96. return
  97. }
  98. var temp1 map[string]interface{}
  99. err = json.Unmarshal(body, &temp1)
  100. var temp ResAccessToken2
  101. err = json.Unmarshal(body, &temp)
  102. err = json.Unmarshal(body, &resAccessToken)
  103. if err != nil {
  104. return
  105. }
  106. if resAccessToken.Code != "0" {
  107. err = fmt.Errorf("get access_token error : errcode=%v , errormsg=%v", resAccessToken.Msg)
  108. return
  109. }
  110. accessTokenCacheKey := fmt.Sprintf("access_token_%s", ctx.AppID)
  111. var expires int64 = 600
  112. if resAccessToken.Data.ExpiresSecond != nil {
  113. expires = *resAccessToken.Data.ExpiresSecond
  114. }
  115. err = ctx.Cache.Set(accessTokenCacheKey, resAccessToken.Data.AccessToken, time.Duration(expires)*time.Second)
  116. return
  117. }
  118. func MyAccessTokenFunc(ctx *wechatCtx.Context) (accessToken string, err error) {
  119. accessTokenCacheKey := fmt.Sprintf("access_token_%s", ctx.AppID)
  120. val := ctx.Cache.Get(accessTokenCacheKey)
  121. if val != nil {
  122. accessToken = val.(string)
  123. return
  124. }
  125. //从微信服务器获取
  126. var resAccessToken ResAccessToken
  127. resAccessToken, err = GetAccessTokenFromServer(ctx)
  128. if err != nil {
  129. return
  130. }
  131. accessToken = resAccessToken.Data.AccessToken
  132. return
  133. }
  134. func NewWechatHelper() *WechatHelper {
  135. appID := os.Getenv("WECHAT_APPID")
  136. appSecret := os.Getenv("WECHAT_SECRET")
  137. wx := wechat.NewWechat(&wechat.Config{
  138. AppID: appID,
  139. AppSecret: appSecret,
  140. Cache: wechatCache.NewAccessToken(cache.RedisReport),
  141. })
  142. wx.Context.SetGetAccessTokenFunc(MyAccessTokenFunc)
  143. return &WechatHelper{
  144. ctx: context.Background(),
  145. AppId: appID,
  146. AppSecret: appSecret,
  147. Wechat: wx,
  148. }
  149. }
  150. type errmsg struct {
  151. Errcode int `json:"errcode"` //错误码
  152. Errmsg string `json:"errmsg"` //错误信息
  153. Result struct {
  154. Suggest string `json:"suggest"` //建议,有risky、pass、review三种值
  155. Label int `json:"label"` //命中标签枚举值,100 正常;10001 广告;20001 时政;20002 色情;20003 辱骂;20006 违法犯罪;20008 欺诈;20012 低俗;20013 版权;21000 其他
  156. } `json:"result"`
  157. }
  158. type Subscribe struct {
  159. ToUser string `json:"touser"`
  160. TemplateId string `json:"template_id"`
  161. MiniProgramState string `json:"miniprogram_state"`
  162. Lang string `json:"lang"`
  163. Data interface{} `json:"data"`
  164. }
  165. func (this *WechatHelper) SendWechatSubscribe(openid string, template string, msg interface{}) int {
  166. token, _ := this.Wechat.GetAccessToken()
  167. url := subscribeUrl + token
  168. logger.Info("send msg use template %s, url %s", template, url)
  169. reqData := Subscribe{ToUser: openid, TemplateId: template, Lang: "zh_CN"}
  170. reqData.Data = msg
  171. data, _ := json.Marshal(reqData)
  172. buf, err := util.HTTPPost(url, string(data))
  173. if err != nil {
  174. logger.Info("[ERROR] CheckWechatMsg post err!err:", err.Error())
  175. return 101
  176. }
  177. var re = new(errmsg)
  178. err = json.Unmarshal(buf, re)
  179. if err != nil {
  180. logger.Info("[ERROR] CheckWechatMsg Unmarshal err!err:", err)
  181. return 103
  182. }
  183. if re.Errcode != 0 {
  184. if re.Errcode == 40003 || re.Errcode == 43101 { //openid无效,appid与openid不匹配, 用户未订阅消息(用户未在近两小时访问小程序)
  185. logger.Info("CheckWechatMsg msg: %s ret: %d errmsg: %s", msg, re.Errcode, re.Errmsg)
  186. return 0
  187. }
  188. } else {
  189. if re.Result.Label != 100 && re.Result.Label != 0 {
  190. logger.Info("[ERROR]CheckWechatMsg msg:", msg, " ret:", re.Result.Label, " ,suggest:", re.Result.Suggest)
  191. return re.Result.Label
  192. }
  193. }
  194. logger.Info("==============> %v", re)
  195. return re.Errcode
  196. }