使用Golang实现高效的微信公众号后台开发与接口调用实践

引言

一、开发前准备

1. 注册微信公众号
  1. 百度搜索“微信公众号”进入官网。
  2. 选择注册类型(订阅号或服务号),填写相关信息完成注册。
  3. 注册完成后,进入公众号主页,下拉至“设置与开发”选项。
2. 获取AppID和AppSecret

在“设置与开发”中,点击“基本配置”,找到并记录AppID和AppSecret。这两个参数在后续代码开发中会用到。

3. 服务器配置

为了方便本地开发调试,可以使用内网穿透工具ngrok将本地服务器映射到公网地址。具体操作如下:

  1. 下载ngrok可执行文件。
  2. 在命令行中输入命令启动ngrok:ngrok.exe http 80,将本地80端口映射到公网地址。
  3. 将映射的公网地址填入微信公众号的“服务器配置”中的URL字段。
  4. Token字段填写一个自定义的字符串,用于后续代码验证。
  5. 选择明文模式。

至此,基本配置完成,接下来进入代码开发阶段。

二、代码编写

1. 创建Web服务器
package main

import (
    "fmt"
    "net/http"
    "wechat-oa/route"
)

func main() {
    fmt.Println("微信公众号服务器程序启动")
    http.HandleFunc("/", route.WechatServer) // 处理微信服务器请求
    err := http.ListenAndServe(":80", nil)
    if err != nil {
        fmt.Println("服务器启动失败:", err)
    }
}
2. 处理微信服务器请求
package route

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "wechat-oa/util"
)

func WechatServer(w http.ResponseWriter, r *http.Request) {
    // 验证微信服务器请求
    if !util.ValidateWechatRequest(r) {
        fmt.Fprintf(w, "验证失败")
        return
    }

    // 读取请求体
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        fmt.Println("读取请求体失败:", err)
        return
    }
    defer r.Body.Close()

    // 处理请求
    response := util.HandleWechatMessage(string(body))
    fmt.Fprintf(w, response)
}
3. 验证微信服务器请求
package util

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

func ValidateWechatRequest(r *http.Request) bool {
    token := "your_token" // 自定义的Token字符串
    signature := r.URL.Query().Get("signature")
    timestamp := r.URL.Query().Get("timestamp")
    nonce := r.URL.Query().Get("nonce")
    echostr := r.URL.Query().Get("echostr")

    // 将token、timestamp、nonce按字典序排序
    strSlice := []string{token, timestamp, nonce}
    sort.Strings(strSlice)
    str := strings.Join(strSlice, "")

    // 对排序后的字符串进行sha1加密
    h := sha1.New()
    h.Write([]byte(str))
    encryptedStr := hex.EncodeToString(h.Sum(nil))

    // 比较加密后的字符串与signature
    return encryptedStr == signature
}
4. 处理微信消息
package util

import (
    "encoding/xml"
    "fmt"
    "time"
)

type WechatMessage struct {
    ToUserName   string
    FromUserName string
    CreateTime   int
    MsgType      string
    Content      string
    MsgId        string
}

type WechatResponse struct {
    XMLName      xml.Name `xml:"xml"`
    ToUserName   string
    FromUserName string
    CreateTime   int
    MsgType      string
    Content      string
}

func HandleWechatMessage(msg string) string {
    var message WechatMessage
    xml.Unmarshal([]byte(msg), &message)

    response := WechatResponse{
        ToUserName:   message.FromUserName,
        FromUserName: message.ToUserName,
        CreateTime:   time.Now().Unix(),
        MsgType:      "text",
        Content:      "收到您的消息:" + message.Content,
    }

    responseBytes, _ := xml.Marshal(response)
    return string(responseBytes)
}

三、调用微信高级接口

1. 获取Access Token
package util

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

type AccessTokenResponse struct {
    AccessToken string `json:"access_token"`
    ExpiresIn   int    `json:"expires_in"`
}

func GetAccessToken(appID, appSecret string) (string, error) {
    url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, appSecret)
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    var tokenResp AccessTokenResponse
    json.Unmarshal(body, &tokenResp)
    return tokenResp.AccessToken, nil
}
2. 调用自定义菜单接口

使用获取到的Access Token调用自定义菜单接口。以下是一个示例代码:

package util

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

type MenuButton struct {
    Type      string `json:"type"`
    Name      string `json:"name"`
    Key       string `json:"key"`
    URL       string `json:"url"`
    SubButton []MenuButton `json:"sub_button,omitempty"`
}

type Menu struct {
    Button []MenuButton `json:"button"`
}

func CreateMenu(accessToken string, menu Menu) error {
    menuBytes, _ := json.Marshal(menu)
    url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s", accessToken)
    resp, err := http.Post(url, "application/json", bytes.NewReader(menuBytes))
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    fmt.Println("创建菜单响应:", string(body))
    return nil
}

四、总结

参考文献

  1. 《微信公众号开发从入门到精通》
  2. 微信官方开发者文档
  3. Golang官方文档

结语