wechat_client.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. package wechat
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "eta/eta_mini_ht_api/common/component/config"
  6. logger "eta/eta_mini_ht_api/common/component/log"
  7. "eta/eta_mini_ht_api/common/component/wechat/we_http"
  8. "eta/eta_mini_ht_api/common/contants"
  9. "eta/eta_mini_ht_api/common/exception"
  10. "eta/eta_mini_ht_api/common/utils/client"
  11. "fmt"
  12. "io"
  13. "io/ioutil"
  14. "net/http"
  15. "net/url"
  16. "sync"
  17. )
  18. const (
  19. baseURL = "https://api.weixin.qq.com"
  20. codeAPI = "/sns/jscode2session"
  21. accessTokenApi = "/cgi-bin/token"
  22. )
  23. const (
  24. // WeChatServerError 微信服务器错误时返回返回消息
  25. )
  26. var (
  27. wechatClient *Client
  28. once sync.Once
  29. )
  30. type Client struct {
  31. // HTTP请求客户端
  32. client *client.HttpClient
  33. // 小程序后台配置: 小程序ID
  34. appid string
  35. // 小程序后台配置: 小程序密钥
  36. secret string
  37. }
  38. func GetInstance() *Client {
  39. once.Do(func() {
  40. wechatConf, ok := config.GetConfig(contants.WECHAT).(*config.WechatConfig)
  41. if !ok {
  42. // 处理错误情况,比如记录日志或返回错误信息
  43. logger.Info("加载wechat配置失败")
  44. return // 或者采取其他适当的错误处理措施
  45. }
  46. // 默认配置
  47. wechatClient = NewClient(wechatConf.GetAppid(), wechatConf.GetSecret(), WithHttpClient(client.DefaultClient()))
  48. })
  49. return wechatClient
  50. }
  51. // AccessTokenGetter 用户自定义获取access_token的方法
  52. type AccessTokenGetter func(appid, secret string) (token string, expireIn uint)
  53. // NewClient 初始化客户端并用自定义配置替换默认配置
  54. func NewClient(appid, secret string, opts ...func(*Client)) *Client {
  55. cli := &Client{
  56. appid: appid,
  57. secret: secret,
  58. }
  59. // 执行额外的配置函数
  60. for _, fn := range opts {
  61. fn(cli)
  62. }
  63. return cli
  64. }
  65. // WithHttpClient 自定义 HTTP Client
  66. func WithHttpClient(hc *client.HttpClient) func(*Client) {
  67. return func(cli *Client) {
  68. cli.client = hc
  69. }
  70. }
  71. type loginResponse struct {
  72. we_http.BaseResponse
  73. we_http.LoginResponse
  74. }
  75. // Login 小程序登录
  76. func (cli *Client) Login(code string) (wxUser WxUser, err error) {
  77. if code == "" {
  78. err = exception.New(exception.WeChatCodeEmpty)
  79. return
  80. }
  81. api, err := code2url(cli.appid, cli.secret, code)
  82. if err != nil {
  83. err = exception.New(exception.WeChatIllegalRequest)
  84. return
  85. }
  86. //请求微信接口
  87. res, err := cli.client.Get(api)
  88. if err != nil || res.StatusCode != 200 {
  89. if res != nil {
  90. logger.Error("获取微信用户信息失败:%v", res.Status)
  91. } else {
  92. logger.Error("获取微信用户信息失败:%v", err)
  93. }
  94. err = exception.New(exception.WeChatServerError)
  95. return
  96. }
  97. defer func(Body io.ReadCloser) {
  98. Body.Close()
  99. }(res.Body)
  100. var data loginResponse
  101. err = json.NewDecoder(res.Body).Decode(&data)
  102. if err != nil {
  103. logger.Error("解析微信应答失败:%v", err)
  104. err = exception.New(exception.WeChatResponseError)
  105. return
  106. }
  107. if data.Errcode != 0 {
  108. logger.Error("获取用户信息失败:%v", data.Errmsg)
  109. err = exception.New(exception.WechatUserInfoFailed)
  110. return
  111. }
  112. wxUser = WxUser{
  113. OpenId: data.LoginResponse.OpenID,
  114. UnionId: data.LoginResponse.UnionID,
  115. }
  116. return
  117. }
  118. type WxUser struct {
  119. OpenId string
  120. UnionId string
  121. }
  122. // 拼接 获取 session_key 的 URL
  123. func code2url(appID, secret, code string) (string, error) {
  124. url, err := url.Parse(baseURL + codeAPI)
  125. if err != nil {
  126. return "", err
  127. }
  128. query := url.Query()
  129. query.Set("appid", appID)
  130. query.Set("secret", secret)
  131. query.Set("js_code", code)
  132. query.Set("grant_type", "authorization_code")
  133. url.RawQuery = query.Encode()
  134. return url.String(), nil
  135. }
  136. func accessToken2url(appID, secret string) (string, error) {
  137. url, err := url.Parse(baseURL + accessTokenApi)
  138. if err != nil {
  139. return "", err
  140. }
  141. query := url.Query()
  142. query.Set("appid", appID)
  143. query.Set("secret", secret)
  144. query.Set("grant_type", "client_credential")
  145. url.RawQuery = query.Encode()
  146. return url.String(), nil
  147. }
  148. type WxUserInfo struct {
  149. OpenId string `json:"openid"`
  150. AccessToken string `json:"access_token"`
  151. RefreshToken string `json:"refresh_token"`
  152. Scope string `json:"scope"`
  153. ErrCode int
  154. ErrMsg string
  155. }
  156. type QRCodeRequest struct {
  157. Page string `json:"page"`
  158. Scene string `json:"scene"`
  159. CheckPath bool `json:"check_path"`
  160. Width int `json:"width,omitempty"`
  161. EnvVersion string `json:"env_version"`
  162. AutoColor bool `json:"auto_color,omitempty"`
  163. LineColor struct {
  164. R int `json:"r"`
  165. G int `json:"g"`
  166. B int `json:"b"`
  167. } `json:"line_color,omitempty"`
  168. IsHyaline bool `json:"is_hyaline,omitempty"`
  169. }
  170. func (cli *Client) GenerateQRCode(code, path string) ([]byte, error) {
  171. wechatConf, ok := config.GetConfig(contants.WECHAT).(*config.WechatConfig)
  172. if !ok {
  173. return nil, exception.New(exception.WeChatIllegalRequest)
  174. }
  175. accessToken, err := getAccessToken(wechatConf.GetAppid(), wechatConf.GetSecret(), code)
  176. if err != nil {
  177. return nil, err
  178. }
  179. api := fmt.Sprintf("%s%s?access_token=%s", baseURL, "/wxa/getwxacodeunlimit", accessToken)
  180. reqBody := QRCodeRequest{
  181. Page: path,
  182. Scene: "id=32",
  183. Width: 1280,
  184. CheckPath: false,
  185. EnvVersion: "develop",
  186. AutoColor: true,
  187. LineColor: struct {
  188. R int `json:"r"`
  189. G int `json:"g"`
  190. B int `json:"b"`
  191. }{
  192. R: 0,
  193. G: 0,
  194. B: 0,
  195. },
  196. IsHyaline: false,
  197. }
  198. jsonData, err := json.Marshal(reqBody)
  199. if err != nil {
  200. return nil, err
  201. }
  202. resp, err := http.Post(api, "application/json", bytes.NewBuffer(jsonData))
  203. if err != nil {
  204. return nil, err
  205. }
  206. defer resp.Body.Close()
  207. if resp.StatusCode != 200 {
  208. body, _ := ioutil.ReadAll(resp.Body)
  209. return nil, fmt.Errorf("failed to generate QR code: %s", body)
  210. }
  211. qrCodeBytes, err := ioutil.ReadAll(resp.Body)
  212. if err != nil {
  213. return nil, err
  214. }
  215. return qrCodeBytes, nil
  216. }
  217. func getAccessToken(appid, secret, code string) (string, error) {
  218. api, err := accessToken2url(appid, secret)
  219. if err != nil {
  220. return "", exception.New(exception.WeChatIllegalRequest)
  221. }
  222. resp, err := http.Get(api)
  223. if err != nil {
  224. return "", err
  225. }
  226. defer resp.Body.Close()
  227. body, err := ioutil.ReadAll(resp.Body)
  228. if err != nil {
  229. return "", err
  230. }
  231. var result map[string]interface{}
  232. err = json.Unmarshal(body, &result)
  233. if err != nil {
  234. return "", err
  235. }
  236. if accessToken, ok := result["access_token"].(string); ok {
  237. return accessToken, nil
  238. }
  239. return "", fmt.Errorf("failed to get access token: %s", string(body))
  240. }
  241. // Login 小程序登录
  242. func (cli *Client) GzhLogin(code string) (wxUser WxUser, err error) {
  243. if code == "" {
  244. err = exception.New(exception.WeChatCodeEmpty)
  245. return
  246. }
  247. api, err := code2url(cli.appid, cli.secret, code)
  248. if err != nil {
  249. err = exception.New(exception.WeChatIllegalRequest)
  250. return
  251. }
  252. //请求微信接口
  253. res, err := cli.client.Get(api)
  254. defer func(Body io.ReadCloser) {
  255. Body.Close()
  256. }(res.Body)
  257. if err != nil || res.StatusCode != 200 {
  258. logger.Error("获取微信用户信息失败:%v", res.Status)
  259. err = exception.New(exception.WeChatServerError)
  260. return
  261. }
  262. var data loginResponse
  263. err = json.NewDecoder(res.Body).Decode(&data)
  264. if err != nil {
  265. logger.Error("解析微信应答失败:%v", err)
  266. err = exception.New(exception.WeChatResponseError)
  267. return
  268. }
  269. fmt.Println(data)
  270. if data.Errcode != 0 {
  271. logger.Error("获取用户信息失败:%v", data.Errmsg)
  272. err = exception.New(exception.WechatUserInfoFailed)
  273. return
  274. }
  275. wxUser = WxUser{
  276. OpenId: data.LoginResponse.OpenID,
  277. UnionId: data.LoginResponse.UnionID,
  278. }
  279. return
  280. }