利用Go制作微信机器人(一)发送消息
文章目录
- 前言
- 一、前置准备
- 二、url验证
- 三、消息回复
- 四、总结
前言
上一篇介绍了如何使用go来主动给微信发送消息。这一节主要是介绍如何接收消息并回复。对于接收消息又可以分位文字消息,图片消息,视频消息等多种情况。本次我们只讨论最常用的文字消息(即发送给机器人文字,机器人也回复文字)
先展示成果:
一、前置准备
打开企微管理页面,到我们要设置的回复机器人应用的管理界面后。点击下方的设置api接收。
然后随机生成Token
和EncodingAESKey
,这两个参数主要就是认证和识别的作用。将两值配置到我们的代码中,后续会用到。
然后从企微官方文档中下载官方封装好的解密加密的方法: 下载传送们 下载下来后,可以将wxbizmsgcrypt.go
文件放到我们项目中的utils包中,因为此文件主要就是针对企微发来的信息进行解密加密等处理。
这样就可以通过调用 DecryptMsg
对发来的数据进行解密,EncryptMsg
对发出的数据进行加密。
二、url验证
在设置好上述操作后,就可以在应用配置页面处,配置我们的url了。首先微信会验证这个url是否可用,验证原理如下:
- 企微发送一段消息给到我们的服务端
- 服务端收到消息并解密出消息内容
- 将消息内容直接返回给微信
而且验证url是要使用Get方法来验证,因此我们可以写一个Get的方法。
我们需要拿到get请求中的参数以及配置的token和EncodingAESKey对数据进行解密。具体代码如下
// handler
func VerifyMsg(c *gin.Context) {
verifyMsgSign, _ := c.GetQuery("msg_signature")
verifyTimestamp, _ := c.GetQuery("timestamp")
verifyNonce, _ := c.GetQuery("nonce")
verifyEchoStr, _ := c.GetQuery("echostr")
msg, err := service.VerifyMsg(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr)
if err != nil {
log.Logger.Fatal(err.Error())
return
}
if msg == "" {
msg = "NULL"
r.Fail(c, 200, "解析信息失败")
}
c.Writer.Write([]byte(msg))
}
// service包中验证方法
func VerifyMsg(verifyMsgSign string, verifyTimestamp string, verifyNonce string, verifyEchoStr string) (string, error) {
// 配置的token
token := c.Config.WeChat.Token
// 配置的EncodingAESKey
EncodingAESKey := c.Config.WeChat.EncodingAesKey
wxcpt := internal_utils.NewWXBizMsgCrypt(token, EncodingAESKey, "", internal_utils.XmlType)
echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr)
if nil != cryptErr {
fmt.Println("verifyUrl fail", cryptErr)
return "", errors.New(cryptErr.ErrMsg)
}
fmt.Println("verifyUrl success echoStr", string(echoStr))
return string(echoStr), nil
}
可以看出,主要是通过获取get请求中的参数,然后和配置文件中配置的两个参数一起拿出来,然后调用企微封装好的解密方法对数据进行解密。在解密完成后原封不动的将数据返回。
最后给这个方法配上对应的路由。
func setWeChatRouter(r *gin.Engine) {
// version 1
v1 := r.Group("wechat")
{
v1.GET("/receive", w.VerifyMsg)
}
}
启动项目后,就可以通过url验证了。
三、消息回复
对于第二步的操作,其实只是完成了对发来消息的解析,后边还要是实现对消息的回复。而且在实际企微给我们后端发消息时候,走的时候post请求。所以,我们可以直接重新写一个新的接口来实现。
由于微信发来的消息是xml文件,里边定义了从哪个用户发来,发来的消息类型,内容多多个信息。因此我将多种类型的信息进行了汇总,封装了一个结构体,用于存储微信发来的信息
// 微信消息结构体,这样封装有些暴力,对于每种消息都会造成多个字段空值。后续可改成针对不同类型使用不同的结构体
type MsgContent struct {
ToUsername string `xml:"ToUserName" json:"ToUserName"`
FromUsername string `xml:"FromUserName" json:"FromUserName"`
CreateTime uint32 `xml:"CreateTime" json:"CreateTime"`
MsgType string `xml:"MsgType" json:"MsgType"`
Content string `xml:"Content" json:"Content"` //内容为txt时,此字段保存发来的信息
PicUrl string `xml:"PicUrl" json:"PicUrl"` //图片信息
MediaId string `xml:"MediaId" json:"MediaId"` // 视频音频信息
MsgId string `xml:"MsgId" json:"MsgId"`
Format string `xml:"Format" json:"Format"` //多媒体数据格式
ThumbMediaId string `xml:"ThumbMediaId" json:"ThumbMediaId"`
Location_X string `xml:"Location_X" json:"Location_X"` //定位信息
Location_Y string `xml:"Location_Y" json:"Location_Y"` //定位信息
Scale string `xml:"Scale" json:"Scale"` //定位信息
Label string `xml:"Label" json:"Label"`
Agentid uint32 `xml:"AgentId" json:"Agentid"`
Latitude float32 `xml:"Latitude" json:"Latitude"`
Longitude float32 `xml:"Longitude" json:"Longitude"`
Precision int `xml:"Precision" json:"Precision"`
}
有了这个结构体后,就可以写接口了
// handler
func ReceiveMsg(c *gin.Context) {
verifyMsgSign, _ := c.GetQuery("msg_signature")
verifyTimestamp, _ := c.GetQuery("timestamp")
verifyNonce, _ := c.GetQuery("nonce")
b, _ := c.GetRawData()
msg, err := service.ReceiveMsg(verifyMsgSign, verifyTimestamp, verifyNonce, b)
if err != nil {
log.Logger.Fatal(err.Error())
return
}
if msg == "" {
msg = "NULL"
}
c.Writer.Write([]byte(msg))
}
// service包中的解析加密
func ReceiveMsg(reqMsgSign, reqTimestamp, reqNonce string, reqData []byte) (string, error) {
token := c.Config.WeChat.Token
EncodingAESKey := c.Config.WeChat.EncodingAesKey
wxcpt := internal_utils.NewWXBizMsgCrypt(token, EncodingAESKey, "", internal_utils.XmlType)
msg, cryptErr := wxcpt.DecryptMsg(reqMsgSign, reqTimestamp, reqNonce, reqData)
if nil != cryptErr {
fmt.Println("DecryptMsg fail", cryptErr)
}
var msgContent model.MsgContent
err := xml.Unmarshal(msg, &msgContent) //
if err != nil {
return "", errors.New("unmarshal fail")
}
bt, _ := json.Marshal(msgContent)
str := utils.ByteSliceToString(msg)
log.Logger.Sugar().Infof("received message: [%s]", str)
// todo,此处是我个人定制的方法,用于对发来消息的处理。返回的m依旧是model.MsgContent类型
m, err := receive.ReceiveMsg(msgContent)
if err != nil {
return "", err
}
bt, _ = xml.Marshal(m)
str = utils.ByteSliceToString(bt)
encryptMsg, _ := wxcpt.EncryptMsg(str, reqTimestamp, reqNonce)
return string(encryptMsg), nil
}
注意上方的m, err := receive.ReceiveMsg(msgContent)
这个是我个人真对发来的消息进行一些个性化处理的方法,在初次调试阶段,可以直接将解密好的信息重新进行加密后返回,这样就可以达到你发了什么,机器人就是复读机重复你的内容。
最终,添加好路由,重新启动项目,即可实现自动回复消息的功能
func setWeChatRouter(r *gin.Engine) {
// version 1
v1 := r.Group("wechat")
{
//验证时使用,验证通过后可注释掉
v1.GET("/receive", w.VerifyMsg)
v1.POST("/receive", w.ReceiveMsg)
}
}
四、总结
对于微信回复消息,首先要配置url,然后验证url,在验证完成后要实现回复消息的功能。其中验证和回复的功能可以分成两个方法来写。因为微信针对验证使用的是get请求,在实际我们给机器人发消息时使用的是post请求。