Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:企业微信客户端API JS-SDK wx.config 和 wx.agentConfig 方法权限签名 #817

Merged
merged 7 commits into from
Jan 14, 2025
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
Loading