package weixin import ( "fmt" "io" "net/http" "sync" "time" cache2 "git.u8t.cn/open/gosdk/cache" "github.com/go-redis/redis" uuid2 "github.com/google/uuid" "github.com/tidwall/gjson" ) const ( code2AccessTokenUrl string = "https://api.weixin.qq.com/sns/oauth2/access_token" jscode2SessionUrl string = "https://api.weixin.qq.com/sns/jscode2session" accessTokenUrl string = "https://api.weixin.qq.com/cgi-bin/token" userPhoneNumberUrl string = "https://api.weixin.qq.com/wxa/business/getuserphonenumber" getWxACodeUnLimitUrl string = "https://api.weixin.qq.com/wxa/getwxacodeunlimit" getWxScheme string = "https://api.weixin.qq.com/wxa/generatescheme" code2UserinfoUrl string = "https://api.weixin.qq.com/sns/userinfo" oaQrCodeCreateUrl string = "https://api.weixin.qq.com/cgi-bin/qrcode/create" userInfoByOpenid string = "https://api.weixin.qq.com/cgi-bin/user/info" CreateOaMenu string = "https://api.weixin.qq.com/cgi-bin/menu/create" QueryOaMenu string = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info" DeleteOaMenu string = "https://api.weixin.qq.com/cgi-bin/menu/delete" UploadOaMedia string = "https://api.weixin.qq.com/cgi-bin/media/upload" ) const ( OaMenuTypeClick = "click" OaMenuTypeView = "view" ) type UserInfo struct { Openid string Unionid string AccessToken string Nickname string HeadUrl string Sex int //1:为男性 2位女性 Country string City string Province string } type BaseSdk struct { appid string secret string accessToken string expireIn int64 lock sync.Mutex } type OaMenuButton struct { Type string `json:"type,omitempty"` Name string `json:"name"` Key string `json:"key,omitempty"` Url string `json:"url,omitempty"` SubButton []*OaMenuButton `json:"sub_button"` } func (o *BaseSdk) getAccessToken() (string, error) { o.lock.Lock() defer o.lock.Unlock() if time.Now().Unix() < o.expireIn { return o.accessToken, nil } // 获取一个新token url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", accessTokenUrl, o.appid, o.secret) res, err := http.Get(url) if err != nil { return "", err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return "", err } g := gjson.ParseBytes(body) accessToken := g.Get("access_token").String() if accessToken == "" { return "", fmt.Errorf("%d:%s", g.Get("errcode").Int(), g.Get("errmsg").String()) } o.accessToken = accessToken o.expireIn = time.Now().Unix() + g.Get("expires_in").Int() - 10 return o.accessToken, nil } // redis share version func (o *BaseSdk) getAccessToken2() (string, error) { redix := o.getRedis() tokenKey := fmt.Sprintf("weixin_oa_access_token_v2_%s", o.appid) lockKey := fmt.Sprintf("get_access_token_unique_%s", o.appid) uuid := uuid2.New().String() // get token token := redix.Get(tokenKey).Val() if token != "" { return token, nil } // setEX and expire // if not set key. wait 200ms and get again and retry 5 times if ok := redix.SetNX(lockKey, uuid, 10*time.Second).Val(); !ok { for i := range 5 { time.Sleep(time.Duration(200*(i+2)) * time.Millisecond) if token = redix.Get(tokenKey).Val(); token != "" { return token, nil } } return "", fmt.Errorf(" wait 5 times,but still not get token") } defer func() { if redix.Get(lockKey).Val() == uuid { redix.Del(lockKey) } }() // get new token url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", accessTokenUrl, o.appid, o.secret) res, err := http.Get(url) if err != nil { return "", err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return "", err } g := gjson.ParseBytes(body) token = g.Get("access_token").String() if token == "" { return "", fmt.Errorf("%d:%s", g.Get("errcode").Int(), g.Get("errmsg").String()) } expire := g.Get("expires_in").Int() - 10 redix.Set(tokenKey, token, time.Second*time.Duration(expire)) return token, nil } func (o *BaseSdk) getRedis() *redis.Client { return cache2.GetRedis() } func (o *BaseSdk) getUserInfoByCode(code string, auth bool) (*UserInfo, error) { url := fmt.Sprintf("%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code", code2AccessTokenUrl, o.appid, o.secret, code) res, err := http.Get(url) if err != nil { return nil, err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return nil, err } g := gjson.ParseBytes(body) errcode := g.Get("errcode").Int() if errcode != 0 { return nil, fmt.Errorf("%d:%s", errcode, g.Get("errmsg").String()) } var user UserInfo user.Unionid = g.Get("unionid").String() user.AccessToken = g.Get("access_token").String() user.Openid = g.Get("openid").String() if auth { err = o.getUserInfo(&user) if err != nil { return nil, err } } return &user, nil } func (o *BaseSdk) getUserInfo(user *UserInfo) error { url := fmt.Sprintf("%s?access_token=%s&openid=%s&lang=zh_CN", code2UserinfoUrl, user.AccessToken, user.Openid) res, err := http.Get(url) if err != nil { return err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return err } g := gjson.ParseBytes(body) errcode := g.Get("errcode").Int() if errcode != 0 { return fmt.Errorf("%d:%s", errcode, g.Get("errmsg").String()) } user.Unionid = g.Get("unionid").String() user.Nickname = g.Get("nickname").String() user.HeadUrl = g.Get("headimgurl").String() user.Province = g.Get("province").String() user.City = g.Get("city").String() user.Country = g.Get("country").String() user.Sex = int(g.Get("sex").Int()) return nil }