package wechat import ( "context" "crypto/md5" "dsbqj-admin/pkg/cache" "dsbqj-admin/pkg/logger" "dsbqj-admin/pkg/util" "encoding/hex" "fmt" "github.com/goccy/go-json" "github.com/silenceper/wechat" wechatCtx "github.com/silenceper/wechat/context" "os" "sort" "strconv" "strings" "sync/atomic" "time" wechatCache "dsbqj-admin/pkg/cache/wechat" ) var GOpenid OpenidMgr type OpenidMgr struct { V atomic.Value //值 Over int64 //过期时间 } type WechatHelper struct { ctx context.Context Wechat *wechat.Wechat AppId string AppSecret string } const ( subscribeUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" accessTokenURL = "http://fx.wumiao666.com/cp/getaccesstoken" ) type SignResult struct { Param string Line string Sign string } func AppSign(params map[string]string, key string) SignResult { var builder strings.Builder // 1. 排序 key keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) // 2. 拼接 key=value& for _, k := range keys { builder.WriteString(k) builder.WriteString("=") builder.WriteString(params[k]) builder.WriteString("&") } strForWX := builder.String() str := strings.TrimSuffix(strForWX, "&") line := str + key sign := md5Hex(line) return SignResult{ Param: str, Line: line, Sign: strings.ToLower(sign), } } func md5Hex(s string) string { sum := md5.Sum([]byte(s)) return hex.EncodeToString(sum[:]) } type TokenData struct { AccessToken string `json:"accessToken"` ExpiresSecond *int64 `json:"expiresSecond"` } type ResAccessToken struct { Msg string `json:"msg"` Code string `json:"code"` Data *TokenData `json:"data"` } func GetAccessTokenFromServer(ctx *wechatCtx.Context) (resAccessToken ResAccessToken, err error) { var params = map[string]string{} params["platformId"] = os.Getenv("SDK_PLATFORMID") params["appId"] = os.Getenv("SDK_APPID") params["wxAppId"] = os.Getenv("WECHAT_APPID") params["clearCache"] = os.Getenv("SDK_CLEARCACHE") params["requestTime"] = strconv.Itoa(int(util.GetNowSecond())) res := AppSign(params, os.Getenv("SDK_APPKEY")) url := fmt.Sprintf("%s?%s&sign=%s", accessTokenURL, res.Param, res.Sign) var body []byte body, err = util.HTTPGet(url) if err != nil { return } err = json.Unmarshal(body, &resAccessToken) if err != nil { return } if resAccessToken.Code != "0" { err = fmt.Errorf("get access_token error : errcode=%v , errormsg=%v", resAccessToken.Msg) return } accessTokenCacheKey := fmt.Sprintf("access_token_%s", ctx.AppID) var expires int64 = 600 if resAccessToken.Data.ExpiresSecond != nil { expires = *resAccessToken.Data.ExpiresSecond } err = ctx.Cache.Set(accessTokenCacheKey, resAccessToken.Data.AccessToken, time.Duration(expires)*time.Second) return } func MyAccessTokenFunc(ctx *wechatCtx.Context) (accessToken string, err error) { accessTokenCacheKey := fmt.Sprintf("access_token_%s", ctx.AppID) val := ctx.Cache.Get(accessTokenCacheKey) if val != nil { accessToken = val.(string) return } //从微信服务器获取 var resAccessToken ResAccessToken resAccessToken, err = GetAccessTokenFromServer(ctx) if err != nil { return } accessToken = resAccessToken.Data.AccessToken return } func NewWechatHelper() *WechatHelper { appID := os.Getenv("WECHAT_APPID") appSecret := os.Getenv("WECHAT_SECRET") wx := wechat.NewWechat(&wechat.Config{ AppID: appID, AppSecret: appSecret, Cache: wechatCache.NewAccessToken(cache.RedisReport), }) wx.Context.SetGetAccessTokenFunc(MyAccessTokenFunc) return &WechatHelper{ ctx: context.Background(), AppId: appID, AppSecret: appSecret, Wechat: wx, } } type errmsg struct { Errcode int `json:"errcode"` //错误码 Errmsg string `json:"errmsg"` //错误信息 Result struct { Suggest string `json:"suggest"` //建议,有risky、pass、review三种值 Label int `json:"label"` //命中标签枚举值,100 正常;10001 广告;20001 时政;20002 色情;20003 辱骂;20006 违法犯罪;20008 欺诈;20012 低俗;20013 版权;21000 其他 } `json:"result"` } type Subscribe struct { ToUser string `json:"touser"` TemplateId string `json:"template_id"` MiniProgramState string `json:"miniprogram_state"` Lang string `json:"lang"` Data interface{} `json:"data"` } func (this *WechatHelper) SendWechatSubscribe(openid string, template string, msg interface{}) int { token, _ := this.Wechat.GetAccessToken() url := subscribeUrl + token logger.Info("send msg use template %s, url %s", template, url) reqData := Subscribe{ToUser: openid, TemplateId: template, Lang: "zh_CN"} reqData.Data = msg data, _ := json.Marshal(reqData) buf, err := util.HTTPPost(url, string(data)) if err != nil { logger.Info("[ERROR] CheckWechatMsg post err!err:", err.Error()) return 101 } var re = new(errmsg) err = json.Unmarshal(buf, re) if err != nil { logger.Info("[ERROR] CheckWechatMsg Unmarshal err!err:", err) return 103 } if re.Errcode != 0 { if re.Errcode == 40003 || re.Errcode == 43101 { //openid无效,appid与openid不匹配, 用户未订阅消息(用户未在近两小时访问小程序) logger.Info("CheckWechatMsg msg: %s ret: %d errmsg: %s", msg, re.Errcode, re.Errmsg) return 0 } } else { if re.Result.Label != 100 && re.Result.Label != 0 { logger.Info("[ERROR]CheckWechatMsg msg:", msg, " ret:", re.Result.Label, " ,suggest:", re.Result.Suggest) return re.Result.Label } } logger.Info("==============> %v", re) return re.Errcode }