mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-11 16:22:26 +08:00
Compare commits
5 Commits
fdacb5f67e
...
e6d960c22f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6d960c22f | ||
|
|
71c8ab58fb | ||
|
|
92bf6c7699 | ||
|
|
6b9d4f82da | ||
|
|
17521d047e |
@@ -7,6 +7,16 @@ type AccessTokenHandle interface {
|
||||
GetAccessToken() (accessToken string, err error)
|
||||
}
|
||||
|
||||
// AccessTokenCompatibleHandle 同时实现 AccessTokenHandle 和 AccessTokenContextHandle
|
||||
type AccessTokenCompatibleHandle struct {
|
||||
AccessTokenHandle
|
||||
}
|
||||
|
||||
// GetAccessTokenContext 获取access_token,先从cache中获取,没有则从服务端获取
|
||||
func (c AccessTokenCompatibleHandle) GetAccessTokenContext(_ context.Context) (accessToken string, err error) {
|
||||
return c.GetAccessToken()
|
||||
}
|
||||
|
||||
// AccessTokenContextHandle AccessToken 接口
|
||||
type AccessTokenContextHandle interface {
|
||||
AccessTokenHandle
|
||||
|
||||
@@ -189,19 +189,27 @@ func (ak *StableAccessToken) GetAccessTokenDirectly(ctx context.Context, forceRe
|
||||
type WorkAccessToken struct {
|
||||
CorpID string
|
||||
CorpSecret string
|
||||
AgentID string // 可选,用于区分不同应用
|
||||
cacheKeyPrefix string
|
||||
cache cache.Cache
|
||||
accessTokenLock *sync.Mutex
|
||||
}
|
||||
|
||||
// NewWorkAccessToken new WorkAccessToken
|
||||
func NewWorkAccessToken(corpID, corpSecret, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
|
||||
// NewWorkAccessToken new WorkAccessToken (保持向后兼容)
|
||||
func NewWorkAccessToken(corpID, corpSecret, agentID, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
|
||||
// 调用新方法,保持兼容性
|
||||
return NewWorkAccessTokenWithAgentID(corpID, corpSecret, agentID, cacheKeyPrefix, cache)
|
||||
}
|
||||
|
||||
// NewWorkAccessTokenWithAgentID new WorkAccessToken with agentID
|
||||
func NewWorkAccessTokenWithAgentID(corpID, corpSecret, agentID, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
|
||||
if cache == nil {
|
||||
panic("cache the not exist")
|
||||
panic("cache is needed")
|
||||
}
|
||||
return &WorkAccessToken{
|
||||
CorpID: corpID,
|
||||
CorpSecret: corpSecret,
|
||||
AgentID: agentID,
|
||||
cache: cache,
|
||||
cacheKeyPrefix: cacheKeyPrefix,
|
||||
accessTokenLock: new(sync.Mutex),
|
||||
@@ -218,7 +226,17 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok
|
||||
// 加上lock,是为了防止在并发获取token时,cache刚好失效,导致从微信服务器上获取到不同token
|
||||
ak.accessTokenLock.Lock()
|
||||
defer ak.accessTokenLock.Unlock()
|
||||
accessTokenCacheKey := fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.CorpID)
|
||||
|
||||
// 构建缓存key
|
||||
var accessTokenCacheKey string
|
||||
if ak.AgentID != "" {
|
||||
// 如果设置了AgentID,使用新的key格式
|
||||
accessTokenCacheKey = fmt.Sprintf("%s_access_token_%s_%s", ak.cacheKeyPrefix, ak.CorpID, ak.AgentID)
|
||||
} else {
|
||||
// 兼容历史版本的key格式
|
||||
accessTokenCacheKey = fmt.Sprintf("%s_access_token_%s", ak.cacheKeyPrefix, ak.CorpID)
|
||||
}
|
||||
|
||||
val := ak.cache.Get(accessTokenCacheKey)
|
||||
if val != nil {
|
||||
accessToken = val.(string)
|
||||
@@ -234,6 +252,9 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok
|
||||
|
||||
expires := resAccessToken.ExpiresIn - 1500
|
||||
err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
accessToken = resAccessToken.AccessToken
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
@@ -28,13 +29,18 @@ type PhoneInfo struct {
|
||||
|
||||
// GetPhoneNumber code换取用户手机号。 每个code只能使用一次,code的有效期为5min
|
||||
func (business *Business) GetPhoneNumber(in *GetPhoneNumberRequest) (info PhoneInfo, err error) {
|
||||
accessToken, err := business.GetAccessToken()
|
||||
return business.GetPhoneNumberWithContext(context.Background(), in)
|
||||
}
|
||||
|
||||
// GetPhoneNumberWithContext 利用context将code换取用户手机号。 每个code只能使用一次,code的有效期为5min
|
||||
func (business *Business) GetPhoneNumberWithContext(ctx context.Context, in *GetPhoneNumberRequest) (info PhoneInfo, err error) {
|
||||
accessToken, err := business.GetAccessTokenContext(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(getPhoneNumberURL, accessToken)
|
||||
response, err := util.PostJSON(uri, in)
|
||||
response, err := util.PostJSONContext(ctx, uri, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ import (
|
||||
// Context struct
|
||||
type Context struct {
|
||||
*config.Config
|
||||
credential.AccessTokenHandle
|
||||
credential.AccessTokenContextHandle
|
||||
}
|
||||
|
||||
@@ -42,15 +42,22 @@ func NewMiniProgram(cfg *config.Config) *MiniProgram {
|
||||
defaultAkHandle = credential.NewDefaultAccessToken(cfg.AppID, cfg.AppSecret, cacheKeyPrefix, cfg.Cache)
|
||||
}
|
||||
ctx := &context.Context{
|
||||
Config: cfg,
|
||||
AccessTokenHandle: defaultAkHandle,
|
||||
Config: cfg,
|
||||
AccessTokenContextHandle: defaultAkHandle,
|
||||
}
|
||||
return &MiniProgram{ctx}
|
||||
}
|
||||
|
||||
// SetAccessTokenHandle 自定义 access_token 获取方式
|
||||
func (miniProgram *MiniProgram) SetAccessTokenHandle(accessTokenHandle credential.AccessTokenHandle) {
|
||||
miniProgram.ctx.AccessTokenHandle = accessTokenHandle
|
||||
miniProgram.ctx.AccessTokenContextHandle = credential.AccessTokenCompatibleHandle{
|
||||
AccessTokenHandle: accessTokenHandle,
|
||||
}
|
||||
}
|
||||
|
||||
// SetAccessTokenContextHandle 自定义 access_token 获取方式
|
||||
func (miniProgram *MiniProgram) SetAccessTokenContextHandle(accessTokenContextHandle credential.AccessTokenContextHandle) {
|
||||
miniProgram.ctx.AccessTokenContextHandle = accessTokenContextHandle
|
||||
}
|
||||
|
||||
// GetContext get Context
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package miniprogram
|
||||
|
||||
import (
|
||||
originalContext "context"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/credential"
|
||||
@@ -37,6 +38,22 @@ func (miniProgram *MiniProgram) GetAccessToken() (string, error) {
|
||||
return akRes.AccessToken, nil
|
||||
}
|
||||
|
||||
// GetAccessTokenContext 利用ctx获取ak
|
||||
func (miniProgram *MiniProgram) GetAccessTokenContext(ctx originalContext.Context) (string, error) {
|
||||
ak, akErr := miniProgram.openContext.GetAuthrAccessTokenContext(ctx, miniProgram.AppID)
|
||||
if akErr == nil {
|
||||
return ak, nil
|
||||
}
|
||||
if miniProgram.authorizerRefreshToken == "" {
|
||||
return "", fmt.Errorf("please set the authorizer_refresh_token first")
|
||||
}
|
||||
akRes, akResErr := miniProgram.GetComponent().RefreshAuthrTokenContext(ctx, miniProgram.AppID, miniProgram.authorizerRefreshToken)
|
||||
if akResErr != nil {
|
||||
return "", akResErr
|
||||
}
|
||||
return akRes.AccessToken, nil
|
||||
}
|
||||
|
||||
// SetAuthorizerRefreshToken 设置代执操作业务授权账号authorizer_refresh_token
|
||||
func (miniProgram *MiniProgram) SetAuthorizerRefreshToken(authorizerRefreshToken string) *MiniProgram {
|
||||
miniProgram.authorizerRefreshToken = authorizerRefreshToken
|
||||
@@ -68,7 +85,7 @@ func (miniProgram *MiniProgram) GetBasic() *basic.Basic {
|
||||
// GetURLLink 小程序URL Link接口 调用前需确认已调用 SetAuthorizerRefreshToken 避免由于缓存中 authorizer_access_token 过期执行中断
|
||||
func (miniProgram *MiniProgram) GetURLLink() *urllink.URLLink {
|
||||
return urllink.NewURLLink(&miniContext.Context{
|
||||
AccessTokenHandle: miniProgram,
|
||||
AccessTokenContextHandle: miniProgram,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,11 @@ import (
|
||||
|
||||
// Config for 企业微信
|
||||
type Config struct {
|
||||
CorpID string `json:"corp_id"` // corp_id
|
||||
CorpSecret string `json:"corp_secret"` // corp_secret,如果需要获取会话存档实例,当前参数请填写聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看
|
||||
AgentID string `json:"agent_id"` // agent_id
|
||||
Cache cache.Cache
|
||||
RasPrivateKey string // 消息加密私钥,可以在企业微信管理端--管理工具--消息加密公钥查看对用公钥,私钥一般由自己保存
|
||||
|
||||
CorpID string `json:"corp_id"` // corp_id
|
||||
CorpSecret string `json:"corp_secret"` // corp_secret,如果需要获取会话存档实例,当前参数请填写聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看
|
||||
AgentID string `json:"agent_id"` // agent_id
|
||||
Cache cache.Cache
|
||||
RasPrivateKey string // 消息加密私钥,可以在企业微信管理端--管理工具--消息加密公钥查看对用公钥,私钥一般由自己保存
|
||||
Token string `json:"token"` // 微信客服回调配置,用于生成签名校验回调请求的合法性
|
||||
EncodingAESKey string `json:"encoding_aes_key"` // 微信客服回调p配置,用于解密回调消息内容对应的密文
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type AccountAddSchema struct {
|
||||
}
|
||||
|
||||
// AccountAdd 添加客服账号
|
||||
// see https://developer.work.weixin.qq.com/document/path/94662
|
||||
func (r *Client) AccountAdd(options AccountAddOptions) (info AccountAddSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
@@ -59,6 +60,7 @@ type AccountDelOptions struct {
|
||||
}
|
||||
|
||||
// AccountDel 删除客服账号
|
||||
// see https://developer.work.weixin.qq.com/document/path/94663
|
||||
func (r *Client) AccountDel(options AccountDelOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
@@ -86,7 +88,8 @@ type AccountUpdateOptions struct {
|
||||
MediaID string `json:"media_id"` // 客服头像临时素材。可以调用上传临时素材接口获取, 不多于128个字节
|
||||
}
|
||||
|
||||
// AccountUpdate 修复客服账号
|
||||
// AccountUpdate 修改客服账号
|
||||
// see https://developer.work.weixin.qq.com/document/path/94664
|
||||
func (r *Client) AccountUpdate(options AccountUpdateOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
@@ -109,9 +112,10 @@ func (r *Client) AccountUpdate(options AccountUpdateOptions) (info util.CommonEr
|
||||
|
||||
// AccountInfoSchema 客服详情
|
||||
type AccountInfoSchema struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
Name string `json:"name"` // 客服帐号名称
|
||||
Avatar string `json:"avatar"` // 客服头像URL
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
Name string `json:"name"` // 客服帐号名称
|
||||
Avatar string `json:"avatar"` // 客服头像URL
|
||||
ManagePrivilege bool `json:"manage_privilege"` // 当前调用接口的应用身份,是否有该客服账号的管理权限(编辑客服账号信息、分配会话和收发消息)
|
||||
}
|
||||
|
||||
// AccountListSchema 获取客服账号列表响应内容
|
||||
@@ -141,6 +145,31 @@ func (r *Client) AccountList() (info AccountListSchema, err error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// AccountPagingRequest 分页获取客服账号列表请求
|
||||
type AccountPagingRequest struct {
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
// AccountPaging 分页获取客服账号列表
|
||||
// see https://developer.work.weixin.qq.com/document/path/94661
|
||||
func (r *Client) AccountPaging(req *AccountPagingRequest) (*AccountListSchema, error) {
|
||||
var (
|
||||
accessToken string
|
||||
err error
|
||||
)
|
||||
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
if response, err = util.PostJSON(fmt.Sprintf(accountListAddr, accessToken), req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &AccountListSchema{}
|
||||
err = util.DecodeWithError(response, result, "AccountPaging")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// AddContactWayOptions 获取客服账号链接
|
||||
// 1.若scene非空,返回的客服链接开发者可拼接scene_param=SCENE_PARAM参数使用,用户进入会话事件会将SCENE_PARAM原样返回。其中SCENE_PARAM需要urlencode,且长度不能超过128字节。
|
||||
// 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2
|
||||
@@ -158,6 +187,7 @@ type AddContactWaySchema struct {
|
||||
}
|
||||
|
||||
// AddContactWay 获取客服账号链接
|
||||
// see https://developer.work.weixin.qq.com/document/path/94665
|
||||
func (r *Client) AddContactWay(options AddContactWayOptions) (info AddContactWaySchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
|
||||
@@ -24,7 +24,7 @@ func NewClient(cfg *config.Config) (client *Client, err error) {
|
||||
}
|
||||
|
||||
// 初始化 AccessToken Handle
|
||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, cfg.AgentID, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||
ctx := &context.Context{
|
||||
Config: cfg,
|
||||
AccessTokenHandle: defaultAkHandle,
|
||||
|
||||
@@ -27,7 +27,7 @@ type Work struct {
|
||||
|
||||
// NewWork init work
|
||||
func NewWork(cfg *config.Config) *Work {
|
||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, cfg.AgentID, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||
ctx := &context.Context{
|
||||
Config: cfg,
|
||||
AccessTokenHandle: defaultAkHandle,
|
||||
|
||||
Reference in New Issue
Block a user