// 微信全网接入测试 // 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, }) }