client.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. package wechat
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/url"
  6. "time"
  7. "git.chuangxin1.com/cx/util"
  8. )
  9. // Client wechat
  10. type Client struct {
  11. AppID string `json:"appid"`
  12. AppSecret string `json:"appsecret"`
  13. Token string `json:"token"`
  14. EncodingAESKey string `json:"encodingaeskey"`
  15. AccessToken string
  16. LastTokenTime int64
  17. Ticket string
  18. LastTicketTime int64
  19. }
  20. // FormAuthorize get code
  21. type FormAuthorize struct {
  22. Code string `form:"code"`
  23. State string `form:"state"`
  24. URL string `form:"url"`
  25. }
  26. // FormOpenID openid
  27. type FormOpenID struct {
  28. OpenID string `form:"openid"`
  29. }
  30. // FormMaterial Material
  31. type FormMaterial struct {
  32. Type string `form:"type"`
  33. Offset int `form:"offset"`
  34. Count int `form:"count"`
  35. }
  36. // UserInfo userinfo
  37. type UserInfo struct {
  38. SubScribe int `json:"subscribe"`
  39. OpenID string `json:"openid"`
  40. NickName string `json:"nickname"`
  41. Sex int `json:"sex"`
  42. Language string `json:"language"`
  43. City string `json:"city"`
  44. Province string `json:"province"`
  45. Country string `json:"country"`
  46. HeadImgURL string `json:"headimgurl"`
  47. SubscribeTime int `json:"subscribe_time"`
  48. UnionID string `json:"unionid"`
  49. Remark string `json:"remark"`
  50. GroupID int `json:"groupid"`
  51. TagidList []int `json:"tagid_list"`
  52. SubscribeScene string `json:"subscribe_scene"`
  53. QrScene int `json:"qr_scene"`
  54. QrSceneStr string `json:"qr_scene_str"`
  55. }
  56. // List openid list
  57. type List struct {
  58. OpenID []string `json:"openid"`
  59. }
  60. // UserList user list
  61. type UserList struct {
  62. Total int `json:"total"`
  63. Count int `json:"count"`
  64. Data List `json:"data"`
  65. NextOpenID string `json:"next_openid"`
  66. }
  67. type newsitem struct {
  68. Title string `json:"title"`
  69. Author string `json:"author"`
  70. Digest string `json:"digest"`
  71. Content string `json:"content"`
  72. ContentSourceURL string `json:"content_source_url"`
  73. ThumbMediaID string `json:"thumb_media_id"`
  74. ShowCoverPic int `json:"show_cover_pic"`
  75. URL string `json:"url"`
  76. ThumbURL string `json:"thumb_url"`
  77. NeedOpenComment int `json:"need_open_comment"`
  78. OnlyFansCanComment int `json:"only_fans_can_comment"`
  79. }
  80. type content struct {
  81. NewsItem []*newsitem `json:"news_item,omitempty"`
  82. CreateTime int `json:"create_time,omitempty"`
  83. UpdateTime int `json:"update_time,omitempty"`
  84. }
  85. type item struct {
  86. MediaID string `json:"media_id"`
  87. Name string `json:"name,omitempty"`
  88. UpdateTime int `json:"update_time"`
  89. URL string `json:"url,omitempty"`
  90. Content *content `json:"content,omitempty"`
  91. }
  92. // Material 素材
  93. type Material struct {
  94. TotalCount int `json:"total_count"`
  95. ItemCount int `json:"item_count"`
  96. Item []item `json:"item"`
  97. }
  98. // MiniProgramPage mini
  99. type MiniProgramPage struct {
  100. AppID string `json:"appid,omitempty"`
  101. PagePath string `json:"pagepath,omitempty"`
  102. }
  103. // ValueColor value color
  104. type ValueColor struct {
  105. Value string `json:"value"`
  106. Color string `json:"color,omitempty"`
  107. }
  108. // TemplateData data
  109. type TemplateData struct {
  110. First *ValueColor `json:"first"`
  111. Keyword1 *ValueColor `json:"keyword1,omitempty"`
  112. Keyword2 *ValueColor `json:"keyword2,omitempty"`
  113. Keyword3 *ValueColor `json:"keyword3,omitempty"`
  114. Keyword4 *ValueColor `json:"keyword4,omitempty"`
  115. Keyword5 *ValueColor `json:"keyword5,omitempty"`
  116. Remark *ValueColor `json:"remark,omitempty"`
  117. }
  118. // TemplateMessage template message
  119. type TemplateMessage struct {
  120. ToUser string `json:"touser"`
  121. TemplateID string `json:"template_id"`
  122. URL string `json:"url"`
  123. MiniProgram *MiniProgramPage `json:"miniprogram,omitempty"`
  124. Data TemplateData `json:"data"`
  125. }
  126. // SignPackage sign package
  127. type SignPackage struct {
  128. AppID string `json:"appId"`
  129. NonceStr string `json:"nonceStr"`
  130. Timestamp int64 `json:"timestamp"`
  131. Signature string `json:"signature"`
  132. URL string `json:"url"`
  133. }
  134. func key(appid string) string {
  135. return "wechat:client:" + appid
  136. }
  137. // NewClient new client
  138. func NewClient(appID, appSecret, token, encodingAESKey string) *Client {
  139. key := key(appID)
  140. if v, ok := cache.Load(key); ok {
  141. return v.(*Client)
  142. }
  143. c := &Client{AppID: appID, AppSecret: appSecret, Token: token, EncodingAESKey: encodingAESKey}
  144. cache.Store(key, c)
  145. return c
  146. }
  147. // getToken get token
  148. func (wc *Client) getToken() (token string, err error) {
  149. now := time.Now().Unix()
  150. if wc.LastTokenTime > 0 {
  151. if now-wc.LastTokenTime < TokenExpires {
  152. token = wc.AccessToken
  153. return
  154. }
  155. }
  156. uri := BaseURL + "/cgi-bin/token?"
  157. args := url.Values{}
  158. args.Add("grant_type", "client_credential")
  159. args.Add("appid", wc.AppID)
  160. args.Add("secret", wc.AppSecret)
  161. uri += args.Encode()
  162. var res Response
  163. if res, err = getJSON(uri); err == nil {
  164. wc.LastTokenTime = now
  165. wc.AccessToken = res.AccessToken
  166. token = wc.AccessToken
  167. key := key(wc.AppID)
  168. cache.Store(key, wc)
  169. }
  170. return
  171. }
  172. // GetCodeURL get code 授权获取 OpenID URL
  173. // /connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
  174. // 转向到
  175. // redirect_uri/?code=CODE&state=STATE
  176. func (wc Client) GetCodeURL(redirectURL, state string) (uri string) {
  177. uri = OpenURL + "/connect/oauth2/authorize?"
  178. args := url.Values{}
  179. args.Add("appid", wc.AppID)
  180. args.Add("redirect_uri", redirectURL)
  181. args.Add("response_type", "code")
  182. args.Add("scope", "snsapi_base")
  183. args.Add("state", state)
  184. uri += args.Encode()
  185. uri += "#wechat_redirect"
  186. return
  187. }
  188. // GetOpenID 获取 OpenID
  189. // /sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
  190. func (wc Client) GetOpenID(code string) (res Response, err error) {
  191. uri := BaseURL + "/sns/oauth2/access_token?"
  192. if wc.AccessToken, err = wc.getToken(); err != nil {
  193. return
  194. }
  195. args := url.Values{}
  196. args.Add("access_token", wc.AccessToken)
  197. args.Add("appid", wc.AppID)
  198. args.Add("secret", wc.AppSecret)
  199. args.Add("code", code)
  200. args.Add("grant_type", "authorization_code")
  201. uri += args.Encode()
  202. res, err = getJSON(uri)
  203. return
  204. }
  205. // GetUserInfo user info
  206. func (wc Client) GetUserInfo(openid string) (res UserInfo, err error) {
  207. uri := BaseURL + "/cgi-bin/user/info?"
  208. if wc.AccessToken, err = wc.getToken(); err != nil {
  209. return
  210. }
  211. args := url.Values{}
  212. args.Add("access_token", wc.AccessToken)
  213. args.Add("openid", openid)
  214. args.Add("lang", "zh_CN")
  215. uri += args.Encode()
  216. var body []byte
  217. body, err = getBody(uri)
  218. if err == nil {
  219. err = json.Unmarshal(body, &res)
  220. }
  221. return
  222. }
  223. // GetUserList user list
  224. func (wc Client) GetUserList(nextOpenID string) (res UserList, err error) {
  225. uri := BaseURL + "/cgi-bin/user/get?"
  226. if wc.AccessToken, err = wc.getToken(); err != nil {
  227. return
  228. }
  229. args := url.Values{}
  230. args.Add("access_token", wc.AccessToken)
  231. args.Add("next_openid", nextOpenID)
  232. uri += args.Encode()
  233. var body []byte
  234. body, err = getBody(uri)
  235. if err == nil {
  236. err = json.Unmarshal(body, &res)
  237. }
  238. return
  239. }
  240. // GetMaterial 永久资料
  241. func (wc Client) GetMaterial(mtype string, offset, count int) (res Material, err error) {
  242. uri := BaseURL + "/cgi-bin/material/batchget_material?"
  243. if wc.AccessToken, err = wc.getToken(); err != nil {
  244. return
  245. }
  246. args := url.Values{}
  247. data := make(map[string]interface{})
  248. args.Add("access_token", wc.AccessToken)
  249. data["type"] = mtype
  250. data["offset"] = offset
  251. data["count"] = count
  252. uri += args.Encode()
  253. var body []byte
  254. params, err := json.Marshal(data)
  255. body, err = postBody(uri, params)
  256. if err == nil {
  257. err = json.Unmarshal(body, &res)
  258. }
  259. return
  260. }
  261. func (wc *Client) getTicket(now int64) (ticket string, err error) {
  262. var res Response
  263. if wc.LastTicketTime > 0 {
  264. if now-wc.LastTokenTime < TokenExpires {
  265. ticket = wc.Ticket
  266. return
  267. }
  268. }
  269. args := url.Values{}
  270. args.Add("access_token", wc.AccessToken)
  271. args.Add("type", "jsapi")
  272. uri := BaseURL + "/cgi-bin/ticket/getticket?"
  273. uri += args.Encode()
  274. if res, err = getJSON(uri); err == nil {
  275. fmt.Println(res)
  276. wc.LastTicketTime = now
  277. wc.Ticket = res.Ticket
  278. key := key(wc.AppID)
  279. cache.Store(key, wc)
  280. ticket = res.Ticket
  281. }
  282. return
  283. }
  284. // GetSignPackage JS 签名
  285. // uri 当前 URL
  286. // nonceStr 随机字符串
  287. func (wc Client) GetSignPackage(uri, nonceStr string) (sign SignPackage, err error) {
  288. if wc.AccessToken, err = wc.getToken(); err != nil {
  289. return
  290. }
  291. var (
  292. s string
  293. unix int64
  294. )
  295. unix = time.Now().Unix()
  296. if wc.Ticket, err = wc.getTicket(unix); err != nil {
  297. return
  298. }
  299. //fmt.Println(wc.Ticket)
  300. s = `jsapi_ticket=` + wc.Ticket
  301. s += `&noncestr=` + nonceStr
  302. s += `&timestamp=` + fmt.Sprintf("%d", unix)
  303. s += `&url=` + uri
  304. //fmt.Println(s)
  305. sign.AppID = wc.AppID
  306. sign.NonceStr = nonceStr
  307. sign.Signature = util.SHA1(s)
  308. sign.Timestamp = unix
  309. sign.URL = uri
  310. return
  311. }
  312. // SendTemplateMessage send template message
  313. // POST /cgi-bin/message/template/send?access_token=ACCESS_TOKEN
  314. func (wc Client) SendTemplateMessage(template TemplateMessage) (res Response, err error) {
  315. uri := BaseURL + "/cgi-bin/message/template/send?"
  316. if wc.AccessToken, err = wc.getToken(); err != nil {
  317. return
  318. }
  319. args := url.Values{}
  320. args.Add("access_token", wc.AccessToken)
  321. uri += args.Encode()
  322. data, err := json.Marshal(template)
  323. if err != nil {
  324. return
  325. }
  326. res, err = postJSON(uri, data)
  327. return
  328. }