wxapi.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package wxapi
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "strings"
  10. "sync"
  11. "time"
  12. )
  13. const (
  14. GetTokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET"
  15. GetServiceUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/account/list?access_token=ACCESS_TOKEN"
  16. GetServicerUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/servicer/list?access_token=ACCESS_TOKEN&open_kfid=OPEN_KFID"
  17. GetStaffUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID"
  18. GetCustomerUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/customer/batchget?access_token=ACCESS_TOKEN"
  19. GetMsgUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=ACCESS_TOKEN"
  20. GetSessionStateUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/get?access_token=ACCESS_TOKEN"
  21. UpdateSessionStateUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=ACCESS_TOKEN"
  22. SendMsgOnEventUrl = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg_on_event?access_token=ACCESS_TOKEN"
  23. )
  24. // 自定义封装企业微信的API接口
  25. type WxApi struct {
  26. lock sync.Mutex
  27. corpid string
  28. corpsecret string
  29. accessToken string
  30. tokenExpiresTime int64
  31. expiresIn int64
  32. }
  33. func NewWxApi(corpid, corpsecret string) *WxApi {
  34. return &WxApi{corpid: corpid, corpsecret: corpsecret}
  35. }
  36. // 刷新token
  37. func (a *WxApi) refreshAccessToken() error {
  38. if a.accessToken == "" || (a.tokenExpiresTime-time.Now().Unix()) < (a.expiresIn/10) {
  39. a.lock.Lock()
  40. defer a.lock.Unlock()
  41. if !((a.tokenExpiresTime - time.Now().Unix()) < (a.expiresIn / 10)) {
  42. return nil
  43. }
  44. url := GetTokenUrl
  45. url = strings.ReplaceAll(url, "ID", a.corpid)
  46. url = strings.ReplaceAll(url, "SECRET", a.corpsecret)
  47. resp, err := http.Get(url)
  48. if err != nil {
  49. return err
  50. }
  51. defer resp.Body.Close()
  52. bts, err := ioutil.ReadAll(resp.Body)
  53. if err != nil {
  54. return err
  55. }
  56. var data TokenResp
  57. err = json.Unmarshal(bts, &data)
  58. if err != nil {
  59. return err
  60. }
  61. if data.Errcode != 0 {
  62. return errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg))
  63. }
  64. a.accessToken = data.AccessToken
  65. a.expiresIn = data.ExpiresIn
  66. a.tokenExpiresTime = time.Now().Add(time.Duration(data.ExpiresIn) * time.Second).Unix()
  67. }
  68. return nil
  69. }
  70. // GetServiceList 获取客服账号列表
  71. func (a *WxApi) GetServiceList() (err error, list []ServiceAccount) {
  72. err = a.refreshAccessToken()
  73. if err != nil {
  74. return
  75. }
  76. url := GetServiceUrl
  77. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  78. res, err := http.Post(url, "application/json", strings.NewReader(`{"offset":0,"limit":999}`))
  79. if err != nil {
  80. return
  81. }
  82. defer res.Body.Close()
  83. bodyBts, err := ioutil.ReadAll(res.Body)
  84. if err != nil {
  85. return
  86. }
  87. var data ServiceListResp
  88. err = json.Unmarshal(bodyBts, &data)
  89. if err != nil {
  90. return
  91. }
  92. if data.Errcode != 0 {
  93. return errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg)), nil
  94. }
  95. list = data.AccountList
  96. return
  97. }
  98. // GetServicerList 获取接待账号列表
  99. func (a *WxApi) GetServicerList(openKfid string) (err error, list []ServicerAccount) {
  100. err = a.refreshAccessToken()
  101. if err != nil {
  102. return
  103. }
  104. url := GetServicerUrl
  105. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  106. url = strings.ReplaceAll(url, "OPEN_KFID", openKfid)
  107. res, err := http.Get(url)
  108. if err != nil {
  109. return
  110. }
  111. defer res.Body.Close()
  112. bodyBts, err := ioutil.ReadAll(res.Body)
  113. if err != nil {
  114. return
  115. }
  116. var data ServicerListResp
  117. err = json.Unmarshal(bodyBts, &data)
  118. if err != nil {
  119. return
  120. }
  121. if data.Errcode != 0 {
  122. return errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg)), nil
  123. }
  124. list = data.ServicerList
  125. return
  126. }
  127. // 读取成员
  128. func (a *WxApi) GetStaffList(userid string) (err error, d Staff) {
  129. err = a.refreshAccessToken()
  130. if err != nil {
  131. return
  132. }
  133. url := GetStaffUrl
  134. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  135. url = strings.ReplaceAll(url, "USERID", userid)
  136. res, err := http.Get(url)
  137. if err != nil {
  138. return
  139. }
  140. defer res.Body.Close()
  141. bodyBts, err := ioutil.ReadAll(res.Body)
  142. if err != nil {
  143. return
  144. }
  145. var data StaffResp
  146. err = json.Unmarshal(bodyBts, &data)
  147. if err != nil {
  148. return
  149. }
  150. if data.Errcode != 0 {
  151. return errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg)), Staff{}
  152. }
  153. d = data.Staff
  154. return
  155. }
  156. // GetCustomerList 获取客户基础信息列表
  157. func (a *WxApi) GetCustomerList(userIds []string) (err error, list []Customer) {
  158. err = a.refreshAccessToken()
  159. if err != nil {
  160. return
  161. }
  162. url := GetCustomerUrl
  163. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  164. body := CustomerParam{
  165. ExternalUseridList: userIds,
  166. NeedEnterSessionContext: 0,
  167. }
  168. bts, _ := json.Marshal(body)
  169. res, err := http.Post(url, "application/json", bytes.NewReader(bts))
  170. if err != nil {
  171. return
  172. }
  173. defer res.Body.Close()
  174. bodyBts, err := ioutil.ReadAll(res.Body)
  175. if err != nil {
  176. return
  177. }
  178. var data CustomerListResp
  179. err = json.Unmarshal(bodyBts, &data)
  180. if err != nil {
  181. return
  182. }
  183. if data.Errcode != 0 {
  184. return errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg)), nil
  185. }
  186. list = data.CustomerList
  187. return
  188. }
  189. // GetMsgList 读取消息
  190. func (a *WxApi) GetMsgList(cursor, token, openKfid string) (err error, hasMore int, nextCursor string, list []Msg) {
  191. err = a.refreshAccessToken()
  192. if err != nil {
  193. return
  194. }
  195. url := GetMsgUrl
  196. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  197. body := MsgParam{
  198. Cursor: cursor,
  199. Token: token,
  200. Limit: 1000,
  201. VoiceFormat: 0,
  202. OpenKfid: openKfid,
  203. }
  204. bts, _ := json.Marshal(body)
  205. res, err := http.Post(url, "application/json", bytes.NewReader(bts))
  206. if err != nil {
  207. return
  208. }
  209. defer res.Body.Close()
  210. bodyBts, err := ioutil.ReadAll(res.Body)
  211. if err != nil {
  212. return
  213. }
  214. var data MsgListResp
  215. err = json.Unmarshal(bodyBts, &data)
  216. if err != nil {
  217. return
  218. }
  219. if data.Errcode != 0 {
  220. err = errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg))
  221. return
  222. }
  223. hasMore = data.HasMore
  224. nextCursor = data.NextCursor
  225. for _, msg := range data.MsgList {
  226. var m Msg
  227. m.Msgid, _ = msg["msgid"].(string)
  228. m.OpenKfid, _ = msg["open_kfid"].(string)
  229. m.ExternalUserid, _ = msg["external_userid"].(string)
  230. m.SendTime, _ = msg["send_time"].(float64)
  231. m.Origin, _ = msg["origin"].(float64)
  232. m.ServicerUserid, _ = msg["servicer_userid"].(string)
  233. m.Msgtype, _ = msg["msgtype"].(string)
  234. if m.Msgtype != "" {
  235. content, _ := msg[m.Msgtype]
  236. eventBts, _ := json.Marshal(content)
  237. m.DataInfo = string(eventBts)
  238. if m.Msgtype == "event" {
  239. var event = make(map[string]interface{})
  240. _ = json.Unmarshal(eventBts, &event)
  241. if _, b := event["event_type"].(string); b {
  242. m.OpenKfid, _ = event["open_kfid"].(string)
  243. m.ExternalUserid, _ = event["external_userid"].(string)
  244. }
  245. }
  246. }
  247. list = append(list, m)
  248. }
  249. return
  250. }
  251. // UpdateSessionState 变更会话状态
  252. func (a *WxApi) UpdateSessionState(openKfid, externalUserid, servicerUserid string, serviceState int) (err error) {
  253. err = a.refreshAccessToken()
  254. if err != nil {
  255. return
  256. }
  257. url := UpdateSessionStateUrl
  258. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  259. body := UpdateSessionStateParam{
  260. OpenKfid: openKfid,
  261. ExternalUserid: externalUserid,
  262. ServiceState: serviceState,
  263. ServicerUserid: servicerUserid,
  264. }
  265. bts, _ := json.Marshal(body)
  266. res, err := http.Post(url, "application/json", bytes.NewReader(bts))
  267. if err != nil {
  268. return
  269. }
  270. defer res.Body.Close()
  271. bodyBts, err := ioutil.ReadAll(res.Body)
  272. if err != nil {
  273. return
  274. }
  275. var data UpdateSessionStateResp
  276. err = json.Unmarshal(bodyBts, &data)
  277. if err != nil {
  278. return
  279. }
  280. if data.Errcode != 0 {
  281. return errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg))
  282. }
  283. return
  284. }
  285. // 获取会话状态
  286. func (a *WxApi) GetSessionState(openKfid, externalUserid string) (err error, d GetSessionStateResp) {
  287. err = a.refreshAccessToken()
  288. if err != nil {
  289. return
  290. }
  291. url := GetSessionStateUrl
  292. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  293. body := GetSessionStateParam{
  294. OpenKfid: openKfid,
  295. ExternalUserid: externalUserid,
  296. }
  297. bts, _ := json.Marshal(body)
  298. res, err := http.Post(url, "application/json", bytes.NewReader(bts))
  299. if err != nil {
  300. return
  301. }
  302. defer res.Body.Close()
  303. bodyBts, err := ioutil.ReadAll(res.Body)
  304. if err != nil {
  305. return
  306. }
  307. var data GetSessionStateResp
  308. err = json.Unmarshal(bodyBts, &data)
  309. if err != nil {
  310. return
  311. }
  312. if data.Errcode != 0 {
  313. err = errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg))
  314. return
  315. }
  316. d = data
  317. return
  318. }
  319. // 发送文字欢迎语
  320. func (a *WxApi) SendMsgOnEvent(code string, msg string) (err error, d SendMsgOnEventResp) {
  321. err = a.refreshAccessToken()
  322. if err != nil {
  323. return
  324. }
  325. url := SendMsgOnEventUrl
  326. url = strings.ReplaceAll(url, "ACCESS_TOKEN", a.accessToken)
  327. body := SendMsgOnEventParam{
  328. Code: code,
  329. Msgtype: "text",
  330. }
  331. body.Text.Content = msg
  332. bts, _ := json.Marshal(body)
  333. res, err := http.Post(url, "application/json", bytes.NewReader(bts))
  334. if err != nil {
  335. return
  336. }
  337. defer res.Body.Close()
  338. bodyBts, err := ioutil.ReadAll(res.Body)
  339. if err != nil {
  340. return
  341. }
  342. var data SendMsgOnEventResp
  343. err = json.Unmarshal(bodyBts, &data)
  344. if err != nil {
  345. return
  346. }
  347. if data.Errcode != 0 {
  348. err = errors.New(fmt.Sprintf("errcode: %v,errmsg: %v", data.Errcode, data.Errmsg))
  349. return
  350. }
  351. d = data
  352. return
  353. }