關(guān)注并使用過微信“飛常準(zhǔn)”公眾號的朋友們都有過如下體驗(yàn):查詢一個(gè)航班情況后,這個(gè)航班的checkin、登機(jī)、起降等信息都會(huì)在后續(xù)陸續(xù)異步發(fā)給你,這個(gè)服務(wù)就是通過微信公眾平臺的客服消息實(shí)現(xiàn)的。
微信公眾平臺開發(fā)文檔中關(guān)于客服消息的解釋如下:“當(dāng)用戶主動(dòng)發(fā)消息給公眾號的時(shí)候(包括發(fā)送信息、點(diǎn)擊自定義菜單、訂閱事件、掃描二維碼事件、支付成功 事件、用戶維權(quán)),微信將會(huì)把消息數(shù)據(jù)推送給開發(fā)者,開發(fā)者在一段時(shí)間內(nèi)(目前修改為48小時(shí))可以調(diào)用客服消息接口,通過POST一個(gè)JSON數(shù)據(jù)包來 發(fā)送消息給普通用戶,在48小時(shí)內(nèi)不限制發(fā)送次數(shù)。此接口主要用于客服等有人工消息處理環(huán)節(jié)的功能,方便開發(fā)者為用戶提供更加優(yōu)質(zhì)的服務(wù)”。
這篇文章我們就來說說如何用golang實(shí)現(xiàn)發(fā)送文本客服消息。
一、獲取access_token
access_token是公眾號的全局唯一票據(jù),公眾號調(diào)用微信平臺各接口時(shí)都需使用access_token。我們要主動(dòng)給微信平臺發(fā)送客服消息,該access_token就是我們的憑證。在構(gòu)造和下發(fā)客服消息前,我們需要獲取這個(gè)access_token。
access_token的有效期為2小時(shí)(7200s),我們獲取一次,兩小時(shí)內(nèi)均可使用。微信公眾平臺開發(fā)文檔也給出了access_token獲取、保存以及刷新的技術(shù)建議。但我們這里僅是Demo,無需考慮這么多。
通過https GET請求,我們可以得到屬于我們的access_token,請求line為:
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
golang提供了默認(rèn)的http client實(shí)現(xiàn),通過默認(rèn)的client實(shí)現(xiàn)我們可以很容器的獲取access_token。
const (
token = "wechat4go"
appID = "wx8e0fb2659c2eexxx"
appSecret = "22746009b0162fe50cb915851c53fyyy"
accessTokenFetchUrl = "https://api.weixin.qq.com/cgi-bin/token"
)
func fetchAccessToken() (string, float64, error) {
requestLine := strings.Join([]string{accessTokenFetchUrl,
"?grant_type=client_credential&appid=",
appID,
"&secret=",
appSecret}, "")
resp, err := http.Get(requestLine)
if err != nil || resp.StatusCode != http.StatusOK {
return "", 0.0, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", 0.0, err
}
fmt.Println(string(body))
… …
}
無論成功與否,微信平臺都會(huì)返回一個(gè)包含json數(shù)據(jù)的應(yīng)答:
如果獲取正確,那么應(yīng)答里的Json數(shù)據(jù)為:
{"access_token":"0QCeHwiRtPRUCiM5MM0cSPYIP5QOUNYdb8usRSgVZcsFuVF6mu3vQq41OIifJdrtJPGn7b1x90HdvUanpb7eZHxg40B6bU_Sgszh2byyF40","expires_in":7200}
如果獲取錯(cuò)誤,那么應(yīng)答里的Json數(shù)據(jù)為:
{"errcode":40001,"errmsg":"invalid credential"}
和xml數(shù)據(jù)包一樣,golang也提供了json格式數(shù)據(jù)包的Marshal和Unmarshal方法,且使用方式相同,也是將一個(gè)json數(shù)據(jù)包與一 個(gè)struct對應(yīng)起來。從上面來看,通過http response,我們無法區(qū)分出是否成功獲取了token,因此我們需要首先判斷試下body中是否包含某些特征字符串,比 如"access_token":
if bytes.Contains(body, []byte("access_token")) {
//unmarshal to AccessTokenResponse struct
} else {
//unmarshal to AccessTokenErrorResponse struct
}
針對獲取成功以及失敗的兩種Json數(shù)據(jù),我們定義了兩個(gè)結(jié)構(gòu)體:
type AccessTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn float64 `json:"expires_in"`
}
type AccessTokenErrorResponse struct {
Errcode float64
Errmsg string
}
Json unmarshal的代碼片段如下:
//Json Decoding
if bytes.Contains(body, []byte("access_token")) {
atr := AccessTokenResponse{}
err = json.Unmarshal(body, &atr)
if err != nil {
return "", 0.0, err
}
return atr.AccessToken, atr.ExpiresIn, nil
} else {
fmt.Println("return err")
ater := AccessTokenErrorResponse{}
err = json.Unmarshal(body, &ater)
if err != nil {
return "", 0.0, err
}
return "", 0.0, fmt.Errorf("%s", ater.Errmsg)
}
我們的main函數(shù)如下:
func main() {
accessToken, expiresIn, err := fetchAccessToken()
if err != nil {
log.Println("Get access_token error:", err)
return
}
fmt.Println(accessToken, expiresIn)
}
編譯執(zhí)行,成功獲取access_token的輸出如下:
0QCeHwiRtPRUCiM5MM0cSPYIP5QOUNYdb8usRSgVZcsFuVF6mu3vQq41OIifJdrtJPGn7b1x90HdvUanpb7eZHxg40B6bU_Sgszh2byyF40 7200
失敗時(shí),輸出如下:
2014/12/30 12:39:56 Get access_token error: invalid credential
二、發(fā)送客服消息
平臺開發(fā)文檔中定義了文本客服消息的body格式,一個(gè)json數(shù)據(jù):
{
"touser":"OPENID",
"msgtype":"text",
"text":
{
"content":"Hello World"
}
}
其中的touser填寫的是openid。之前的文章中提到過,每個(gè)微信用戶針對某一個(gè)訂閱號/服務(wù)號都有唯一的OpenID,這個(gè)ID可以在微信訂閱號 /服務(wù)號管理頁面中看到,也可以在收到的微信平臺轉(zhuǎn)發(fā)的消息中看到(FromUserName)。比如我個(gè)人訂閱的我的測試體驗(yàn)號后得到的OpenID 為:
BQcwuAbKpiSAbbvd_DEZg7q27QI
我們要做的就是構(gòu)造這樣一個(gè)json數(shù)據(jù),并放入HTTP Post包中,發(fā)到:
https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
從平臺開發(fā)文檔給出的json數(shù)據(jù)包樣例來看,這是個(gè)嵌套json數(shù)據(jù)包,我們通過下面方法marshall:
type CustomServiceMsg struct {
ToUser string `json:"touser"`
MsgType string `json:"msgtype"`
Text TextMsgContent `json:"text"`
}
type TextMsgContent struct {
Content string `json:"content"`
}
func pushCustomMsg(accessToken, toUser, msg string) error {
csMsg := &CustomServiceMsg{
ToUser: toUser,
MsgType: "text",
Text: TextMsgContent{Content: msg},
}
body, err := json.MarshalIndent(csMsg, " ", " ")
if err != nil {
return err
}
fmt.Println(string(body))
… …
}
如果單純輸出上面marshal的結(jié)果,可以看到:
{
"touser": "oBQcwuAbKpiSAbbvd_DEZg7q27QI",
"msgtype": "text",
"text": {
"content": "你好"
}
}
接下來將marshal后的[]byte放入一個(gè)http post的body中,發(fā)送到指定url中:
var openID = "oBQcwuAbKpiSAbbvd_DEZg7q27QI"
func pushCustomMsg(accessToken, toUser, msg string) error {
… …
postReq, err := http.NewRequest("POST",
strings.Join([]string{customServicePostUrl, "?access_token=", accessToken}, ""),
bytes.NewReader(body))
if err != nil {
return err
}
postReq.Header.Set("Content-Type", "application/json; encoding=utf-8")
client := &http.Client{}
resp, err := client.Do(postReq)
if err != nil {
return err
}
resp.Body.Close()
return nil
}
我們在main函數(shù)中加上客服消息的發(fā)送環(huán)節(jié):
func main() {
// Fetch access_token
accessToken, expiresIn, err := fetchAccessToken()
if err != nil {
log.Println("Get access_token error:", err)
return
}
fmt.Println(accessToken, expiresIn)
// Post custom service message
msg := "你好"
err = pushCustomMsg(accessToken, openID, msg)
if err != nil {
log.Println("Push custom service message err:", err)
return
}
}
編譯執(zhí)行,手機(jī)響起提示音,打開觀看,微信公眾平臺測試號發(fā)來消息:“你好”。
上述Demo完整代碼在這里可以看到,別忘了appID,appSecret改成你自己的值。
目前客服接口僅提供給認(rèn)證后的訂閱號以及服務(wù)號,對于未認(rèn)證的訂閱號,無法發(fā)送客服消息。
2014, bigwhite. 版權(quán)所有.
Related posts: