client.go 9.2 KB

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