PS:开发微信的东西是真的心累,一大堆坑!文档写的乱七八糟的,找个东西都得半天。
为了发一条模板消息翻了无数个博客,很多都是把代码一放,其实代码这块很好弄,不就组装个数据调一下API吗,主要是前期工作。我把我遇到的坑给大家总结一下,希望后来人可以少走一些弯路。
微信发送模板消息文档:点击查看
文档上是这个接口:
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
实际不是的!!!!!是下面这个,看见没,多了一个wxopen,如果你碰到 48001 返回码,看看是不是这出问题了。
https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN
他们这个必须要配置合法域名,必须是https的,要有SSL证书的,在微信开发工具上可以看到你目前配置的合法域名
因为formID必须要在真机上才可以获取,所以你最好设置一个内网穿透,让手机能访问到你本地的服务。
内网穿透工具可以去下面网站上下载,有免费的
https://www.ngrok.cc
这时你在go语言就不知道用哪个里面的证书了。。。我用的gofram框架,可以这样搞,
将Apache里面的
这两个东西,通过下面网站生成一个 .pem
证书然后再用到go语言中就好使了。
https://www.myssl.cn/tools/merge-pem-cert.html
go语言中这样用
s.EnableHTTPS("https/ssl.pem", "https/3_pibigstar.com.key")
s.SetHTTPSPort(7777)
还有一个坑就是,端口不能是443,可能是我本机是Windows,把443端口屏蔽了,如果你一直出现404情况,换个端口!
大概就些坑,如果你碰到其他的坑可以给我留言,或关注我的微信公众号,希望可以帮到你。
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"`
}
仅仅是个例子,供大家参考
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)
}
<view>
<form bindsubmit="templateSend" report-submit="true">
<button type='primary' formType="submit" size='mini'>发送模板消息button>
form>
view>
templateSend: function (e) {
// 表单需设置report-submit="true"
var formId = e.detail.formId;
// 发送随机模板消息
util.apiRequest("send","get",{
formID: formId,
}).then(data => {
console.log(data)
})
}