mirror of
https://github.com/silenceper/wechat.git
synced 2026-03-01 00:35:26 +08:00
Compare commits
3 Commits
v2.1.5-rc.
...
d92cd35533
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d92cd35533 | ||
|
|
58621cd79d | ||
|
|
8821a3856d |
@@ -12,9 +12,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// AccessTokenURL 获取access_token的接口
|
// accessTokenURL 获取access_token的接口
|
||||||
accessTokenURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
|
accessTokenURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
|
||||||
// AccessTokenURL 企业微信获取access_token的接口
|
// stableAccessTokenURL 获取稳定版access_token的接口
|
||||||
|
stableAccessTokenURL = "https://api.weixin.qq.com/cgi-bin/stable_token"
|
||||||
|
// workAccessTokenURL 企业微信获取access_token的接口
|
||||||
workAccessTokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
|
workAccessTokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
|
||||||
// CacheKeyOfficialAccountPrefix 微信公众号cache key前缀
|
// CacheKeyOfficialAccountPrefix 微信公众号cache key前缀
|
||||||
CacheKeyOfficialAccountPrefix = "gowechat_officialaccount_"
|
CacheKeyOfficialAccountPrefix = "gowechat_officialaccount_"
|
||||||
@@ -79,17 +81,87 @@ func (ak *DefaultAccessToken) GetAccessTokenContext(ctx context.Context) (access
|
|||||||
|
|
||||||
// cache失效,从微信服务器获取
|
// cache失效,从微信服务器获取
|
||||||
var resAccessToken ResAccessToken
|
var resAccessToken ResAccessToken
|
||||||
resAccessToken, err = GetTokenFromServerContext(ctx, fmt.Sprintf(accessTokenURL, ak.appID, ak.appSecret))
|
if resAccessToken, err = GetTokenFromServerContext(ctx, fmt.Sprintf(accessTokenURL, ak.appID, ak.appSecret)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(resAccessToken.ExpiresIn-1500)*time.Second); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accessToken = resAccessToken.AccessToken
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StableAccessToken 获取稳定版接口调用凭据(与getAccessToken获取的调用凭证完全隔离,互不影响)
|
||||||
|
// 不强制更新access_token,可用于不同环境不同服务而不需要分布式锁以及公用缓存,避免access_token争抢
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getStableAccessToken.html
|
||||||
|
type StableAccessToken struct {
|
||||||
|
appID string
|
||||||
|
appSecret string
|
||||||
|
cacheKeyPrefix string
|
||||||
|
cache cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStableAccessToken new StableAccessToken
|
||||||
|
func NewStableAccessToken(appID, appSecret, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
|
||||||
|
if cache == nil {
|
||||||
|
panic("cache is need")
|
||||||
|
}
|
||||||
|
return &StableAccessToken{
|
||||||
|
appID: appID,
|
||||||
|
appSecret: appSecret,
|
||||||
|
cache: cache,
|
||||||
|
cacheKeyPrefix: cacheKeyPrefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessToken 获取access_token,先从cache中获取,没有则从服务端获取
|
||||||
|
func (ak *StableAccessToken) GetAccessToken() (accessToken string, err error) {
|
||||||
|
return ak.GetAccessTokenContext(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessTokenContext 获取access_token,先从cache中获取,没有则从服务端获取
|
||||||
|
func (ak *StableAccessToken) GetAccessTokenContext(ctx context.Context) (accessToken string, err error) {
|
||||||
|
// 先从cache中取
|
||||||
|
accessTokenCacheKey := fmt.Sprintf("%s_stable_access_token_%s", ak.cacheKeyPrefix, ak.appID)
|
||||||
|
if val := ak.cache.Get(accessTokenCacheKey); val != nil {
|
||||||
|
return val.(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache失效,从微信服务器获取
|
||||||
|
var resAccessToken ResAccessToken
|
||||||
|
resAccessToken, err = ak.GetAccessTokenDirectly(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expires := resAccessToken.ExpiresIn - 1500
|
expires := resAccessToken.ExpiresIn - 300
|
||||||
err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
|
_ = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
|
||||||
|
|
||||||
|
accessToken = resAccessToken.AccessToken
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessTokenDirectly 从微信获取access_token
|
||||||
|
func (ak *StableAccessToken) GetAccessTokenDirectly(ctx context.Context, forceRefresh bool) (resAccessToken ResAccessToken, err error) {
|
||||||
|
b, err := util.PostJSONContext(ctx, stableAccessTokenURL, map[string]interface{}{
|
||||||
|
"grant_type": "client_credential",
|
||||||
|
"appid": ak.appID,
|
||||||
|
"secret": ak.appSecret,
|
||||||
|
"force_refresh": forceRefresh,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
accessToken = resAccessToken.AccessToken
|
|
||||||
|
if err = json.Unmarshal(b, &resAccessToken); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resAccessToken.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf("get stable access_token error : errcode=%v , errormsg=%v", resAccessToken.ErrCode, resAccessToken.ErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ const (
|
|||||||
updateTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/update?access_token=%s"
|
updateTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/update?access_token=%s"
|
||||||
// deleteTagURL 删除标签
|
// deleteTagURL 删除标签
|
||||||
deleteTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?access_token=%s&tagid=%d"
|
deleteTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?access_token=%s&tagid=%d"
|
||||||
|
// getTagURL 获取标签成员
|
||||||
|
getTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?access_token=%s&tagid=%d"
|
||||||
|
// addTagUsersURL 增加标签成员
|
||||||
|
addTagUsersURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers?access_token=%s"
|
||||||
|
// delTagUsersURL 删除标签成员
|
||||||
|
delTagUsersURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers?access_token=%s"
|
||||||
|
// listTagURL 获取标签列表
|
||||||
|
listTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/list?access_token=%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -90,3 +98,145 @@ func (r *Client) DeleteTag(tagID int) error {
|
|||||||
}
|
}
|
||||||
return util.DecodeWithCommonError(response, "DeleteTag")
|
return util.DecodeWithCommonError(response, "DeleteTag")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// GetTagResponse 获取标签成员响应
|
||||||
|
GetTagResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
TagName string `json:"tagname"`
|
||||||
|
UserList []GetTagUserList `json:"userlist"`
|
||||||
|
PartyList []int `json:"partylist"`
|
||||||
|
}
|
||||||
|
// GetTagUserList 标签中包含的成员列表
|
||||||
|
GetTagUserList struct {
|
||||||
|
UserID string `json:"userid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTag 获取标签成员
|
||||||
|
// @see https://developer.work.weixin.qq.com/document/path/90213
|
||||||
|
func (r *Client) GetTag(tagID int) (*GetTagResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(getTagURL, accessToken, tagID)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &GetTagResponse{}
|
||||||
|
if err = util.DecodeWithError(response, result, "GetTag"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// AddTagUsersRequest 增加标签成员请求
|
||||||
|
AddTagUsersRequest struct {
|
||||||
|
TagID int `json:"tagid"`
|
||||||
|
UserList []string `json:"userlist"`
|
||||||
|
PartyList []int `json:"partylist"`
|
||||||
|
}
|
||||||
|
// AddTagUsersResponse 增加标签成员响应
|
||||||
|
AddTagUsersResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
InvalidList string `json:"invalidlist"`
|
||||||
|
InvalidParty []int `json:"invalidparty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddTagUsers 增加标签成员
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/90214
|
||||||
|
func (r *Client) AddTagUsers(req *AddTagUsersRequest) (*AddTagUsersResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(addTagUsersURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &AddTagUsersResponse{}
|
||||||
|
if err = util.DecodeWithError(response, result, "AddTagUsers"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// DelTagUsersRequest 删除标签成员请求
|
||||||
|
DelTagUsersRequest struct {
|
||||||
|
TagID int `json:"tagid"`
|
||||||
|
UserList []string `json:"userlist"`
|
||||||
|
PartyList []int `json:"partylist"`
|
||||||
|
}
|
||||||
|
// DelTagUsersResponse 删除标签成员响应
|
||||||
|
DelTagUsersResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
InvalidList string `json:"invalidlist"`
|
||||||
|
InvalidParty []int `json:"invalidparty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// DelTagUsers 删除标签成员
|
||||||
|
// see https://developer.work.weixin.qq.com/document/path/90215
|
||||||
|
func (r *Client) DelTagUsers(req *DelTagUsersRequest) (*DelTagUsersResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.PostJSON(fmt.Sprintf(delTagUsersURL, accessToken), req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &DelTagUsersResponse{}
|
||||||
|
if err = util.DecodeWithError(response, result, "DelTagUsers"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ListTagResponse 获取标签列表响应
|
||||||
|
ListTagResponse struct {
|
||||||
|
util.CommonError
|
||||||
|
TagList []Tag `json:"taglist"`
|
||||||
|
}
|
||||||
|
// Tag 标签
|
||||||
|
Tag struct {
|
||||||
|
TagID int `json:"tagid"`
|
||||||
|
TagName string `json:"tagname"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListTag 获取标签列表
|
||||||
|
// @see https://developer.work.weixin.qq.com/document/path/90216
|
||||||
|
func (r *Client) ListTag() (*ListTagResponse, error) {
|
||||||
|
var (
|
||||||
|
accessToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if accessToken, err = r.GetAccessToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []byte
|
||||||
|
if response, err = util.HTTPGet(fmt.Sprintf(listTagURL, accessToken)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &ListTagResponse{}
|
||||||
|
if err = util.DecodeWithError(response, result, "ListTag"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ type callbackOriginMessage struct {
|
|||||||
|
|
||||||
// CallbackMessage 微信客服回调消息
|
// CallbackMessage 微信客服回调消息
|
||||||
type CallbackMessage struct {
|
type CallbackMessage struct {
|
||||||
ToUserName string `json:"to_user_name"` // 微信客服组件ID
|
ToUserName string `json:"to_user_name" xml:"ToUserName"` // 微信客服组件ID
|
||||||
CreateTime int `json:"create_time"` // 消息创建时间,unix时间戳
|
CreateTime int `json:"create_time" xml:"CreateTime"` // 消息创建时间,unix时间戳
|
||||||
MsgType string `json:"msgtype"` // 消息的类型,此时固定为 event
|
MsgType string `json:"msgtype" xml:"MsgType"` // 消息的类型,此时固定为 event
|
||||||
Event string `json:"event"` // 事件的类型,此时固定为 kf_msg_or_event
|
Event string `json:"event" xml:"Event"` // 事件的类型,此时固定为 kf_msg_or_event
|
||||||
Token string `json:"token"` // 调用拉取消息接口时,需要传此token,用于校验请求的合法性
|
Token string `json:"token" xml:"Token"` // 调用拉取消息接口时,需要传此token,用于校验请求的合法性
|
||||||
OpenKfID string `json:"open_kfid"` // 有新消息的客服帐号。可通过sync_msg接口指定open_kfid获取此客服帐号的消息
|
OpenKfID string `json:"open_kfid" xml:"OpenKfId"` // 有新消息的客服帐号。可通过sync_msg接口指定open_kfid获取此客服帐号的消息
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCallbackMessage 获取回调事件中的消息内容
|
// GetCallbackMessage 获取回调事件中的消息内容
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ const (
|
|||||||
|
|
||||||
// SyncMsgOptions 获取消息查询参数
|
// SyncMsgOptions 获取消息查询参数
|
||||||
type SyncMsgOptions struct {
|
type SyncMsgOptions struct {
|
||||||
Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节
|
Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节
|
||||||
Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节
|
Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节
|
||||||
Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。
|
Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。
|
||||||
VoiceFormat uint `json:"voice_format"` // 语音消息类型,0-Amr 1-Silk,默认0。可通过该参数控制返回的语音格式,开发者可按需选择自己程序支持的一种格式
|
VoiceFormat uint `json:"voice_format,omitempty"` // 语音消息类型,0-Amr 1-Silk,默认0。可通过该参数控制返回的语音格式,开发者可按需选择自己程序支持的一种格式
|
||||||
OpenKfID string `json:"open_kfid"` // 指定拉取某个客服帐号的消息,否则默认返回有权限的客服帐号的消息。当客服帐号较多,建议按open_kfid来拉取以获取更好的性能。
|
OpenKfID string `json:"open_kfid,omitempty"` // 指定拉取某个客服帐号的消息,否则默认返回有权限的客服帐号的消息。当客服帐号较多,建议按open_kfid来拉取以获取更好的性能。
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncMsgSchema 获取消息查询响应内容
|
// SyncMsgSchema 获取消息查询响应内容
|
||||||
|
|||||||
Reference in New Issue
Block a user