使用 Go 語言的 Web 框架 Gin 進行微信公衆號接入,並實現對微信消息的接收以及回覆處理。html
同時藉助 nginx 代理服務器對代理的端口號以及 URI 進行優化處理。nginx
在文章末尾給出該 Demo 的項目地址。git
這裏使用微信公衆平臺提供的接口測試號用於開發使用,接口測試號申請。github
公衆號的接入主要有兩個步驟,微信公衆平臺接入指南:服務器
第一步須要配置服務器的 URL 地址,而且必須以 http://
或 https://
開頭,分別支持 80 端口和 443 端口;還需配置一個 3 ~ 32 位字符的 Token,用於消息驗證。微信
第二步用於驗證消息來源的正確性,當第一步配置完成並點提交後,微信服務器將發送 GET 請求到填寫的服務器地址上,GET 請求攜帶的參數及描述以下表所示:微信公衆平臺
參數 | 描述 |
---|---|
signature | 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。 |
timestamp | 時間戳 |
nonce | 隨機數 |
echostr | 隨機字符串 |
服務器須要作的驗證操做流程大體爲:框架
token
、timestamp
、nonce
三個參數進行字典序排序;token
、timestamp
、nonce
三個參數按順序拼接成一個字符串,並對該字符串進行 sha1
加密;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位整型 |
明白了微信服務器向開發服務器傳遞微信用戶消息的方式以及傳遞的數據包結構後,可知進行消息接收開發,大體須要進行兩個步驟:
POST
類型請求的處理函數;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>
複製代碼
參數 | 是否必須 | 描述 |
---|---|---|
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>
的數據包都沒法成功回覆。
將添加消息回覆的代碼更新至服務器後,向服務器發送消息將收到以下回復:
一般服務器都不會把 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…