微信公众号接入及用户消息处理

发表于:2020-5-06 13:14

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:ColeLie    来源:掘金

  使用 Go 语言的 Web 框架 Gin 进行微信公众号接入,并实现对微信消息的接收以及回复处理。
  同时借助 nginx 代理服务器对代理的端口号以及 URI 进行优化处理。
  公众号接入
  这里使用微信公众平台提供的接口测试号用于开发使用,接口测试号申请。
  公众号的接入主要有两个步骤,微信公众平台接入指南:
  填写服务器配置
  验证服务器地址的有效性
  第一步需要配置服务器的 URL 地址,并且必须以 http:// 或 https:// 开头,分别支持 80 端口和 443 端口;还需配置一个 3 ~ 32 位字符的 Token,用于消息验证。
  第二步用于验证消息来源的正确性,当第一步配置完成并点提交后,微信服务器将发送 GET 请求到填写的服务器地址上,GET 请求携带的参数及描述如下表所示:
  服务器需要做的验证操作流程大致为:
  1.
  对 token、timestamp、nonce 三个参数进行字典序排序;
  2.将排序后的 token、timestamp、nonce 三个参数按顺序拼接成一个字符串,并对该字符串进行 sha1 加密;
  3.使用加密后的字符串与 signature 参数进行比较,如果字符串值相同,则表示校验通过,将 echostr 参数原样返回即可。
  使用 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))
  }
  最后,将项目部署至服务器,并在接口配置信息中点击提交按钮,完成微信公众号的接入。
  需注意,由于该 Web 程序需监听 80 端口,所以服务器不能有其他监听 80 端口的程序,如 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>
 
  明白了微信服务器向开发服务器传递微信用户消息的方式以及传递的数据包结构后,可知进行消息接收开发,大致需要进行两个步骤:
  1.创建处理微信服务器发送到开发服务器的 POST 类型请求的处理函数;
  2.对请求中的 XML 数据包进行解析。
  对于第二步,我们可以借助 Gin 框架的 ShouldBindXML 或 BindXML 方法来对 XML 数据包进行解析。
  使用 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 为例,实现对微信用户发送的消息的回复,参考微信官方文档,被动消息回复。
  消息回复与消息接收类似,都需要使用 XML 格式的数据包,回复文本消息需要的 XML 数据包结构以及参数如下:
   <xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
  </xml>
  
  使用 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)
  }
  需要注意的是 WXRepTextMsg 结构体中必须添加 XMLName 属性,并且对该属性进行 xml 标记,用于将该 xml 名标记为 xml,即使用 xml.Marshal 方法对该结构体对象进行编码后,得到的 xml 数据的最外层标签为 <xml></xml>,如若不添加该 XMLName 属性,则编码后得到的 xml 数据的最外层标签为 <WXRepTextMsg></WXRepTextMsg>,不符合微信官方要求的 xml 数据包格式,因此所有 xml 名称即编码后的 xml 数据的最外层标签不为 <xml></xml> 的数据包都无法成功回复。
  将添加消息回复的代码更新至服务器后,向服务器发送消息将收到如下回复:

  使用 nginx 代理服务器
  通常服务器都不会把 80 端口或 443 端口交给 Web 程序,这时可使用 nginx 作为代理服务器,将 Web 程序跑在其它端口上,如 8002,让 nginx 监听 80 端口或 443 端口,并对指定的 URI 进行反向代理操作,如以下配置,将把 80 端口 URI 为 /weixin 的请求代理到服务器本地的 8002 端口的 /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 的日志文件中可以看到其确实收到了 URI 为 /weixin 的请求,并且在该 Web 程序的日志文件中,也可以看到其收到的 URI 为 /wx 的请求,通过观察两份日志记录的请求参数,可以发现,nginx 做的代理是成功的。
  小结
  最后做一个对该文章的小结,这篇文章主要使用了 Go 语言的 Gin 框架以及借助微信接口测试号,完成了对微信公众号接入的开发,以及实现接收微信用户消息和回复微信用户消息的两个功能。

      本文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号