使用 Go 语言的 Web 框架 Gin 进行微信公众号接入,并实现对微信消息的接收以及回复处理。

同时借助 nginx 代理服务器对代理的端口号以及 URI 进行优化处理。

在文章末尾给出该 Demo 的项目地址。

目录

  • 公众号接入
  • 消息接收
  • 消息回复
  • 使用 ngxin 代理服务器
  • 小结

公众号接入

这里使用微信公众平台提供的接口测试号用于开发使用,接口测试号申请。

公众号的接入主要有两个步骤,微信公众平台接入指南:

  1. 填写服务器配置
  2. 验证服务器地址的有效性
http://https://

第二步用于验证消息来源的正确性,当第一步配置完成并点提交后,微信服务器将发送 GET 请求到填写的服务器地址上,GET 请求携带的参数及描述如下表所示:

参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串

服务器需要做的验证操作流程大致为:

tokentimestampnoncetokentimestampnoncesha1signatureechostr

使用 Go 实现的微信公众号接入代码如下:

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"weixin-demo/util"
)

// 与填写的服务器配置中的Token一致
const Token = "coleliedev"

func main() {
	router := gin.Default()

	router.GET("/wx", WXCheckSignature)

	log.Fatalln(router.Run(":80"))
}

// WXCheckSignature 微信接入校验
func WXCheckSignature(c *gin.Context) {
	signature := c.Query("signature")
	timestamp := c.Query("timestamp")
	nonce := c.Query("nonce")
	echostr := c.Query("echostr")

	ok := util.CheckSignature(signature, timestamp, nonce, Token)
	if !ok {
		log.Println("微信公众号接入校验失败!")
		return
	}

	log.Println("微信公众号接入校验成功!")
	_, _ = c.Writer.WriteString(echostr)
}
复制代码
package util

import (
	"crypto/sha1"
	"encoding/hex"
	"sort"
	"strings"
)

// CheckSignature 微信公众号签名检查
func CheckSignature(signature, timestamp, nonce, token string) bool {
	arr := []string{timestamp, nonce, token}
	// 字典序排序
	sort.Strings(arr)

	n := len(timestamp) + len(nonce) + len(token)
	var b strings.Builder
	b.Grow(n)
	for i := 0; i < len(arr); i++ {
		b.WriteString(arr[i])
	}

	return Sha1(b.String()) == signature
}

// 进行Sha1编码
func Sha1(str string) string {
	h := sha1.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}
复制代码

最后,将项目部署至服务器,并在接口配置信息中点击提交按钮,完成微信公众号的接入。

nginx

消息接收

完成微信公众号的接入后,接下来以普通消息接收和被动回复用户消息这两个 API 为例,来完成 Go 对微信消息的接收和回复处理的具体实现。

首先是消息接收,参考微信官方文档,接收普通消息。

当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。

XML
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>
复制代码
参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,文本为text
Content 文本消息内容
MsgId 消息id,64位整型

明白了微信服务器向开发服务器传递微信用户消息的方式以及传递的数据包结构后,可知进行消息接收开发,大致需要进行两个步骤:

POSTXML
ShouldBindXMLBindXMLXML

使用 Go 实现的消息接收代码如下:

package main

import (
	"github.com/gin-gonic/gin"
	"log"
	"weixin-demo/util"
)

const Token = "coleliedev"

func main() {
	router := gin.Default()

	router.GET("/wx", WXCheckSignature)
	router.POST("/wx", WXMsgReceive)

	log.Fatalln(router.Run(":80"))
}

// WXTextMsg 微信文本消息结构体
type WXTextMsg struct {
	ToUserName   string
	FromUserName string
	CreateTime   int64
	MsgType      string
	Content      string
	MsgId        int64
}

