企业微信应用接收消息

使用golang编写企业微信应用,接收消息,以及进行被动回复。

Golang 接收企业微信消息

在实现一个企业微信中的机器人的时候遇到了一个问题,我想要获取用户输入的信息,而在企业微信API里面,对于消息的获取采取的是推的模式(webhook),由企业微信的服务器主动向我们推送消息,个人感觉相对于拉模式或者是websocket要麻烦一些。我们需要向企业微信提供一个“接收消息的服务器”。

企业应用接收来自企业微信的消息分为下面几个步骤

  1. 验证URL (验证服务器)

    参考 https://work.weixin.qq.com/api/doc/90000/90139/90968

    在添加应用后,企业微信的服务器会验证我们的接收消息的服务器的有效性,我们需要实现一个http服务器。我使用了已有的库进行加密解密 。 它们提供了解析JSON和xml两种格式信息的库,然而却没有办法指定传输的数据的格式是什么(默认是xml),真是令人费解。

    假设接收消息地址设置为:http://api.3dept.com/,企业微信将向该地址发送如下验证请求:

    请求方式:GET 请求地址:http://api.3dept.com/?msg_signature=ASDFQWEXZCVAQFASDFASDFSS&timestamp=13500001234&nonce=123412323&echostr=ENCRYPT_STR

  2. 接收消息

    在验证完成URL之后,消息都会以POST方式发送到同一链接下,企业应用服务器在收到请求后需要及时做出响应,返回头部状态码200的响应,其他响应码都会被当作失败,对没得到响应的POST请求,企业微信服务器会尝试三次重新请求。

  3. 被动回复信息

    在接收到信息后,做出响应的时候可以带上我们的回复。响应的消息内容需要为xml格式,在将回复消息结构体序列化成xml后,我们可以直接使用 wxbizmsgcrypt 包中的方法对其进行加密,得到加密后的xml信息,通过 Write 方法写回即可。

代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package bot

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"wxbot-test/wxbizmsgcrypt"
)

//ToUserName	成员UserID
//FromUserName	企业微信CorpID
//CreateTime	消息创建时间(整型)
//MsgType	消息类型,此时固定为:text
//Content	文本消息内容,最长不超过2048个字节,超过将截断
type ReplyTextMsg struct {
	ToUsername   string `xml:"ToUserName"`
	FromUsername string `xml:"FromUserName"`
	CreateTime   uint32 `xml:"CreateTime"`
	MsgType      string `xml:"MsgType"`
	Content      string `xml:"Content"`
}
type MsgContent struct {
	ToUsername   string `xml:"ToUserName"`
	FromUsername string `xml:"FromUserName"`
	CreateTime   uint32 `xml:"CreateTime"`
	MsgType      string `xml:"MsgType"`
	Content      string `xml:"Content"`
	Msgid        string `xml:"MsgId"`
	Agentid      uint32 `xml:"AgentId"`
}

var (
	corpId, token, encodingAesKey string
	wxcrypt                       *wxbizmsgcrypt.WXBizMsgCrypt
)

func init() {
	// 读取配置文件
	corpId = "ww592d57xxxxxxx"
	token = "VbmqbkBQWnxxxxxxxxx"
	encodingAesKey = "HDyFtR7DB6oyeG7DuzCOgjWg7xxxxxxxxxxxxxxxxx"
	// receive_id 企业应用的回调,表示corpid
	wxcrypt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, corpId, wxbizmsgcrypt.XmlType)
}

func Start() {
	// 开启一个http服务器,接收来自企业微信的消息
	http.HandleFunc("/api/bot/message", func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" {
			fmt.Println("接收到验证请求")
			handleVerify(w, r)
		} else if r.Method == "POST" {
			fmt.Println("接收到消息")
			handleMessage(w, r)
		}
	})
	log.Fatalln(http.ListenAndServe("127.0.0.1:8888", nil))
}
func handleMessage(w http.ResponseWriter, r *http.Request) {
	msgSignature := r.URL.Query().Get("msg_signature")
	timestamp := r.URL.Query().Get("timestamp")
	nonce := r.URL.Query().Get("nonce")
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Println("读取Body错误", err.Error())
	} else {
		msg, err := wxcrypt.DecryptMsg(msgSignature, timestamp, nonce, body)
		if err != nil {
			fmt.Println("解密消息Body错误", err.ErrMsg)
		} else {
			var msgContent MsgContent
			err := xml.Unmarshal(msg, &msgContent)
			if err != nil {
				fmt.Println("反序列化错误")
			} else {
				fmt.Println(msgContent)
				// 回复信息
				replyMsg, _ := xml.Marshal(ReplyTextMsg{
					ToUsername:   msgContent.FromUsername,
					FromUsername: msgContent.ToUsername,
					CreateTime:   msgContent.CreateTime,
					MsgType:      "text",
					Content:      "Receive",
				})
				encryptMsg, cryptErr := wxcrypt.EncryptMsg(string(replyMsg), timestamp, nonce)
				if cryptErr != nil {
					fmt.Println("回复加密出错", cryptErr)
				} else {
					fmt.Println(string(encryptMsg))
					l, err := w.Write(encryptMsg)
					if err != nil {
						fmt.Println("返回消息失败")
					} else {
						fmt.Println("成功写入", l)
					}
				}

			}
		}
	}
}
func handleVerify(w http.ResponseWriter, r *http.Request) {
	msgSignature := r.URL.Query().Get("msg_signature")
	timestamp := r.URL.Query().Get("timestamp")
	nonce := r.URL.Query().Get("nonce")
	echoStr := r.URL.Query().Get("echostr")
	// 合法性验证
	echoStrBytes, err := wxcrypt.VerifyURL(msgSignature, timestamp, nonce, echoStr)
	if err != nil {
		fmt.Println("验证失败", err.ErrMsg)
	} else {
		fmt.Println("验证成功", string(echoStrBytes))
		// 需要返回才能通过验证
		_, err := w.Write(echoStrBytes)
		if err != nil {
			fmt.Println("返回验证结果失败", err.Error())
		}
	}
}
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
本站访客数:
使用 Hugo 构建
主题 StackJimmy 设计