利用Go制作微信机器人(一)发送消息



文章目录

  • 前言
  • 一、前置准备
  • 二、url验证
  • 三、消息回复
  • 四、总结



前言

上一篇介绍了如何使用go来主动给微信发送消息。这一节主要是介绍如何接收消息并回复。对于接收消息又可以分位文字消息,图片消息,视频消息等多种情况。本次我们只讨论最常用的文字消息(即发送给机器人文字,机器人也回复文字)

先展示成果:

java 自动操作微信客户端回复消息 java微信机器人回复_web


一、前置准备

打开企微管理页面,到我们要设置的回复机器人应用的管理界面后。点击下方的设置api接收。

java 自动操作微信客户端回复消息 java微信机器人回复_xml_02


然后随机生成TokenEncodingAESKey,这两个参数主要就是认证和识别的作用。将两值配置到我们的代码中,后续会用到。

java 自动操作微信客户端回复消息 java微信机器人回复_golang_03


然后从企微官方文档中下载官方封装好的解密加密的方法: 下载传送们 下载下来后,可以将wxbizmsgcrypt.go文件放到我们项目中的utils包中,因为此文件主要就是针对企微发来的信息进行解密加密等处理。

java 自动操作微信客户端回复消息 java微信机器人回复_golang_04


这样就可以通过调用 DecryptMsg对发来的数据进行解密,EncryptMsg对发出的数据进行加密。

二、url验证

在设置好上述操作后,就可以在应用配置页面处,配置我们的url了。首先微信会验证这个url是否可用,验证原理如下:

  1. 企微发送一段消息给到我们的服务端
  2. 服务端收到消息并解密出消息内容
  3. 将消息内容直接返回给微信

而且验证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请求。