// WXMsgReceive 微信消息接收
func WXMsgReceive(c *gin.Context) {
	var textMsg WXTextMsg
	err := c.ShouldBindXML(&textMsg)
	if err != nil {
		log.Printf("[消息接收] - XML数据包解析失败: %v\n", err)
		return
	}

	log.Printf("[消息接收] - 收到消息, 消息类型为: %s, 消息内容为: %s\n", textMsg.MsgType, textMsg.Content)
}
复制代码

将添加消息接收的代码更新至服务器后,对该接口测试号发送消息,可在服务器查看到如下记录:

消息回复

接下来以被动回复用户消息这个 API 为例,实现对微信用户发送的消息的回复,参考微信官方文档,被动消息回复。

XMLXML
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>
复制代码
参数 是否必须 描述
ToUserName 接收方帐号(收到的OpenID)
FromUserName 开发者微信号
CreateTime 消息创建时间 (整型)
MsgType 消息类型,文本为text
Content 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)

使用 Go 实现的消息回复代码如下:

package main

import (
	"encoding/xml"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"time"
	"weixin-demo/util"
)

const Token = "coleliedev"

func main() {
	router := gin.Default()

	router.GET("/wx", WXCheckSignature)
	router.POST("/wx", WXMsgReceive)

	log.Fatalln(router.Run(":80"))
}

// WXMsgReceive 微信消息接收
func WXMsgReceive(c *gin.Context) {
	var textMsg WXTextMsg
	err := c.ShouldBindXML(&textMsg)
	if err != nil {
		log.Printf("[消息接收] - XML数据包解析失败: %v\n", err)
		return
	}

	log.Printf("[消息接收] - 收到消息, 消息类型为: %s, 消息内容为: %s\n", textMsg.MsgType, textMsg.Content)

	// 对接收的消息进行被动回复
	WXMsgReply(c, textMsg.ToUserName, textMsg.FromUserName)
}

// WXRepTextMsg 微信回复文本消息结构体
type WXRepTextMsg struct {
	ToUserName   string
	FromUserName string
	CreateTime   int64
	MsgType      string
	Content      string
	// 若不标记XMLName, 则解析后的xml名为该结构体的名称
	XMLName      xml.Name `xml:"xml"`
}

// WXMsgReply 微信消息回复
func WXMsgReply(c *gin.Context, fromUser, toUser string) {
	repTextMsg := WXRepTextMsg{
		ToUserName:   toUser,
		FromUserName: fromUser,
		CreateTime:   time.Now().Unix(),
		MsgType:      "text",
		Content:      fmt.Sprintf("[消息回复] - %s", time.Now().Format("2006-01-02 15:04:05")),
	}

	msg, err := xml.Marshal(&repTextMsg)
	if err != nil {
		log.Printf("[消息回复] - 将对象进行XML编码出错: %v\n", err)
		return
	}
	_, _ = c.Writer.Write(msg)
}
复制代码
WXRepTextMsgXMLNamexmlxmlxmlxml.MarshalxmlXMLNamexmlxmlxmlxml

将添加消息回复的代码更新至服务器后,向服务器发送消息将收到如下回复:

使用 nginx 代理服务器

nginxnginx/weixin/wx
server {
	listen 80;

	location /weixin {
		proxy_pass http://127.0.0.1:8002/wx;
		proxy_redirect default;
	}
}
复制代码

修改程序监听的端口号为 8002:

func main() {
	router := gin.Default()

	router.GET("/wx", WXCheckSignature)
	router.POST("/wx", WXMsgReceive)

	log.Fatalln(router.Run(":8002"))
}
复制代码

修改微信公众号接入接口配置:

最后测试结果如下:

nginx/weixin/wxnginx

小结

最后做一个对该文章的小结,这篇文章主要使用了 Go 语言的 Gin 框架以及借助微信接口测试号,完成了对微信公众号接入的开发,以及实现接收微信用户消息和回复微信用户消息的两个功能。

以及,感谢大家的耐心阅读!!!

演示 Demo github 地址:github.com/hkail/weixi…

演示 Demo gitee 地址:gitee.com/hkail/weixi…