Skip to content

Commit

Permalink
feat:企业微信客户端API JS-SDK wx.config 和 wx.agentConfig 方法权限签名 (#817)
Browse files Browse the repository at this point in the history
* feat: enhance WorkAccessToken to include AgentID for improved token management

- Added AgentID field to WorkAccessToken struct.
- Updated NewWorkAccessToken function to accept AgentID as a parameter.
- Modified access token cache key to incorporate AgentID, ensuring unique cache entries per agent.

This change improves the handling of access tokens in a multi-agent environment.

* refactor: enhance WorkAccessToken to improve cache key handling

- Updated the AgentID field in WorkAccessToken struct to clarify its optional nature for distinguishing applications.
- Modified the access token cache key construction to support both new and legacy formats based on the presence of AgentID.
- Added comments for better understanding of the cache key logic and its compatibility with historical versions.

This change improves the flexibility and clarity of access token management in multi-agent scenarios.

* feat(work): add JsSdk method for JavaScript SDK integration

- Introduced a new JsSdk method in the Work struct to facilitate the creation of a Js instance.
- This addition enhances the functionality of the Work module by enabling JavaScript SDK support.

This change improves the integration capabilities for developers working with the WeChat Work API.

* fix gofmt
  • Loading branch information
mahongran authored Jan 14, 2025
1 parent 71c8ab5 commit 9c87d1c
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions credential/default_access_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ func (ak *WorkAccessToken) GetAccessTokenContext(ctx context.Context) (accessTok

// 构建缓存key
var accessTokenCacheKey string

if ak.AgentID != "" {
// 如果设置了AgentID,使用新的key格式
accessTokenCacheKey = fmt.Sprintf("%s_access_token_%s_%s", ak.cacheKeyPrefix, ak.CorpID, ak.AgentID)
Expand Down
118 changes: 118 additions & 0 deletions credential/work_js_ticket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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
}
73 changes: 73 additions & 0 deletions work/jsapi/jsapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package jsapi

import (
"strconv"

"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()
config.Signature = util.Signature(ticketStr, config.NonceStr, strconv.FormatInt(config.Timestamp, 10), uri)
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()
config.Signature = util.Signature(ticketStr, config.NonceStr, strconv.FormatInt(config.Timestamp, 10), uri)
return
}
6 changes: 6 additions & 0 deletions work/work.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/silenceper/wechat/v2/work/context"
"github.com/silenceper/wechat/v2/work/externalcontact"
"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/material"
"github.com/silenceper/wechat/v2/work/message"
Expand Down Expand Up @@ -52,6 +53,11 @@ func (wk *Work) GetKF() (*kf.Client, error) {
return kf.NewClient(wk.ctx.Config)
}

// JsSdk get JsSdk
func (wk *Work) JsSdk() *jsapi.Js {
return jsapi.NewJs(wk.ctx)
}

// GetExternalContact get external_contact
func (wk *Work) GetExternalContact() *externalcontact.Client {
return externalcontact.NewClient(wk.ctx)
Expand Down

0 comments on commit 9c87d1c

Please sign in to comment.