mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-23 13:42:25 +08:00
Compare commits
4 Commits
v2.1.8
...
3c54064570
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c54064570 | ||
|
|
4620819c82 | ||
|
|
7a601f773e | ||
|
|
d01c858921 |
@@ -189,27 +189,19 @@ func (ak *StableAccessToken) GetAccessTokenDirectly(ctx context.Context, forceRe
|
|||||||
type WorkAccessToken struct {
|
type WorkAccessToken struct {
|
||||||
CorpID string
|
CorpID string
|
||||||
CorpSecret string
|
CorpSecret string
|
||||||
AgentID string // 可选,用于区分不同应用
|
|
||||||
cacheKeyPrefix string
|
cacheKeyPrefix string
|
||||||
cache cache.Cache
|
cache cache.Cache
|
||||||
accessTokenLock *sync.Mutex
|
accessTokenLock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWorkAccessToken new WorkAccessToken (保持向后兼容)
|
// NewWorkAccessToken new WorkAccessToken
|
||||||
func NewWorkAccessToken(corpID, corpSecret, agentID, cacheKeyPrefix string, cache cache.Cache) AccessTokenContextHandle {
|
func NewWorkAccessToken(corpID, corpSecret, 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 {
|
if cache == nil {
|
||||||
panic("cache is needed")
|
panic("cache the not exist")
|
||||||
}
|
}
|
||||||
return &WorkAccessToken{
|
return &WorkAccessToken{
|
||||||
CorpID: corpID,
|
CorpID: corpID,
|
||||||
CorpSecret: corpSecret,
|
CorpSecret: corpSecret,
|
||||||
AgentID: agentID,
|
|
||||||
cache: cache,
|
cache: cache,
|
||||||
cacheKeyPrefix: cacheKeyPrefix,
|
cacheKeyPrefix: cacheKeyPrefix,
|
||||||
accessTokenLock: new(sync.Mutex),
|
accessTokenLock: new(sync.Mutex),
|
||||||
@@ -226,18 +218,7 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok
|
|||||||
// 加上lock,是为了防止在并发获取token时,cache刚好失效,导致从微信服务器上获取到不同token
|
// 加上lock,是为了防止在并发获取token时,cache刚好失效,导致从微信服务器上获取到不同token
|
||||||
ak.accessTokenLock.Lock()
|
ak.accessTokenLock.Lock()
|
||||||
defer ak.accessTokenLock.Unlock()
|
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)
|
val := ak.cache.Get(accessTokenCacheKey)
|
||||||
if val != nil {
|
if val != nil {
|
||||||
accessToken = val.(string)
|
accessToken = val.(string)
|
||||||
@@ -253,9 +234,6 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok
|
|||||||
|
|
||||||
expires := resAccessToken.ExpiresIn - 1500
|
expires := resAccessToken.ExpiresIn - 1500
|
||||||
err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
|
err = ak.cache.Set(accessTokenCacheKey, resAccessToken.AccessToken, time.Duration(expires)*time.Second)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken = resAccessToken.AccessToken
|
accessToken = resAccessToken.AccessToken
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
package credential
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/cache"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TicketType ticket类型
|
|
||||||
type TicketType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TicketTypeCorpJs 企业jsapi ticket
|
|
||||||
TicketTypeCorpJs TicketType = iota
|
|
||||||
// TicketTypeAgentJs 应用jsapi ticket
|
|
||||||
TicketTypeAgentJs
|
|
||||||
)
|
|
||||||
|
|
||||||
// 企业微信相关的 ticket URL
|
|
||||||
const (
|
|
||||||
// 企业微信 jsapi ticket
|
|
||||||
getWorkJsTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=%s"
|
|
||||||
// 企业微信应用 jsapi ticket
|
|
||||||
getWorkAgentJsTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=%s&type=agent_config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WorkJsTicket 企业微信js ticket获取
|
|
||||||
type WorkJsTicket struct {
|
|
||||||
corpID string
|
|
||||||
agentID string
|
|
||||||
cacheKeyPrefix string
|
|
||||||
cache cache.Cache
|
|
||||||
jsAPITicketLock *sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWorkJsTicket new WorkJsTicket
|
|
||||||
func NewWorkJsTicket(corpID, agentID, cacheKeyPrefix string, cache cache.Cache) *WorkJsTicket {
|
|
||||||
return &WorkJsTicket{
|
|
||||||
corpID: corpID,
|
|
||||||
agentID: agentID,
|
|
||||||
cache: cache,
|
|
||||||
cacheKeyPrefix: cacheKeyPrefix,
|
|
||||||
jsAPITicketLock: new(sync.Mutex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTicket 根据类型获取相应的jsapi_ticket
|
|
||||||
func (js *WorkJsTicket) GetTicket(accessToken string, ticketType TicketType) (ticketStr string, err error) {
|
|
||||||
var cacheKey string
|
|
||||||
switch ticketType {
|
|
||||||
case TicketTypeCorpJs:
|
|
||||||
cacheKey = fmt.Sprintf("%s_corp_jsapi_ticket_%s", js.cacheKeyPrefix, js.corpID)
|
|
||||||
case TicketTypeAgentJs:
|
|
||||||
if js.agentID == "" {
|
|
||||||
err = fmt.Errorf("agentID is empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cacheKey = fmt.Sprintf("%s_agent_jsapi_ticket_%s_%s", js.cacheKeyPrefix, js.corpID, js.agentID)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unsupported ticket type: %v", ticketType)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := js.cache.Get(cacheKey); val != nil {
|
|
||||||
return val.(string), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
js.jsAPITicketLock.Lock()
|
|
||||||
defer js.jsAPITicketLock.Unlock()
|
|
||||||
|
|
||||||
// 双检,防止重复从微信服务器获取
|
|
||||||
if val := js.cache.Get(cacheKey); val != nil {
|
|
||||||
return val.(string), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ticket ResTicket
|
|
||||||
ticket, err = js.getTicketFromServer(accessToken, ticketType)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expires := ticket.ExpiresIn - 1500
|
|
||||||
err = js.cache.Set(cacheKey, ticket.Ticket, time.Duration(expires)*time.Second)
|
|
||||||
ticketStr = ticket.Ticket
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTicketFromServer 从服务器中获取ticket
|
|
||||||
func (js *WorkJsTicket) getTicketFromServer(accessToken string, ticketType TicketType) (ticket ResTicket, err error) {
|
|
||||||
var url string
|
|
||||||
switch ticketType {
|
|
||||||
case TicketTypeCorpJs:
|
|
||||||
url = fmt.Sprintf(getWorkJsTicketURL, accessToken)
|
|
||||||
case TicketTypeAgentJs:
|
|
||||||
url = fmt.Sprintf(getWorkAgentJsTicketURL, accessToken)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unsupported ticket type: %v", ticketType)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var response []byte
|
|
||||||
response, err = util.HTTPGet(url)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(response, &ticket)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ticket.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("getTicket Error : errcode=%d , errmsg=%s", ticket.ErrCode, ticket.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -39,6 +39,7 @@ func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
|
|||||||
// GetConfig 获取jssdk需要的配置参数
|
// GetConfig 获取jssdk需要的配置参数
|
||||||
// uri 为当前网页地址
|
// uri 为当前网页地址
|
||||||
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
||||||
|
config = new(Config)
|
||||||
var accessToken string
|
var accessToken string
|
||||||
accessToken, err = js.GetAccessToken()
|
accessToken, err = js.GetAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,11 +50,12 @@ func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nonceStr := util.RandomStr(16)
|
nonceStr := util.RandomStr(16)
|
||||||
timestamp := util.GetCurrTS()
|
timestamp := util.GetCurrTS()
|
||||||
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
|
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, nonceStr, timestamp, uri)
|
||||||
sigStr := util.Signature(str)
|
sigStr := util.Signature(str)
|
||||||
config = new(Config)
|
|
||||||
config.AppID = js.AppID
|
config.AppID = js.AppID
|
||||||
config.NonceStr = nonceStr
|
config.NonceStr = nonceStr
|
||||||
config.Timestamp = timestamp
|
config.Timestamp = timestamp
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import (
|
|||||||
|
|
||||||
// Config for 企业微信
|
// Config for 企业微信
|
||||||
type Config struct {
|
type Config struct {
|
||||||
CorpID string `json:"corp_id"` // corp_id
|
CorpID string `json:"corp_id"` // corp_id
|
||||||
CorpSecret string `json:"corp_secret"` // corp_secret,如果需要获取会话存档实例,当前参数请填写聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看
|
CorpSecret string `json:"corp_secret"` // corp_secret,如果需要获取会话存档实例,当前参数请填写聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看
|
||||||
AgentID string `json:"agent_id"` // agent_id
|
AgentID string `json:"agent_id"` // agent_id
|
||||||
Cache cache.Cache
|
Cache cache.Cache
|
||||||
RasPrivateKey string // 消息加密私钥,可以在企业微信管理端--管理工具--消息加密公钥查看对用公钥,私钥一般由自己保存
|
RasPrivateKey string // 消息加密私钥,可以在企业微信管理端--管理工具--消息加密公钥查看对用公钥,私钥一般由自己保存
|
||||||
|
|
||||||
Token string `json:"token"` // 微信客服回调配置,用于生成签名校验回调请求的合法性
|
Token string `json:"token"` // 微信客服回调配置,用于生成签名校验回调请求的合法性
|
||||||
EncodingAESKey string `json:"encoding_aes_key"` // 微信客服回调p配置,用于解密回调消息内容对应的密文
|
EncodingAESKey string `json:"encoding_aes_key"` // 微信客服回调p配置,用于解密回调消息内容对应的密文
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package jsapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/credential"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
"github.com/silenceper/wechat/v2/work/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Js struct
|
|
||||||
type Js struct {
|
|
||||||
*context.Context
|
|
||||||
jsTicket *credential.WorkJsTicket
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJs init
|
|
||||||
func NewJs(context *context.Context) *Js {
|
|
||||||
js := new(Js)
|
|
||||||
js.Context = context
|
|
||||||
js.jsTicket = credential.NewWorkJsTicket(
|
|
||||||
context.Config.CorpID,
|
|
||||||
context.Config.AgentID,
|
|
||||||
credential.CacheKeyWorkPrefix,
|
|
||||||
context.Cache,
|
|
||||||
)
|
|
||||||
return js
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config 返回给用户使用的配置
|
|
||||||
type Config struct {
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
NonceStr string `json:"nonce_str"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfig 获取企业微信JS配置 https://developer.work.weixin.qq.com/document/path/90514
|
|
||||||
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
|
||||||
config = new(Config)
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = js.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var ticketStr string
|
|
||||||
ticketStr, err = js.jsTicket.GetTicket(accessToken, credential.TicketTypeCorpJs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
config.NonceStr = util.RandomStr(16)
|
|
||||||
config.Timestamp = util.GetCurrTS()
|
|
||||||
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, config.NonceStr, config.Timestamp, uri)
|
|
||||||
config.Signature = util.Signature(str)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAgentConfig 获取企业微信应用JS配置 https://developer.work.weixin.qq.com/document/path/94313
|
|
||||||
func (js *Js) GetAgentConfig(uri string) (config *Config, err error) {
|
|
||||||
config = new(Config)
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = js.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var ticketStr string
|
|
||||||
ticketStr, err = js.jsTicket.GetTicket(accessToken, credential.TicketTypeAgentJs)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
config.NonceStr = util.RandomStr(16)
|
|
||||||
config.Timestamp = util.GetCurrTS()
|
|
||||||
str := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticketStr, config.NonceStr, config.Timestamp, uri)
|
|
||||||
config.Signature = util.Signature(str)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ func NewClient(cfg *config.Config) (client *Client, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 初始化 AccessToken Handle
|
// 初始化 AccessToken Handle
|
||||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, cfg.AgentID, credential.CacheKeyWorkPrefix, cfg.Cache)
|
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||||
ctx := &context.Context{
|
ctx := &context.Context{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
AccessTokenHandle: defaultAkHandle,
|
AccessTokenHandle: defaultAkHandle,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/work/context"
|
"github.com/silenceper/wechat/v2/work/context"
|
||||||
"github.com/silenceper/wechat/v2/work/externalcontact"
|
"github.com/silenceper/wechat/v2/work/externalcontact"
|
||||||
"github.com/silenceper/wechat/v2/work/invoice"
|
"github.com/silenceper/wechat/v2/work/invoice"
|
||||||
"github.com/silenceper/wechat/v2/work/jsapi"
|
|
||||||
"github.com/silenceper/wechat/v2/work/kf"
|
"github.com/silenceper/wechat/v2/work/kf"
|
||||||
"github.com/silenceper/wechat/v2/work/material"
|
"github.com/silenceper/wechat/v2/work/material"
|
||||||
"github.com/silenceper/wechat/v2/work/message"
|
"github.com/silenceper/wechat/v2/work/message"
|
||||||
@@ -25,7 +24,7 @@ type Work struct {
|
|||||||
|
|
||||||
// NewWork init work
|
// NewWork init work
|
||||||
func NewWork(cfg *config.Config) *Work {
|
func NewWork(cfg *config.Config) *Work {
|
||||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, cfg.AgentID, credential.CacheKeyWorkPrefix, cfg.Cache)
|
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||||
ctx := &context.Context{
|
ctx := &context.Context{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
AccessTokenHandle: defaultAkHandle,
|
AccessTokenHandle: defaultAkHandle,
|
||||||
@@ -53,11 +52,6 @@ func (wk *Work) GetKF() (*kf.Client, error) {
|
|||||||
return kf.NewClient(wk.ctx.Config)
|
return kf.NewClient(wk.ctx.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JsSdk get JsSdk
|
|
||||||
func (wk *Work) JsSdk() *jsapi.Js {
|
|
||||||
return jsapi.NewJs(wk.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExternalContact get external_contact
|
// GetExternalContact get external_contact
|
||||||
func (wk *Work) GetExternalContact() *externalcontact.Client {
|
func (wk *Work) GetExternalContact() *externalcontact.Client {
|
||||||
return externalcontact.NewClient(wk.ctx)
|
return externalcontact.NewClient(wk.ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user