golang 小游戏添加客服(微信版)
教程只是提供思路, 让你快速完成你想要的功能, 不要全部依赖于此.
不喜勿碰.
大致思路:
- 上传图片 获取 media_id (当然你发送不是图片你可以不获取这个media_id)
- 获取 access_token
- 判定用户发送的消息类型, 做出相应的消息回复.
这里我说明一下, 我这里的需求是要送图片的.
我这里的思路是:
1.先将要发送的图片上传到 mongodb 数据库.
2.从数据库中读取图片数据
3.获取media_id, 并保存media_id, 因为这 media_id 有个时效性, 你可以保存一份, 减少请求,加快相应速度.
4.获取access_token, 并保存, 原因同上.
5.判定用户发送的消息类型, 做出相应的消息回复.
基础代码
// 对内置的 http的 Post 和 Get 做了一个简单的封装
func HttpPost(url, params string, contentType string, retMap interface{}) error {
if contentType == "" {
contentType = "application/json"
// "application/x-www-form-urlencoded"
}
resp, err := http.Post(url,
contentType,
strings.NewReader(params))
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(body, retMap)
if err != nil {
return err
}
return nil
}
func HttpGet(url string, retMap interface{}) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(body, retMap)
if err != nil {
return err
}
return nil
}
客服消息
type MediaResp struct {
ErrorCode int `json:"errcode"`
ErrorMsg string `json:"errmsg"`
Type string `json:"type"`
MediaID string `json:"media_id"`
CreatedAt int64 `json:"created_at"`
}
type StoreMdeiaKV struct {
MediaId string
Expires int64
CreateTime int64
}
func getWechatMediaId(fileName, appid, appSecret, projName, platform string, data []byte, retMap *MediaResp) error {
buf := new(bytes.Buffer)
w := multipart.NewWriter(buf)
err := w.WriteField("contentType", "image/jpeg")
if err != nil {
return err
}
fw, err := w.CreateFormFile("value", fileName)
if err != nil {
return err
}
// image/jpeg
_, err = fw.Write(data)
if err != nil {
return err
}
_ = w.Close()
err, retToken := checkAndGetTokenWechat(fmt.Sprintf("%s-%s", projName, platform), appid, appSecret)
if err != nil {
return err
}
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=image", retToken.Token)
resp, err := http.Post(url,
w.FormDataContentType(),
buf)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(body, retMap)
if err != nil {
return err
}
return nil
}
var _mediaMap sync.Map = sync.Map{}
func checkMediaIdTime(key string, fileName, appid, appSecret, projName, platform string, data []byte) (error, *StoreMdeiaKV) {
retMap := &MediaResp{}
if val, ok := _mediaMap.Load(key); !ok {
err := getWechatMediaId(fileName, appid, appSecret, projName, platform, data, retMap)
if err != nil {
return err, nil
}
d := &StoreMdeiaKV{
MediaId: retMap.MediaID,
Expires: time.Now().Unix() + int64(3*24*259200-3600),
CreateTime: time.Now().Unix(),
}
fmt.Println(fmt.Sprintf("media_id key not exist | expires ==> %d | key ==>%s", d.Expires, key))
_mediaMap.Store(key, d)
return nil, d
} else {
d := val.(*StoreMdeiaKV)
t := time.Now().Unix()
if t >= d.Expires {
fmt.Println(fmt.Sprintf("expires d ==> %d, t ==> %d, key ==> %s", d.Expires, t, key))
err := getWechatMediaId(fileName, appid, appSecret, projName, platform, data, retMap)
if err != nil {
return err, nil
}
d := &StoreMdeiaKV{
MediaId: retMap.MediaID,
Expires: time.Now().Unix() + int64(3*24*259200-3600),
CreateTime: time.Now().Unix(),
}
_mediaMap.Store(key, d)
return nil, d
} else {
return nil, d
}
}
}
func CheckMediaId(projName, platform string) (bool, *StoreMdeiaKV) {
key := fmt.Sprintf("%s-%s", projName, platform)
if val, ok := _mediaMap.Load(key); !ok {
return false, nil
} else {
d := val.(*StoreMdeiaKV)
t := time.Now().Unix()
if t >= d.Expires {
return false, nil
} else {
return true, d
}
}
}
func GetMediaId(fileName, appid, appSecret, projName, platform string, data []byte) (error, *StoreMdeiaKV) {
return checkMediaIdTime(fmt.Sprintf("%s-%s", projName, platform), fileName, appid, appSecret, projName, platform, data)
}
var _tokenMap sync.Map = sync.Map{}
type storeTokenKV struct {
Token string
Expires int64
CreateTime int64
}
type WechatTokenRetMap struct {
Token string `json:"access_token"`
Expires int64 `json:"expires_in"`
Errcode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func getTokenWechat(appid, appSecret string, retMap *WechatTokenRetMap) error {
url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
getUrl := fmt.Sprintf(url, appid, appSecret)
err := HttpGet(getUrl, retMap)
if err != nil {
return err
}
if retMap.Errcode != 0 {
return errors.New(fmt.Sprintf("code:%d,errmsg:%s", retMap.Errcode, retMap.ErrMsg))
}
return nil
}
func checkAndGetTokenWechat(key, appid, appSecret string) (error, *storeTokenKV) {
if val, ok := _tokenMap.Load(key); !ok {
retToken := &WechatTokenRetMap{}
err := getTokenWechat(appid, appSecret, retToken)
if err != nil {
return err, nil
}
d := &storeTokenKV{
Token: retToken.Token,
Expires: retToken.Expires,
CreateTime: time.Now().Unix(),
}
_tokenMap.Store(key, d)
return nil, d
} else {
d := val.(*storeTokenKV)
t := time.Now().Unix() - d.CreateTime + 60
if t >= d.Expires {
retToken := &WechatTokenRetMap{}
err := getTokenWechat(appid, appSecret, retToken)
if err != nil {
return err, nil
}
d := &storeTokenKV{
Token: retToken.Token,
Expires: retToken.Expires,
CreateTime: time.Now().Unix(),
}
_tokenMap.Store(key, d)
return nil, d
} else {
return nil, d
}
}
}
type SendServiceMsgResp struct {
ErrorCode int `json:"errCode"`
ErrorMsg string `json:"errMsg"`
}
type SendServiceMsgInnerReq struct {
MediaId string `json:"media_id"`
}
type SendServiceMsgReq struct {
Touser string `json:"touser"`
MsgType string `json:"msgtype"`
Image SendServiceMsgInnerReq `json:"image"`
}
func SendServiceMessage(appid, appSecret, projName, platform string, retMap *SendServiceMsgResp, d interface{}) error {
// https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
err, retToken := checkAndGetTokenWechat(fmt.Sprintf("%s-%s", projName, platform), appid, appSecret)
if err != nil {
return err
}
//err, retMedia := GetMediaId(fileName, appid, appSecret, projName, platform, data)
//
//if err != nil {
// return err
//}
fmt.Println(fmt.Sprintf("token ==> %s", retToken))
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s", retToken.Token)
dJson, _ := json.Marshal(d)
sJson := string(dJson)
err = HttpPost(url, sJson, "", retMap)
if err != nil {
return err
}
return nil
}
解释微信数据
我这里没有验证是不是来自微信. 直接返回
type Cs struct {
FromUserName string `json:"FromUserName"`
ToUserName string `json:"ToUserName"`
Content string `json:"Content"`
CreateTime int64 `json:"CreateTime"`
MsgId int64 `json:"MsgId"`
MsgType string `json:"MsgType"`
Encrypt string `json:"Encrypt"`
}
func CustomerService(w http.ResponseWriter, r *http.Request) {
Log.Log.Debugf("come in wechat msg | request method ==> %s", r.Method)
if r.Method == "GET" {
query := r.URL.Query()
// query["signature"][0]
// query["timestamp"][0]
// query["nonce"][0]
echostr := query["echostr"][0]
_, _ = w.Write([]byte(echostr))
} else {
req := &Cs{}
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
Log.Log.Errorf(fmt.Sprintf("errr ==> %s", err.Error()))
_, _ = w.Write([]byte("success"))
return
}
}
调用例子
func test() {
resp := &Common.SendServiceMsgResp{}
data := map[string]interface{}{
"touser": req.FromUserName,
"msgtype": "text",
"text": map[string]interface{}{
"content": "消息测试",
},
}
err := Common.SendServiceMessage(g.AppId, g.AppSecret, g.ProjName, g.Platform, resp, data)
if err != nil {
Log.Log.Errorf("send customer service msg fail | err ===> %s", err.Error())
return Common.CodeError, nil
}
}