|
|
|
|
package callback
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"encoding/xml"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/flipped-aurora/gin-vue-admin/server/commonus"
|
|
|
|
|
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
|
|
|
|
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
|
|
|
|
"github.com/flipped-aurora/gin-vue-admin/server/model/wechatcallback"
|
|
|
|
|
"github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizmsgcrypt"
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
//入口
|
|
|
|
|
func (a *CallBackApi) Index(c *gin.Context) {
|
|
|
|
|
outPut := commonus.MapOut()
|
|
|
|
|
response.Result(0, outPut, "企业微信回调入口", c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//回调入口
|
|
|
|
|
func (a *CallBackApi) CallbackMessageApi(c *gin.Context) {
|
|
|
|
|
MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。
|
|
|
|
|
Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。
|
|
|
|
|
Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。
|
|
|
|
|
Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文
|
|
|
|
|
|
|
|
|
|
var basicValueCallback CallBackData //企业微信回调基础参数
|
|
|
|
|
basicValueCallback.MsgSignature = MsgSignature
|
|
|
|
|
timeStampInt, timeStampIntErr := strconv.ParseInt(Timestamp, 10, 64)
|
|
|
|
|
if timeStampIntErr == nil {
|
|
|
|
|
basicValueCallback.Timestamp = timeStampInt
|
|
|
|
|
}
|
|
|
|
|
basicValueCallback.Nonce = Nonce
|
|
|
|
|
if Echostr != "" {
|
|
|
|
|
//Api地址验证
|
|
|
|
|
basicValueCallback.Echostr = Echostr
|
|
|
|
|
msgStr := basicValueCallback.VerificationUrl()
|
|
|
|
|
c.String(200, msgStr)
|
|
|
|
|
} else {
|
|
|
|
|
var xmlMessageStr CallBackVerificationXml
|
|
|
|
|
xmlErr := c.ShouldBindXML(&xmlMessageStr)
|
|
|
|
|
if xmlErr != nil {
|
|
|
|
|
response.Result(101, xmlErr, "XML获取错误!", c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// fmt.Printf("Xml----->%v\n", xmlMessageStr)
|
|
|
|
|
basicValueCallback.ToUserName = xmlMessageStr.ToUserName.Text
|
|
|
|
|
basicValueCallback.Encrypt = xmlMessageStr.Encrypt.Text
|
|
|
|
|
basicValueCallback.AgentID = xmlMessageStr.AgentID.Text
|
|
|
|
|
msgStr := basicValueCallback.DecryptMessage()
|
|
|
|
|
c.String(200, msgStr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//启动企业微信验证程序
|
|
|
|
|
func WechatVerification() (wxcpt *wxbizmsgcrypt.WXBizMsgCrypt) {
|
|
|
|
|
//正式数据
|
|
|
|
|
// token := "kkUA3s2s3"
|
|
|
|
|
token := global.GVA_CONFIG.WorkWechatSchool.WechatTokening
|
|
|
|
|
receiverId := global.GVA_CONFIG.WorkWechatId.CompanyId
|
|
|
|
|
encodingAeskey := global.GVA_CONFIG.WorkWechatSchool.EncodingAESKey
|
|
|
|
|
|
|
|
|
|
// fmt.Printf("------->%v------->%v------->%v\n", token, receiverId, encodingAeskey)
|
|
|
|
|
//测试数据
|
|
|
|
|
// token := "QDG6eK"
|
|
|
|
|
// receiverId := "wx5823bf96d3bd56c7"
|
|
|
|
|
// encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"
|
|
|
|
|
wxcpt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//验证URL
|
|
|
|
|
func (c *CallBackData) VerificationUrl() (msg string) {
|
|
|
|
|
wecahtCpt := WechatVerification()
|
|
|
|
|
timestampStr := strconv.FormatInt(c.Timestamp, 10)
|
|
|
|
|
echoStr, cryptErr := wecahtCpt.VerifyURL(c.MsgSignature, timestampStr, c.Nonce, c.Echostr)
|
|
|
|
|
var callbackLog wechatcallback.CallbackLog
|
|
|
|
|
//
|
|
|
|
|
callbackLog.MsgSignature = c.MsgSignature
|
|
|
|
|
callbackLog.TimeStamp = c.Timestamp
|
|
|
|
|
callbackLog.Nonce = c.Nonce
|
|
|
|
|
callbackLog.Echostr = c.Echostr
|
|
|
|
|
callbackLog.Xmlstr = string(echoStr)
|
|
|
|
|
// callbackLog.Reqdata = string(reqData)
|
|
|
|
|
callbackLog.AddTime = time.Now().Unix()
|
|
|
|
|
global.GVA_DB_WechatCallBack.Create(&callbackLog)
|
|
|
|
|
if nil != cryptErr {
|
|
|
|
|
fmt.Println("verifyUrl fail", cryptErr)
|
|
|
|
|
}
|
|
|
|
|
msg = string(echoStr)
|
|
|
|
|
return
|
|
|
|
|
// fmt.Println(string(echoStr))
|
|
|
|
|
// fmt.Print(string(echoStr))
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//解析消息结构
|
|
|
|
|
func (c *CallBackData) DecryptMessage() (echoMsg string) {
|
|
|
|
|
wecahtCpt := WechatVerification()
|
|
|
|
|
timestampStr := strconv.FormatInt(c.Timestamp, 10)
|
|
|
|
|
reqData := []byte("<xml><ToUserName><![CDATA[" + c.ToUserName + "]]></ToUserName><Encrypt><![CDATA[" + c.Encrypt + "]]></Encrypt><AgentID><![CDATA[" + c.AgentID + "]]></AgentID></xml>")
|
|
|
|
|
msg, cryptErr := wecahtCpt.DecryptMsg(c.MsgSignature, timestampStr, c.Nonce, reqData)
|
|
|
|
|
|
|
|
|
|
// fmt.Printf("%v=====>%v=====>%v\n: ", c.ToUserName, c.Encrypt, c.AgentID)
|
|
|
|
|
if nil != cryptErr {
|
|
|
|
|
fmt.Println("DecryptMsg fail", cryptErr)
|
|
|
|
|
}
|
|
|
|
|
// fmt.Println("after decrypt msg: ", string(msg))
|
|
|
|
|
var msgContent MsgContent
|
|
|
|
|
err := xml.Unmarshal(msg, &msgContent)
|
|
|
|
|
if nil != err {
|
|
|
|
|
fmt.Println("Unmarshal fail")
|
|
|
|
|
}
|
|
|
|
|
// fmt.Printf("1========>%v========>%v\n ", msgContent.MsgType, msgContent.Event)
|
|
|
|
|
switch msgContent.MsgType {
|
|
|
|
|
/*消息格式类型
|
|
|
|
|
*/
|
|
|
|
|
case "text": //文本
|
|
|
|
|
case "image": //图片
|
|
|
|
|
case "voice": //语音
|
|
|
|
|
case "video": //视频
|
|
|
|
|
case "location": //位置
|
|
|
|
|
|
|
|
|
|
case "link": //链接
|
|
|
|
|
/*事件格式类型*/
|
|
|
|
|
case "event":
|
|
|
|
|
/*
|
|
|
|
|
事件附属格式
|
|
|
|
|
*/
|
|
|
|
|
echoMsg = EventProcessing(msgContent.Event, msg, c)
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var callbackLog wechatcallback.CallbackLog
|
|
|
|
|
//
|
|
|
|
|
callbackLog.MsgSignature = c.MsgSignature
|
|
|
|
|
callbackLog.TimeStamp = c.Timestamp
|
|
|
|
|
callbackLog.Nonce = c.Nonce
|
|
|
|
|
callbackLog.Echostr = c.Echostr
|
|
|
|
|
callbackLog.Xmlstr = string(msg)
|
|
|
|
|
callbackLog.Reqdata = string(reqData)
|
|
|
|
|
msgCont, jsonErr := json.Marshal(msgContent)
|
|
|
|
|
if jsonErr == nil {
|
|
|
|
|
callbackLog.Jsonstr = string(msgCont)
|
|
|
|
|
}
|
|
|
|
|
callbackLog.AddTime = time.Now().Unix()
|
|
|
|
|
if msgContent.Event != "LOCATION" {
|
|
|
|
|
global.GVA_DB_WechatCallBack.Create(&callbackLog)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//企业微信事件处理
|
|
|
|
|
func EventProcessing(event string, decryptMsg []byte, v *CallBackData) (msg string) {
|
|
|
|
|
var msgContent MsgContentMailList
|
|
|
|
|
err := xml.Unmarshal(decryptMsg, &msgContent)
|
|
|
|
|
if nil != err {
|
|
|
|
|
fmt.Println("Unmarshal fail")
|
|
|
|
|
}
|
|
|
|
|
switch event {
|
|
|
|
|
case "subscribe": //关注
|
|
|
|
|
case "unsubscribe": //取消关注
|
|
|
|
|
case "enter_agent": //本事件在成员进入企业微信的应用时触发
|
|
|
|
|
case "LOCATION": //上报地理位置
|
|
|
|
|
GeographicalPosition(decryptMsg)
|
|
|
|
|
case "batch_job_result": //异步任务完成事件推送
|
|
|
|
|
case "change_contact": //通讯录变更事件
|
|
|
|
|
WorkWechatMailList(msgContent.ChangeType, decryptMsg)
|
|
|
|
|
case "click": //点击菜单拉取消息的事件推送
|
|
|
|
|
case "view": //点击菜单跳转链接的事件推送
|
|
|
|
|
case "scancode_push": //扫码推事件的事件推送
|
|
|
|
|
case "scancode_waitmsg": //扫码推事件且弹出“消息接收中”提示框的事件推送
|
|
|
|
|
case "pic_sysphoto": //弹出系统拍照发图的事件推送
|
|
|
|
|
case "pic_photo_or_album": //弹出拍照或者相册发图的事件推送
|
|
|
|
|
case "pic_weixin": //弹出微信相册发图器的事件推送
|
|
|
|
|
case "location_select": //弹出地理位置选择器的事件推送
|
|
|
|
|
case "open_approval_change": //审批状态通知事件 自建应用
|
|
|
|
|
OpenApprovalChange(decryptMsg)
|
|
|
|
|
case "sys_approval_change": //系统审批应用
|
|
|
|
|
case "share_agent_change": //企业互联共享应用事件回调
|
|
|
|
|
case "share_chain_change": //上下游共享应用事件回调
|
|
|
|
|
case "template_card_event": //模板卡片事件推送
|
|
|
|
|
|
|
|
|
|
var msgContent TemplateCardPush
|
|
|
|
|
err := xml.Unmarshal(decryptMsg, &msgContent)
|
|
|
|
|
if nil != err {
|
|
|
|
|
fmt.Println("***********Unmarshal fail")
|
|
|
|
|
}
|
|
|
|
|
buttonClick := strings.Split(msgContent.EventKey, "_")
|
|
|
|
|
buttonClickNAme := "已批准"
|
|
|
|
|
if len(buttonClick) >= 3 {
|
|
|
|
|
if buttonClick[2] != "1" {
|
|
|
|
|
buttonClickNAme = "已驳回"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("***********%v------------->%v------------->%v\n", buttonClick, buttonClick[2], msgContent)
|
|
|
|
|
xmlReply := fmt.Sprintf("<xml><ToUserName><![CDATA[%v]]></ToUserName><FromUserName><![CDATA[%v]]></FromUserName><CreateTime>%v</CreateTime><MsgType><![CDATA[update_button]]></MsgType><Button><ReplaceName><![CDATA[%v]]></ReplaceName></Button></xml>", msgContent.ToUsername, msgContent.FromUsername, msgContent.CreateTime, buttonClickNAme)
|
|
|
|
|
wecahtCpt := WechatVerification()
|
|
|
|
|
timestampStr := strconv.FormatInt(v.Timestamp, 10)
|
|
|
|
|
encryptMsg, cryptErr := wecahtCpt.EncryptMsg(xmlReply, timestampStr, v.Nonce)
|
|
|
|
|
if cryptErr == nil {
|
|
|
|
|
msg = string(encryptMsg)
|
|
|
|
|
} else {
|
|
|
|
|
msg = "25000"
|
|
|
|
|
}
|
|
|
|
|
templateEventPush(decryptMsg)
|
|
|
|
|
case "template_card_menu_event": //通用模板卡片右上角菜单事件推送
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//企业微信通讯录变更事件处理
|
|
|
|
|
func WorkWechatMailList(changeType string, decryptMsg []byte) {
|
|
|
|
|
switch changeType {
|
|
|
|
|
case "create_party": //新增部门事件
|
|
|
|
|
case "update_party": //更新部门事件
|
|
|
|
|
case "delete_party": //删除部门事件
|
|
|
|
|
case "update_tag": //标签成员变更事件
|
|
|
|
|
case "batch_job_result": //异步任务完成事件推送
|
|
|
|
|
case "change_contact": //通讯录变更事件
|
|
|
|
|
|
|
|
|
|
case "create_user": //新增成员事件
|
|
|
|
|
case "update_user": //更新成员事件
|
|
|
|
|
case "delete_user": //删除成员事件
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
}
|