package utils import ( . "battle_srv/common" . "battle_srv/configs" "bytes" "crypto/sha1" . "dnmshared" "encoding/json" "fmt" "go.uber.org/zap" "io" "io/ioutil" "math/rand" "net/http" "sort" "time" ) var WechatIns *wechat var WechatGameIns *wechat func InitWechat(conf WechatConfig) { WechatIns = NewWechatIns(&conf, Constants.AuthChannel.Wechat) } func InitWechatGame(conf WechatConfig) { WechatGameIns = NewWechatIns(&conf, Constants.AuthChannel.WechatGame) } func NewWechatIns(conf *WechatConfig, channel int) *wechat { newWechat := &wechat{ config: conf, channel: channel, } return newWechat } const () type wechat struct { config *WechatConfig channel int } // CommonError 微信返回的通用错误json type CommonError struct { ErrCode int64 `json:"errcode"` ErrMsg string `json:"errmsg"` } // ResAccessToken 获取用户授权access_token的返回结果 type resAccessToken struct { CommonError AccessToken string `json:"access_token"` ExpiresIn int64 `json:"expires_in"` RefreshToken string `json:"refresh_token"` OpenID string `json:"openid"` Scope string `json:"scope"` } // Config 返回给用户jssdk配置信息 type JsConfig struct { AppID string `json:"app_id"` Timestamp int64 `json:"timestamp"` NonceStr string `json:"nonce_str"` Signature string `json:"signature"` } // resTicket 请求jsapi_tikcet返回结果 type resTicket struct { CommonError Ticket string `json:"ticket"` ExpiresIn int64 `json:"expires_in"` } func (w *wechat) GetJsConfig(uri string) (config *JsConfig, err error) { config = new(JsConfig) var ticketStr string ticketStr, err = w.getTicket() if err != nil { return } nonceStr := randomStr(16) timestamp := UnixtimeSec() str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri) sigStr := signature(str) config.AppID = w.config.AppID config.NonceStr = nonceStr config.Timestamp = timestamp config.Signature = sigStr return } // TODO add cache, getTicket 获取jsapi_ticket func (w *wechat) getTicket() (ticketStr string, err error) { var ticket resTicket ticket, err = w.getTicketFromServer() if err != nil { return } ticketStr = ticket.Ticket return } func (w *wechat) GetOauth2Basic(authcode string) (result resAccessToken, err error) { var accessTokenURL string if w.channel == Constants.AuthChannel.WechatGame { accessTokenURL = w.config.ApiProtocol + "://" + w.config.ApiGateway + "/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code" } if w.channel == Constants.AuthChannel.Wechat { accessTokenURL = w.config.ApiProtocol + "://" + w.config.ApiGateway + "/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code" } urlStr := fmt.Sprintf(accessTokenURL, w.config.AppID, w.config.AppSecret, authcode) Logger.Info("urlStr", zap.Any(":", urlStr)) response, err := get(urlStr) if err != nil { return } err = json.Unmarshal(response, &result) if err != nil { Logger.Info("GetOauth2Basic marshal error", zap.Any("err", err)) return } if result.ErrCode != 0 { err = fmt.Errorf("GetOauth2Basic error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg) return } return } // UserInfo 用户授权获取到用户信息 type UserInfo struct { CommonError OpenID string `json:"openid"` Nickname string `json:"nickname"` Sex int32 `json:"sex"` Province string `json:"province"` City string `json:"city"` Country string `json:"country"` HeadImgURL string `json:"headimgurl"` Privilege []string `json:"privilege"` Unionid string `json:"unionid"` } func (w *wechat) GetMoreInfo(accessToken string, openId string) (result UserInfo, err error) { userInfoURL := w.config.ApiProtocol + "://" + w.config.ApiGateway + "/sns/userinfo?appid=%s&access_token=%s&openid=%s&lang=zh_CN" urlStr := fmt.Sprintf(userInfoURL, w.config.AppID, accessToken, openId) response, err := get(urlStr) if err != nil { return } err = json.Unmarshal(response, &result) if err != nil { Logger.Info("GetMoreInfo marshal error", zap.Any("err", err)) return } if result.ErrCode != 0 { err = fmt.Errorf("GetMoreInfo error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg) return } return } // HTTPGet get 请求 func get(uri string) ([]byte, error) { response, err := http.Get(uri) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode) } body, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err } return body, err } // PostJSON post json 数据请求 func post(uri string, obj interface{}) ([]byte, error) { jsonData, err := json.Marshal(obj) if err != nil { return nil, err } jsonData = bytes.Replace(jsonData, []byte("\\u003c"), []byte("<"), -1) jsonData = bytes.Replace(jsonData, []byte("\\u003e"), []byte(">"), -1) jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1) body := bytes.NewBuffer(jsonData) response, err := http.Post(uri, "application/json;charset=utf-8", body) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode) } return ioutil.ReadAll(response.Body) } // Signature sha1签名 func signature(params ...string) string { sort.Strings(params) h := sha1.New() for _, s := range params { io.WriteString(h, s) } return fmt.Sprintf("%x", h.Sum(nil)) } // RandomStr 随机生成字符串 func randomStr(length int) string { str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" bytes := []byte(str) result := []byte{} r := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < length; i++ { result = append(result, bytes[r.Intn(len(bytes))]) } return string(result) } // getTicketFromServer 强制从服务器中获取ticket func (w *wechat) getTicketFromServer() (ticket resTicket, err error) { var accessToken string accessToken, err = w.getAccessTokenFromServer() if err != nil { return } getTicketURL := w.config.ApiProtocol + "://" + w.config.ApiGateway + "/cgi-bin/ticket/getticket?access_token=%s&type=jsapi" var response []byte url := fmt.Sprintf(getTicketURL, accessToken) response, err = get(url) err = json.Unmarshal(response, &ticket) if err != nil { return } if ticket.ErrCode != 0 { err = fmt.Errorf("getTicket Error : errcode=%d , errmsg=%s", ticket.ErrCode, ticket.ErrMsg) return } return } // GetAccessTokenFromServer 强制从微信服务器获取token func (w *wechat) getAccessTokenFromServer() (accessToken string, err error) { AccessTokenURL := w.config.ApiProtocol + "://" + w.config.ApiGateway + "/cgi-bin/token" url := fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", AccessTokenURL, w.config.AppID, w.config.AppSecret) var body []byte body, err = get(url) if err != nil { return } var r resAccessToken err = json.Unmarshal(body, &r) if err != nil { return } if r.ErrMsg != "" { err = fmt.Errorf("get access_token error : errcode=%v , errormsg=%v", r.ErrCode, r.ErrMsg) return } accessToken = r.AccessToken return }