component.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. // 微信全网接入测试
  2. // https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318611&token=&lang=zh_CN
  3. package controllers
  4. import (
  5. "bytes"
  6. "encoding/base64"
  7. "encoding/json"
  8. "errors"
  9. "net/http"
  10. "strings"
  11. "wechat-conf/models"
  12. "wechat-conf/models/model"
  13. "wechat-conf/service/autoreply"
  14. "wechat-conf/service/wechat"
  15. "wechat-conf/utils"
  16. "github.com/astaxie/beego"
  17. "github.com/kinisky564477/wechat/component"
  18. "github.com/kinisky564477/wechat/core"
  19. )
  20. // WechatController 用户
  21. type WechatController struct {
  22. BaseController
  23. serv *autoreply.AutoreplyServ
  24. wechatServ *wechat.WechatServ
  25. compConf *model.SysComponentConf
  26. cipher core.IOCipher
  27. }
  28. const (
  29. INFOTYPE_TICKET = "component_verify_ticket"
  30. INFOTYPE_AUTHORIZED = "authorized"
  31. INFOTYPE_UNAUTHORIZED = "unauthorized"
  32. INFOTYPE_UPDATEAUTHORIZED = "updateauthorized"
  33. )
  34. const (
  35. WX_OFFICE_TEST_APPID = "wx570bc396a51b8ff8"
  36. WX_OFFICE_TEST_USERNAME = "gh_3c884a361561"
  37. )
  38. const (
  39. RESPONSE_CUSTOM_MESSAGE = "response-custom-message"
  40. )
  41. var nilData = []byte("success")
  42. // Constructor 初始化 Controller
  43. // @Title Constructor
  44. // @Description 初始化 Controller, 系统自动调用
  45. func (c *WechatController) Constructor() {
  46. c.serv = autoreply.NewAutoreplyServ(c.Context)
  47. c.wechatServ = wechat.NewWechatServ(c.Context)
  48. var err error
  49. c.compConf, err = c.wechatServ.GetComponentInfo()
  50. if err != nil {
  51. utils.LogError("获取平台配置失败", err)
  52. return
  53. }
  54. c.cipher, err = core.NewCipher(c.compConf.OriginToken, c.compConf.Aeskey, c.compConf.Appid)
  55. if err != nil {
  56. utils.LogError("初始化加解密算法失败", err)
  57. return
  58. }
  59. }
  60. // preCheck 如果有需要用到加解密之前, 请先调用这个方法
  61. func (c *WechatController) preCheck(resp []byte) {
  62. if c.compConf == nil || c.compConf.Appid == "" {
  63. c.ResponseRaw(resp)
  64. }
  65. if c.cipher == nil {
  66. c.ResponseRaw(resp)
  67. }
  68. }
  69. // ComponentPush 接收第三方平台推送
  70. // 主要有 ticket, 取消授权等
  71. func (c *WechatController) ComponentPush() {
  72. utils.LogInfo("接收平台推送")
  73. c.preCheck(nilData)
  74. // 接收内容
  75. receiveData := string(c.Ctx.Input.RequestBody)
  76. utils.LogInfo("接收内容", receiveData)
  77. if receiveData == "" {
  78. c.ResponseRaw(nilData)
  79. }
  80. // 解密内容
  81. r := bytes.NewBufferString(receiveData)
  82. dt, err := c.cipher.Decrypt(r)
  83. if err != nil {
  84. utils.LogError("解密数据失败", err)
  85. c.ResponseRaw(nilData)
  86. }
  87. receiveMessage := string(dt)
  88. utils.LogInfo("解密内容: ", receiveMessage)
  89. // 解析xml
  90. xp := &core.XMLParse{}
  91. data, err := xp.Parse(receiveMessage)
  92. if err != nil {
  93. utils.LogError("解析解密数据失败:", err)
  94. c.ResponseRaw(nilData)
  95. }
  96. infoType := data["InfoType"]
  97. fromWXOffice := data["AppId"] == WX_OFFICE_TEST_APPID
  98. switch infoType {
  99. case INFOTYPE_TICKET:
  100. // 微信测试推送 component_verify_ticket, 只需要返回 success
  101. if fromWXOffice {
  102. c.ResponseRaw(nilData)
  103. }
  104. // 更新ticket
  105. c.compConf.Ticket = data["ComponentVerifyTicket"]
  106. err := c.wechatServ.UpdateComponentTicket(c.compConf)
  107. if err != nil {
  108. utils.LogError("修改ticket失败:", err)
  109. }
  110. utils.RefreshComponentTicket(data["ComponentVerifyTicket"])
  111. break
  112. case INFOTYPE_AUTHORIZED:
  113. // 授权成功
  114. var cert = map[string]string{
  115. "appid": data["AuthorizerAppid"],
  116. "authorization_code": data["AuthorizationCode"],
  117. }
  118. wxclient := utils.WechatInit(cert, c.wechatServ.UpdateToken)
  119. if wxclient == nil {
  120. utils.LogError("获取wxclient失败")
  121. c.ResponseRaw(nilData)
  122. }
  123. utils.AppendWxClient(wxclient)
  124. // 获取微信信息
  125. info, err := wxclient.GetWechatInfo()
  126. if err != nil {
  127. utils.LogError("获取微信信息失败")
  128. c.ResponseRaw(nilData)
  129. }
  130. authorizerInfo := (info["authorizer_info"]).(map[string]interface{})
  131. // 插入数据库
  132. mjson, _ := json.Marshal(data)
  133. HeadImg := ""
  134. if authorizerInfo["head_img"] != nil {
  135. HeadImg = authorizerInfo["head_img"].(string)
  136. }
  137. var conf = model.SysWechatConf{
  138. Appid: data["AuthorizerAppid"],
  139. AuthorizationCode: data["AuthorizationCode"],
  140. AuthorizationInfo: string(mjson),
  141. WxNikeName: authorizerInfo["nick_name"].(string),
  142. HeadImg: HeadImg,
  143. UserName: authorizerInfo["user_name"].(string),
  144. PrincipalName: authorizerInfo["principal_name"].(string),
  145. QrcodeUrl: authorizerInfo["qrcode_url"].(string),
  146. }
  147. err = c.wechatServ.SaveWechatConf(conf)
  148. if err != nil {
  149. utils.LogError("保存微信授权信息失败:", err)
  150. c.ResponseRaw(nilData)
  151. }
  152. break
  153. case INFOTYPE_UPDATEAUTHORIZED:
  154. // 授权更新
  155. break
  156. case INFOTYPE_UNAUTHORIZED:
  157. // 取消授权
  158. // 删除wechatConf信息,同时将org解绑
  159. appid := data["AuthorizerAppid"]
  160. c.wechatServ.UnAuthorized(appid)
  161. // 删除第三方中的微信信息
  162. utils.Component.DelWxClient(appid)
  163. break
  164. }
  165. c.ResponseRaw(nilData)
  166. }
  167. // WechatInfo 微信接入
  168. func (c *WechatController) WechatInfo() {
  169. method := c.Ctx.Input.Method()
  170. if method == http.MethodGet {
  171. echostr := c.GetString("echostr")
  172. c.ResponseRaw([]byte(echostr))
  173. } else {
  174. c.WxReceive()
  175. }
  176. }
  177. // WxReceive 微信消息接收
  178. func (c *WechatController) WxReceive() {
  179. utils.LogInfo("接收微信事件, URI: ", c.Ctx.Input.URI())
  180. c.preCheck(nilData)
  181. // 初始化微信客户端
  182. appid := c.GetString(":appid")
  183. wechat, err := utils.Component.GetWxClient(appid)
  184. if err != nil {
  185. utils.LogError("获取微信失败: " + err.Error())
  186. c.ResponseRaw(nilData)
  187. }
  188. // 校验接收数据
  189. receiveData, err := c.validReceiveData(c.Ctx.Input.RequestBody, c.compConf.Aeskey, wechat)
  190. if err != nil {
  191. c.ResponseRaw(nilData)
  192. }
  193. // 获取返回内容
  194. replyMessage, err := c.getReplayMessage(receiveData, wechat)
  195. if err != nil && err.Error() == RESPONSE_CUSTOM_MESSAGE {
  196. // 微信测试用例会跑到这边
  197. // 模拟粉丝发送消息, 并立即调用客户接口, 反馈固定的字串
  198. go wechat.SendCustomText(receiveData["FromUserName"], wechat.GetAuthCode()+"_from_api")
  199. c.ResponseRaw(nil)
  200. }
  201. if err != nil || replyMessage == nil || len(replyMessage) == 0 {
  202. c.ResponseRaw(nilData)
  203. }
  204. utils.LogInfo("反馈微信内容: ", string(replyMessage))
  205. // 加密内容
  206. sendData, err := c.encryptMessage(replyMessage, c.compConf.Aeskey, c.compConf.OriginToken, c.compConf.Appid, wechat)
  207. if err != nil {
  208. c.ResponseRaw(nilData)
  209. }
  210. sendMessage := string(sendData)
  211. // 去掉多余的回车
  212. sendMessage = strings.Replace(sendMessage, "\n\n", "\n", -1)
  213. sendMessage = strings.Trim(sendMessage, "\n")
  214. utils.LogInfo("反馈加密内容: ", sendMessage)
  215. c.Ctx.Output.Header("Content-Type", "text/xml; charset=utf-8")
  216. c.ResponseRaw([]byte(sendMessage))
  217. }
  218. // validReceiveData 校验接收信息
  219. // 没有通过 signature 校验,默认是正确的
  220. func (c *WechatController) validReceiveData(receiveData []byte, aesKey string, wechat *component.WxClient) (data map[string]string, err error) {
  221. receiveMessage := string(receiveData)
  222. utils.LogInfo("事件内容: ", receiveMessage)
  223. var msg map[string]string
  224. msg, err = wechat.TransformMessage(receiveMessage)
  225. if err != nil {
  226. utils.LogError("读取微信服务发送内容失败: " + err.Error())
  227. return
  228. }
  229. var key []byte
  230. var encryptData []byte
  231. var decryptData []byte
  232. key, err = base64.StdEncoding.DecodeString(aesKey + "=")
  233. if err != nil {
  234. utils.LogError("Base64 decode aes-key 失败:", err)
  235. return
  236. }
  237. encryptData, err = base64.StdEncoding.DecodeString(msg["Encrypt"])
  238. if err != nil {
  239. utils.LogError("密文base64解析", err)
  240. return
  241. }
  242. decryptData, err = utils.AesDecrypt(encryptData, key)
  243. if err != nil {
  244. utils.LogError("解密失败:", err)
  245. return
  246. }
  247. utils.LogInfo("内容解密: ", string(decryptData))
  248. // 解析xml
  249. xp := &core.XMLParse{}
  250. data, err = xp.Parse(string(decryptData))
  251. if err != nil {
  252. utils.LogError("xml解析失败:", err)
  253. return
  254. }
  255. return
  256. }
  257. // encryptReceiveMessage 加密需要发送的信息
  258. func (c *WechatController) encryptMessage(message []byte, aesKey, token, appID string, wechat *component.WxClient) ([]byte, error) {
  259. cipher, err := core.NewCipher(token, aesKey, appID)
  260. if err != nil {
  261. utils.LogError("NewCipher 失败: " + err.Error())
  262. return nil, err
  263. }
  264. dt := bytes.NewBuffer([]byte{})
  265. nonce := c.GetString("nonce")
  266. timestamp := c.GetString("timestamp")
  267. cipher.Encrypt(dt, message, nonce, timestamp)
  268. return dt.Bytes(), nil
  269. }
  270. func (c *WechatController) getReplayMessage(receviceData map[string]string, wechat *component.WxClient) (message []byte, err error) {
  271. var replay *model.TaAutoReply
  272. msgType := receviceData["MsgType"]
  273. from := receviceData["ToUserName"]
  274. openID := receviceData["FromUserName"]
  275. appID := wechat.GetAppID()
  276. fromWXOffice := openID == WX_OFFICE_TEST_USERNAME
  277. // 暂时支持 文本以及订阅事件消息
  278. switch msgType {
  279. case "text":
  280. content := receviceData["Content"]
  281. // 微信文本消息测试
  282. if fromWXOffice {
  283. // 普通文本测试, 返回 TESTCOMPONENT_MSG_TYPE_TEXT_callback
  284. if content == "TESTCOMPONENT_MSG_TYPE_TEXT" {
  285. replay = &model.TaAutoReply{
  286. MessageType: models.MESSAGE_TYPE_PARAGRAPH,
  287. MessageParagraph: "TESTCOMPONENT_MSG_TYPE_TEXT_callback",
  288. IsUse: models.AUTOREPLY_IS_USE_ON,
  289. }
  290. // 发送 query_auth_code , 返回空, 并立即调用客服接口
  291. } else if strings.Index(content, "QUERY_AUTH_CODE") > -1 {
  292. replay = nil
  293. return nil, errors.New(RESPONSE_CUSTOM_MESSAGE)
  294. }
  295. break
  296. }
  297. if replay, err = c.serv.GetAutoReplayByAppID(appID, content); err != nil {
  298. utils.LogError("获取微信自动回复信息失败: " + err.Error())
  299. return
  300. }
  301. break
  302. case "event":
  303. switch receviceData["Event"] {
  304. case "subscribe":
  305. if replay, err = c.serv.GetSubscribeByAppID(appID); err != nil {
  306. utils.LogError("获取微信自动回复信息失败: " + err.Error())
  307. return
  308. }
  309. break
  310. case "CLICK":
  311. target := receviceData["EventKey"]
  312. if target != "" {
  313. var replyText string
  314. replyText, err = c.wechatServ.GetMenuReplyText(target)
  315. if err != nil {
  316. utils.LogError("获取菜单回复信息失败: " + err.Error())
  317. return
  318. }
  319. replay = &model.TaAutoReply{
  320. MessageType: models.MESSAGE_TYPE_PARAGRAPH,
  321. MessageParagraph: replyText,
  322. IsUse: models.AUTOREPLY_IS_USE_ON,
  323. }
  324. }
  325. break
  326. }
  327. break
  328. }
  329. if replay == nil {
  330. return
  331. }
  332. switch replay.MessageType {
  333. case models.MESSAGE_TYPE_PARAGRAPH:
  334. return wechat.ResponseMessageText(from, openID, replay.MessageParagraph)
  335. case models.MESSAGE_TYPE_IMG:
  336. return wechat.ResponseMessageImage(from, openID, replay.MessageImg)
  337. }
  338. return
  339. }
  340. // GetPreAuthCode 获取预授权码
  341. func (c *WechatController) GetPreAuthCode() {
  342. code, err := utils.Component.GetPreAuthCode()
  343. if err != nil {
  344. utils.LogError("获取预授权码错误: " + err.Error())
  345. c.ResponseError(err)
  346. }
  347. appid := utils.Component.GetCertificate()["appid"]
  348. c.ResponseJSON(map[string]string{
  349. "appid": appid,
  350. "code": code,
  351. })
  352. }
  353. // GetWechatMenuByAppID 根据appid获取微信详情
  354. func (c *WechatController) GetWechatInfoByAppID() {
  355. appid := c.GetString(":appid")
  356. wxclient, err := utils.Component.GetWxClient(appid)
  357. if err != nil {
  358. utils.LogError("获取微信信息失败: " + err.Error())
  359. c.ResponseError(err)
  360. }
  361. info, err := wxclient.GetWechatInfo()
  362. if err != nil {
  363. utils.LogError("获取微信详情失败: " + err.Error())
  364. c.ResponseError(err)
  365. }
  366. c.ResponseJSON(info)
  367. }
  368. // GetWechatMenuByAppID 获取菜单
  369. func (c *WechatController) GetMaterialByAppID() {
  370. appid := c.GetString(":appid")
  371. wxclient, err := utils.Component.GetWxClient(appid)
  372. if err != nil {
  373. utils.LogError("获取微信信息失败: " + err.Error())
  374. c.ResponseError(err)
  375. }
  376. var data = map[string]string{}
  377. data["offset"] = "0"
  378. data["type"] = "image"
  379. data["count"] = "10"
  380. menus, err := wxclient.GetMaterialList(data)
  381. if err != nil {
  382. utils.LogError("获取微信详情失败: " + err.Error())
  383. c.ResponseError(err)
  384. }
  385. c.ResponseJSON(menus)
  386. }
  387. // GetWechatByCode 根据code获取微信信息
  388. func (c *WechatController) GetWechatByCode() {
  389. code := c.GetString(":code")
  390. beego.Error(code)
  391. conf, err := c.wechatServ.GetWechatByCode(code)
  392. if err != nil {
  393. utils.LogError("获取微信详情失败: " + err.Error())
  394. c.ResponseError(err)
  395. }
  396. beego.Error(conf)
  397. if conf == nil || conf.ConfId == "" || conf.Status == models.STATUS_NORMAL {
  398. c.ResponseJSON("")
  399. }
  400. c.ResponseJSON(map[string]string{
  401. "ConfId": conf.ConfId,
  402. "NickName": conf.WxNikeName,
  403. "HeadImg": conf.HeadImg,
  404. })
  405. }