Go 實戰丨微信公衆號接入及用戶消息處理

使用 Go 語言的 Web 框架 Gin 進行微信公衆號接入,並實現對微信消息的接收以及回覆處理。html

同時藉助 nginx 代理服務器對代理的端口號以及 URI 進行優化處理。nginx

在文章末尾給出該 Demo 的項目地址。git

目錄

  • 公衆號接入
  • 消息接收
  • 消息回覆
  • 使用 ngxin 代理服務器
  • 小結

公衆號接入

這裏使用微信公衆平臺提供的接口測試號用於開發使用,接口測試號申請github

公衆號的接入主要有兩個步驟,微信公衆平臺接入指南服務器

  1. 填寫服務器配置
  2. 驗證服務器地址的有效性

第一步須要配置服務器的 URL 地址,而且必須以 http://https:// 開頭,分別支持 80 端口和 443 端口;還需配置一個 3 ~ 32 位字符的 Token,用於消息驗證。微信

第二步用於驗證消息來源的正確性,當第一步配置完成並點提交後,微信服務器將發送 GET 請求到填寫的服務器地址上,GET 請求攜帶的參數及描述以下表所示:微信公衆平臺

參數 描述
signature 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。
timestamp 時間戳
nonce 隨機數
echostr 隨機字符串

服務器須要作的驗證操做流程大體爲:框架

  1. tokentimestampnonce 三個參數進行字典序排序;
  2. 將排序後的 tokentimestampnonce 三個參數按順序拼接成一個字符串,並對該字符串進行 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>
複製代碼
參數 描述
ToUserName 開發者微信號
FromUserName 發送方賬號(一個OpenID)
CreateTime 消息建立時間 (整型)
MsgType 消息類型,文本爲text
Content 文本消息內容
MsgId 消息id,64位整型

明白了微信服務器向開發服務器傳遞微信用戶消息的方式以及傳遞的數據包結構後,可知進行消息接收開發,大體須要進行兩個步驟:

  1. 建立處理微信服務器發送到開發服務器的 POST 類型請求的處理函數;
  2. 對請求中的 XML 數據包進行解析。

對於第二步,咱們能夠藉助 Gin 框架的 ShouldBindXMLBindXML 方法來對 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>
複製代碼
參數 是否必須 描述
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)
}
複製代碼

須要注意的是 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 框架以及藉助微信接口測試號,完成了對微信公衆號接入的開發,以及實現接收微信用戶消息和回覆微信用戶消息的兩個功能。

以及,感謝你們的耐心閱讀!!!

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

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

相關文章
相關標籤/搜索