go语言发送微信小程序模板消息

PS:开发微信的东西是真的心累,一大堆坑!文档写的乱七八糟的,找个东西都得半天。

为了发一条模板消息翻了无数个博客,很多都是把代码一放,其实代码这块很好弄,不就组装个数据调一下API吗,主要是前期工作。我把我遇到的坑给大家总结一下,希望后来人可以少走一些弯路。

微信发送模板消息文档:点击查看

  1. 超级大坑!发送接口问题

文档上是这个接口:

1
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN

实际不是的!!!!!是下面这个,看见没,多了一个wxopen,如果你碰到 48001 返回码,看看是不是这出问题了。

1
https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN
  1. 域名问题

    他们这个必须要配置合法域名,必须是https的,要有SSL证书的,在微信开发工具上可以看到你目前配置的合法域名

  1. 内网穿透问题

    因为formID必须要在真机上才可以获取,所以你最好设置一个内网穿透,让手机能访问到你本地的服务。

内网穿透工具可以去下面网站上下载,有免费的
https://www.ngrok.cc

  1. 证书问题
    在腾讯云上下载的证书是下面这个样子的:

这时你在go语言就不知道用哪个里面的证书了。。。我用的gofram框架,可以这样搞,
将Apache里面的

这两个东西,通过下面网站生成一个 .pem证书然后再用到go语言中就好使了。
https://www.myssl.cn/tools/merge-pem-cert.html
go语言中这样用

1
2
s.EnableHTTPS("https/ssl.pem", "https/3_pibigstar.com.key")
s.SetHTTPSPort(7777)

还有一个坑就是,端口不能是443,可能是我本机是Windows,把443端口屏蔽了,如果你一直出现404情况,换个端口!

大概就些坑,如果你碰到其他的坑可以给我留言,或关注我的微信公众号,希望可以帮到你。

放代码

go后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"

"github.com/pibigstar/go-todo/config"
"github.com/pibigstar/go-todo/constant"
"github.com/pibigstar/go-todo/models/db"
)

// 发送模板消息

var (
send_template_url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=%s"
get_access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
)

// SendTemplate 发送模板消息
func SendTemplate(msg *TemplateMsg) (*SendTemplateResponse, error) {
msg.Miniprogram.AppID = config.ServerConfig.Appid
accessToken, err := getAccessToken(msg.Touser)
if err != nil {
log.Error("获取accessToken失败")
return nil, err
}
url := fmt.Sprintf(send_template_url, accessToken.AccessToken)
data, err := json.Marshal(msg)
if err != nil {
log.Error("模板消息JSON格式错误", "err", err.Error())
return nil, err
}
client := http.Client{}
resp, err := client.Post(url, "application/json", bytes.NewBuffer(data))
if err != nil {
log.Error("网络错误,发送模板消息失败", "err", err.Error())
return nil, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var templateResponse SendTemplateResponse
err = json.Unmarshal(body, &templateResponse)
if err != nil {
log.Error("解析responseBody错误", "err", err.Error())
return nil, err
}
return &templateResponse, nil
}

func getAccessToken(openID string) (*GetAccessTokenResponse, error) {
var accessTokenResponse GetAccessTokenResponse
// 先从redis中拿
accessToken, err := getAccessTokenFromRedis(openID)
if accessToken != "" && err == nil {
accessTokenResponse = GetAccessTokenResponse{AccessToken: accessToken}
log.Info("从redis中获取到access_token", "access_token", accessToken)
return &accessTokenResponse, nil
}
appID := config.ServerConfig.Appid
secret := config.ServerConfig.Secret
url := fmt.Sprintf(get_access_token_url, appID, secret)
client := http.Client{}
resp, err := client.Get(url)
if err != nil {
log.Error("获取access_toke网络异常", "err", err.Error())
return nil, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &accessTokenResponse)
if err != nil {
log.Error("解析AccessToken失败", "err", err.Error())
return nil, err
}
// 存到redis中
if _, err := setAccessTokenToRedis(openID, accessTokenResponse.AccessToken); err != nil {
log.Error("将access_token存储到redis中失败", "err", err.Error())
}
return &accessTokenResponse, nil
}

// 从redis中取access_token
func getAccessTokenFromRedis(openID string) (string, error) {
key := fmt.Sprintf(constant.Access_Token_Redis_Prefix, openID)
accessToken, err := db.Redis.Get(key).Result()
return accessToken, err
}

// 将access_token存储到redis中
func setAccessTokenToRedis(openID, token string) (string, error) {
key := fmt.Sprintf(constant.Access_Token_Redis_Prefix, openID)
b, err := db.Redis.Set(key, token, 7200*time.Second).Result()
return b, err
}

type TemplateMsg struct {
Touser string `json:"touser"` //接收者的OpenID
TemplateID string `json:"template_id"` //模板消息ID
FormID string `json:"form_id"`
URL string `json:"url"` //点击后跳转链接
Miniprogram Miniprogram `json:"miniprogram"` //点击跳转小程序
Data *TemplateData `json:"data"`
}
type Miniprogram struct {
AppID string `json:"appid"`
Pagepath string `json:"pagepath"`
}

type TemplateData struct {
First KeyWordData `json:"first,omitempty"`
Keyword1 KeyWordData `json:"keyword1,omitempty"`
Keyword2 KeyWordData `json:"keyword2,omitempty"`
Keyword3 KeyWordData `json:"keyword3,omitempty"`
Keyword4 KeyWordData `json:"keyword4,omitempty"`
Keyword5 KeyWordData `json:"keyword5,omitempty"`
}

type KeyWordData struct {
Value string `json:"value"`
Color string `json:"color,omitempty"`
}

type SendTemplateResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
MsgID string `json:"msgid"`
}

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

controller例子

仅仅是个例子,供大家参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func init() {
s := g.Server()
s.BindHandler("/send", sendTemplate)
}

func sendTemplate(r *ghttp.Request) {
templateMsg := &utils.TemplateMsg{}
tempData := &utils.TemplateData{}
tempData.First.Value = "测试模板消息"
tempData.Keyword1.Value = "大家记得买票啊"
tempData.Keyword2.Value = "马上就要放假了,大家记得买回家的票啊"
tempData.Keyword3.Value = "2018-12-30 15:59"
tempData.Keyword4.Value = "派大星"
tempData.Keyword5.Value = "记得按时完成"
templateMsg.Data = tempData
formID := r.GetString("formID")
log.Info("formID", "formID", formID)
templateMsg.FormID = formID
openID, _ := middleware.GetOpenID(r)
templateMsg.Touser = openID
templateMsg.TemplateID = constant.Tmeplate_Receive_Task_ID
response, err := utils.SendTemplate(templateMsg)
if err != nil {
fmt.Println("发送模板消息失败", err.Error())
}
r.Response.WriteJson(response)
}

小程序端

1
2
3
4
5
<view>
<form bindsubmit="templateSend" report-submit="true">
<button type='primary' formType="submit" size='mini'>发送模板消息</button>
</form>
</view>
1
2
3
4
5
6
7
8
9
10
templateSend: function (e) {
// 表单需设置report-submit="true"
var formId = e.detail.formId;
// 发送随机模板消息
util.apiRequest("send","get",{
formID: formId,
}).then(data => {
console.log(data)
})
}
-------------本文结束感谢您的阅读-------------