-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworkwx_cache.go
More file actions
105 lines (89 loc) · 2.87 KB
/
workwx_cache.go
File metadata and controls
105 lines (89 loc) · 2.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package wechat
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"github.com/silenceper/wechat/v2/cache"
"github.com/xen0n/go-workwx/v2"
)
type workwxAccessTokenProvider struct {
corpID string
secret string
tokenUrl string
cache cache.Cache
cacheKey string
httpClient *http.Client
mu sync.Mutex
}
// getTokenResponse 企业微信获取 access_token 响应
type getTokenResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
// NewWorkwxAccessTokenProvider 创建基于 cache.Cache 的 AccessToken 提供者
func NewWorkwxAccessTokenProvider(corpID, secret string, cache cache.Cache) workwx.ITokenProvider {
return &workwxAccessTokenProvider{
corpID: corpID,
secret: secret,
tokenUrl: fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", corpID, secret),
cache: cache,
cacheKey: "workwx:access_token:" + secret,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// GetToken 获取 access_token
// 优先从缓存获取,如果缓存中没有,则从企业微信 API 获取
// 使用锁避免并发重复请求企业微信 API
func (p *workwxAccessTokenProvider) GetToken(_ context.Context) (string, error) {
// 先从缓存获取(无锁,快速路径)
rt := p.cache.Get(p.cacheKey)
if val, ok := rt.(string); ok && val != "" {
return val, nil
}
// 缓存中没有,加锁获取
p.mu.Lock()
defer p.mu.Unlock()
// 双重检查:可能在等待锁时,其他 goroutine 已经获取并缓存了 token
rt = p.cache.Get(p.cacheKey)
if val, ok := rt.(string); ok && val != "" {
return val, nil
}
// 从企业微信 API 获取
token, expiresIn, err := p.fetchAccessToken()
if err != nil {
return "", fmt.Errorf("获取 access_token 失败: %w", err)
}
// 将 token 存入缓存,提前5分钟过期以避免临界问题
expiration := time.Duration(expiresIn-300) * time.Second
if expiration < time.Minute {
expiration = time.Minute // 至少缓存1分钟
}
if err := p.cache.Set(p.cacheKey, token, expiration); err != nil {
// 设置缓存失败不影响返回 token
return token, nil
}
return token, nil
}
// fetchAccessToken 从企业微信 API 获取 access_token
func (p *workwxAccessTokenProvider) fetchAccessToken() (string, int, error) {
resp, err := p.httpClient.Get(p.tokenUrl)
if err != nil {
return "", 0, fmt.Errorf("请求企业微信 API 失败: %w", err)
}
defer resp.Body.Close()
var result getTokenResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", 0, fmt.Errorf("解析响应失败: %w", err)
}
if result.ErrCode != 0 {
return "", 0, fmt.Errorf("企业微信 API 返回错误: %d - %s", result.ErrCode, result.ErrMsg)
}
return result.AccessToken, result.ExpiresIn, nil
}