123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- // 微信全网接入测试
- // https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318611&token=&lang=zh_CN
-
- package controllers
-
- import (
- "bytes"
- "encoding/base64"
- "encoding/json"
- "errors"
- "net/http"
- "strings"
- "wechat-conf/models"
- "wechat-conf/models/model"
- "wechat-conf/service/autoreply"
- "wechat-conf/service/wechat"
- "wechat-conf/utils"
-
- "github.com/astaxie/beego"
-
- "github.com/kinisky564477/wechat/component"
-
- "github.com/kinisky564477/wechat/core"
- )
-
- // WechatController 用户
- type WechatController struct {
- BaseController
- serv *autoreply.AutoreplyServ
- wechatServ *wechat.WechatServ
- compConf *model.SysComponentConf
- cipher core.IOCipher
- }
-
- const (
- INFOTYPE_TICKET = "component_verify_ticket"
- INFOTYPE_AUTHORIZED = "authorized"
- INFOTYPE_UNAUTHORIZED = "unauthorized"
- INFOTYPE_UPDATEAUTHORIZED = "updateauthorized"
- )
-
- const (
- WX_OFFICE_TEST_APPID = "wx570bc396a51b8ff8"
- WX_OFFICE_TEST_USERNAME = "gh_3c884a361561"
- )
-
- const (
- RESPONSE_CUSTOM_MESSAGE = "response-custom-message"
- )
-
- var nilData = []byte("success")
-
- // Constructor 初始化 Controller
- // @Title Constructor
- // @Description 初始化 Controller, 系统自动调用
- func (c *WechatController) Constructor() {
- c.serv = autoreply.NewAutoreplyServ(c.Context)
- c.wechatServ = wechat.NewWechatServ(c.Context)
-
- var err error
- c.compConf, err = c.wechatServ.GetComponentInfo()
- if err != nil {
- utils.LogError("获取平台配置失败", err)
- return
- }
-
- c.cipher, err = core.NewCipher(c.compConf.OriginToken, c.compConf.Aeskey, c.compConf.Appid)
- if err != nil {
- utils.LogError("初始化加解密算法失败", err)
- return
- }
- }
-
- // preCheck 如果有需要用到加解密之前, 请先调用这个方法
- func (c *WechatController) preCheck(resp []byte) {
- if c.compConf == nil || c.compConf.Appid == "" {
- c.ResponseRaw(resp)
- }
-
- if c.cipher == nil {
- c.ResponseRaw(resp)
- }
- }
-
- // ComponentPush 接收第三方平台推送
- // 主要有 ticket, 取消授权等
- func (c *WechatController) ComponentPush() {
- utils.LogInfo("接收平台推送")
- c.preCheck(nilData)
-
- // 接收内容
- receiveData := string(c.Ctx.Input.RequestBody)
- utils.LogInfo("接收内容", receiveData)
- if receiveData == "" {
- c.ResponseRaw(nilData)
- }
-
- // 解密内容
- r := bytes.NewBufferString(receiveData)
- dt, err := c.cipher.Decrypt(r)
- if err != nil {
- utils.LogError("解密数据失败", err)
- c.ResponseRaw(nilData)
- }
-
- receiveMessage := string(dt)
- utils.LogInfo("解密内容: ", receiveMessage)
-
- // 解析xml
- xp := &core.XMLParse{}
- data, err := xp.Parse(receiveMessage)
- if err != nil {
- utils.LogError("解析解密数据失败:", err)
- c.ResponseRaw(nilData)
- }
-
- infoType := data["InfoType"]
- fromWXOffice := data["AppId"] == WX_OFFICE_TEST_APPID
-
- switch infoType {
- case INFOTYPE_TICKET:
- // 微信测试推送 component_verify_ticket, 只需要返回 success
- if fromWXOffice {
- c.ResponseRaw(nilData)
- }
-
- // 更新ticket
- c.compConf.Ticket = data["ComponentVerifyTicket"]
- err := c.wechatServ.UpdateComponentTicket(c.compConf)
- if err != nil {
- utils.LogError("修改ticket失败:", err)
- }
- utils.RefreshComponentTicket(data["ComponentVerifyTicket"])
- break
-
- case INFOTYPE_AUTHORIZED:
- // 授权成功
- var cert = map[string]string{
- "appid": data["AuthorizerAppid"],
- "authorization_code": data["AuthorizationCode"],
- }
-
- wxclient := utils.WechatInit(cert, c.wechatServ.UpdateToken)
- if wxclient == nil {
- utils.LogError("获取wxclient失败")
- c.ResponseRaw(nilData)
- }
-
- utils.AppendWxClient(wxclient)
-
- // 获取微信信息
- info, err := wxclient.GetWechatInfo()
- if err != nil {
- utils.LogError("获取微信信息失败")
- c.ResponseRaw(nilData)
- }
-
- authorizerInfo := (info["authorizer_info"]).(map[string]interface{})
-
- // 插入数据库
- mjson, _ := json.Marshal(data)
- HeadImg := ""
- if authorizerInfo["head_img"] != nil {
- HeadImg = authorizerInfo["head_img"].(string)
- }
- var conf = model.SysWechatConf{
- Appid: data["AuthorizerAppid"],
- AuthorizationCode: data["AuthorizationCode"],
- AuthorizationInfo: string(mjson),
- WxNikeName: authorizerInfo["nick_name"].(string),
- HeadImg: HeadImg,
- UserName: authorizerInfo["user_name"].(string),
- PrincipalName: authorizerInfo["principal_name"].(string),
- QrcodeUrl: authorizerInfo["qrcode_url"].(string),
- }
-
- err = c.wechatServ.SaveWechatConf(conf)
- if err != nil {
- utils.LogError("保存微信授权信息失败:", err)
- c.ResponseRaw(nilData)
- }
- break
- case INFOTYPE_UPDATEAUTHORIZED:
- // 授权更新
- break
- case INFOTYPE_UNAUTHORIZED:
- // 取消授权
- // 删除wechatConf信息,同时将org解绑
- appid := data["AuthorizerAppid"]
- c.wechatServ.UnAuthorized(appid)
- // 删除第三方中的微信信息
- utils.Component.DelWxClient(appid)
- break
- }
-
- c.ResponseRaw(nilData)
- }
-
- // WechatInfo 微信接入
- func (c *WechatController) WechatInfo() {
- method := c.Ctx.Input.Method()
- if method == http.MethodGet {
- echostr := c.GetString("echostr")
- c.ResponseRaw([]byte(echostr))
- } else {
- c.WxReceive()
- }
- }
-
- // WxReceive 微信消息接收
- func (c *WechatController) WxReceive() {
- utils.LogInfo("接收微信事件, URI: ", c.Ctx.Input.URI())
- c.preCheck(nilData)
-
- // 初始化微信客户端
- appid := c.GetString(":appid")
- wechat, err := utils.Component.GetWxClient(appid)
- if err != nil {
- utils.LogError("获取微信失败: " + err.Error())
- c.ResponseRaw(nilData)
- }
-
- // 校验接收数据
- receiveData, err := c.validReceiveData(c.Ctx.Input.RequestBody, c.compConf.Aeskey, wechat)
- if err != nil {
- c.ResponseRaw(nilData)
- }
-
- // 获取返回内容
- replyMessage, err := c.getReplayMessage(receiveData, wechat)
- if err != nil && err.Error() == RESPONSE_CUSTOM_MESSAGE {
- // 微信测试用例会跑到这边
- // 模拟粉丝发送消息, 并立即调用客户接口, 反馈固定的字串
- go wechat.SendCustomText(receiveData["FromUserName"], wechat.GetAuthCode()+"_from_api")
-
- c.ResponseRaw(nil)
- }
-
- if err != nil || replyMessage == nil || len(replyMessage) == 0 {
- c.ResponseRaw(nilData)
- }
-
- utils.LogInfo("反馈微信内容: ", string(replyMessage))
-
- // 加密内容
- sendData, err := c.encryptMessage(replyMessage, c.compConf.Aeskey, c.compConf.OriginToken, c.compConf.Appid, wechat)
- if err != nil {
- c.ResponseRaw(nilData)
- }
-
- sendMessage := string(sendData)
-
- // 去掉多余的回车
- sendMessage = strings.Replace(sendMessage, "\n\n", "\n", -1)
- sendMessage = strings.Trim(sendMessage, "\n")
- utils.LogInfo("反馈加密内容: ", sendMessage)
-
- c.Ctx.Output.Header("Content-Type", "text/xml; charset=utf-8")
- c.ResponseRaw([]byte(sendMessage))
- }
-
- // validReceiveData 校验接收信息
- // 没有通过 signature 校验,默认是正确的
- func (c *WechatController) validReceiveData(receiveData []byte, aesKey string, wechat *component.WxClient) (data map[string]string, err error) {
- receiveMessage := string(receiveData)
- utils.LogInfo("事件内容: ", receiveMessage)
-
- var msg map[string]string
- msg, err = wechat.TransformMessage(receiveMessage)
- if err != nil {
- utils.LogError("读取微信服务发送内容失败: " + err.Error())
- return
- }
-
- var key []byte
- var encryptData []byte
- var decryptData []byte
-
- key, err = base64.StdEncoding.DecodeString(aesKey + "=")
- if err != nil {
- utils.LogError("Base64 decode aes-key 失败:", err)
- return
- }
-
- encryptData, err = base64.StdEncoding.DecodeString(msg["Encrypt"])
- if err != nil {
- utils.LogError("密文base64解析", err)
- return
- }
-
- decryptData, err = utils.AesDecrypt(encryptData, key)
- if err != nil {
- utils.LogError("解密失败:", err)
- return
- }
-
- utils.LogInfo("内容解密: ", string(decryptData))
-
- // 解析xml
- xp := &core.XMLParse{}
- data, err = xp.Parse(string(decryptData))
- if err != nil {
- utils.LogError("xml解析失败:", err)
- return
- }
-
- return
- }
-
- // encryptReceiveMessage 加密需要发送的信息
- func (c *WechatController) encryptMessage(message []byte, aesKey, token, appID string, wechat *component.WxClient) ([]byte, error) {
- cipher, err := core.NewCipher(token, aesKey, appID)
- if err != nil {
- utils.LogError("NewCipher 失败: " + err.Error())
- return nil, err
- }
-
- dt := bytes.NewBuffer([]byte{})
-
- nonce := c.GetString("nonce")
- timestamp := c.GetString("timestamp")
-
- cipher.Encrypt(dt, message, nonce, timestamp)
-
- return dt.Bytes(), nil
- }
-
- func (c *WechatController) getReplayMessage(receviceData map[string]string, wechat *component.WxClient) (message []byte, err error) {
- var replay *model.TaAutoReply
-
- msgType := receviceData["MsgType"]
- from := receviceData["ToUserName"]
- openID := receviceData["FromUserName"]
- appID := wechat.GetAppID()
- fromWXOffice := openID == WX_OFFICE_TEST_USERNAME
-
- // 暂时支持 文本以及订阅事件消息
- switch msgType {
- case "text":
- content := receviceData["Content"]
-
- // 微信文本消息测试
- if fromWXOffice {
- // 普通文本测试, 返回 TESTCOMPONENT_MSG_TYPE_TEXT_callback
- if content == "TESTCOMPONENT_MSG_TYPE_TEXT" {
- replay = &model.TaAutoReply{
- MessageType: models.MESSAGE_TYPE_PARAGRAPH,
- MessageParagraph: "TESTCOMPONENT_MSG_TYPE_TEXT_callback",
- IsUse: models.AUTOREPLY_IS_USE_ON,
- }
-
- // 发送 query_auth_code , 返回空, 并立即调用客服接口
- } else if strings.Index(content, "QUERY_AUTH_CODE") > -1 {
- replay = nil
- return nil, errors.New(RESPONSE_CUSTOM_MESSAGE)
- }
- break
- }
-
- if replay, err = c.serv.GetAutoReplayByAppID(appID, content); err != nil {
- utils.LogError("获取微信自动回复信息失败: " + err.Error())
- return
- }
-
- break
- case "event":
- switch receviceData["Event"] {
- case "subscribe":
- if replay, err = c.serv.GetSubscribeByAppID(appID); err != nil {
- utils.LogError("获取微信自动回复信息失败: " + err.Error())
- return
- }
-
- break
- case "CLICK":
- target := receviceData["EventKey"]
- if target != "" {
- var replyText string
- replyText, err = c.wechatServ.GetMenuReplyText(target)
- if err != nil {
- utils.LogError("获取菜单回复信息失败: " + err.Error())
- return
- }
-
- replay = &model.TaAutoReply{
- MessageType: models.MESSAGE_TYPE_PARAGRAPH,
- MessageParagraph: replyText,
- IsUse: models.AUTOREPLY_IS_USE_ON,
- }
- }
- break
- }
-
- break
- }
-
- if replay == nil {
- return
- }
-
- switch replay.MessageType {
- case models.MESSAGE_TYPE_PARAGRAPH:
- return wechat.ResponseMessageText(from, openID, replay.MessageParagraph)
- case models.MESSAGE_TYPE_IMG:
- return wechat.ResponseMessageImage(from, openID, replay.MessageImg)
- }
- return
- }
-
- // GetPreAuthCode 获取预授权码
- func (c *WechatController) GetPreAuthCode() {
- code, err := utils.Component.GetPreAuthCode()
- if err != nil {
- utils.LogError("获取预授权码错误: " + err.Error())
- c.ResponseError(err)
- }
- appid := utils.Component.GetCertificate()["appid"]
- c.ResponseJSON(map[string]string{
- "appid": appid,
- "code": code,
- })
- }
-
- // GetWechatMenuByAppID 根据appid获取微信详情
- func (c *WechatController) GetWechatInfoByAppID() {
- appid := c.GetString(":appid")
- wxclient, err := utils.Component.GetWxClient(appid)
- if err != nil {
- utils.LogError("获取微信信息失败: " + err.Error())
- c.ResponseError(err)
- }
- info, err := wxclient.GetWechatInfo()
- if err != nil {
- utils.LogError("获取微信详情失败: " + err.Error())
- c.ResponseError(err)
- }
- c.ResponseJSON(info)
- }
-
- // GetWechatMenuByAppID 获取菜单
- func (c *WechatController) GetMaterialByAppID() {
- appid := c.GetString(":appid")
- wxclient, err := utils.Component.GetWxClient(appid)
- if err != nil {
- utils.LogError("获取微信信息失败: " + err.Error())
- c.ResponseError(err)
- }
- var data = map[string]string{}
- data["offset"] = "0"
- data["type"] = "image"
- data["count"] = "10"
-
- menus, err := wxclient.GetMaterialList(data)
- if err != nil {
- utils.LogError("获取微信详情失败: " + err.Error())
- c.ResponseError(err)
- }
- c.ResponseJSON(menus)
- }
-
- // GetWechatByCode 根据code获取微信信息
- func (c *WechatController) GetWechatByCode() {
- code := c.GetString(":code")
- beego.Error(code)
- conf, err := c.wechatServ.GetWechatByCode(code)
- if err != nil {
- utils.LogError("获取微信详情失败: " + err.Error())
- c.ResponseError(err)
- }
- beego.Error(conf)
- if conf == nil || conf.ConfId == "" || conf.Status == models.STATUS_NORMAL {
- c.ResponseJSON("")
- }
-
- c.ResponseJSON(map[string]string{
- "ConfId": conf.ConfId,
- "NickName": conf.WxNikeName,
- "HeadImg": conf.HeadImg,
- })
- }
|