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