feishu-mcp 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ import * as crypto from 'crypto';
2
+ import { Config } from '../config.js';
3
+ /**
4
+ * 认证工具类
5
+ * 提供认证相关的加密和哈希工具方法
6
+ */
7
+ export class AuthUtils {
8
+ /**
9
+ * 生成客户端缓存键
10
+ * @param userKey 用户标识(可选)
11
+ * @returns 生成的客户端键
12
+ */
13
+ static generateClientKey(userKey) {
14
+ const feishuConfig = Config.getInstance().feishu;
15
+ const userPart = userKey ? `:${userKey}` : '';
16
+ let source = '';
17
+ if (feishuConfig.authType === "tenant") {
18
+ source = `${feishuConfig.appId}:${feishuConfig.appSecret}`;
19
+ }
20
+ else {
21
+ source = `${feishuConfig.appId}:${feishuConfig.appSecret}${userPart}`;
22
+ }
23
+ return crypto.createHash('sha256').update(source).digest('hex');
24
+ }
25
+ /**
26
+ * 生成时间戳
27
+ * @returns 当前时间戳(秒)
28
+ */
29
+ static timestamp() {
30
+ return Math.floor(Date.now() / 1000);
31
+ }
32
+ /**
33
+ * 生成时间戳(毫秒)
34
+ * @returns 当前时间戳(毫秒)
35
+ */
36
+ static timestampMs() {
37
+ return Date.now();
38
+ }
39
+ /**
40
+ * 编码state参数
41
+ * @param appId 应用ID
42
+ * @param appSecret 应用密钥
43
+ * @param clientKey 客户端缓存键
44
+ * @param redirectUri 重定向URI(可选)
45
+ * @returns Base64编码的state字符串
46
+ */
47
+ static encodeState(appId, appSecret, clientKey, redirectUri) {
48
+ const stateData = {
49
+ appId,
50
+ appSecret,
51
+ clientKey,
52
+ redirectUri,
53
+ timestamp: this.timestamp()
54
+ };
55
+ return Buffer.from(JSON.stringify(stateData)).toString('base64');
56
+ }
57
+ /**
58
+ * 解码state参数
59
+ * @param encodedState Base64编码的state字符串
60
+ * @returns 解码后的state数据
61
+ */
62
+ static decodeState(encodedState) {
63
+ try {
64
+ const decoded = Buffer.from(encodedState, 'base64').toString('utf-8');
65
+ return JSON.parse(decoded);
66
+ }
67
+ catch (error) {
68
+ return null;
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,4 @@
1
+ export { UserContextManager, getBaseUrl } from './userContextManager.js';
2
+ export { UserAuthManager } from './userAuthManager.js';
3
+ export { TokenCacheManager } from './tokenCacheManager.js';
4
+ export { AuthUtils } from './authUtils.js';
@@ -0,0 +1,420 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Logger } from '../logger.js';
4
+ /**
5
+ * Token缓存管理器
6
+ * 专门处理用户token和租户token的缓存管理
7
+ */
8
+ export class TokenCacheManager {
9
+ /**
10
+ * 私有构造函数,用于单例模式
11
+ */
12
+ constructor() {
13
+ Object.defineProperty(this, "cache", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: void 0
18
+ });
19
+ Object.defineProperty(this, "userTokenCacheFile", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: void 0
24
+ });
25
+ Object.defineProperty(this, "tenantTokenCacheFile", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: void 0
30
+ });
31
+ this.cache = new Map();
32
+ this.userTokenCacheFile = path.resolve(process.cwd(), 'user_token_cache.json');
33
+ this.tenantTokenCacheFile = path.resolve(process.cwd(), 'tenant_token_cache.json');
34
+ this.loadTokenCaches();
35
+ this.startCacheCleanupTimer();
36
+ }
37
+ /**
38
+ * 获取TokenCacheManager实例
39
+ */
40
+ static getInstance() {
41
+ if (!TokenCacheManager.instance) {
42
+ TokenCacheManager.instance = new TokenCacheManager();
43
+ }
44
+ return TokenCacheManager.instance;
45
+ }
46
+ /**
47
+ * 系统启动时从本地文件缓存中读取token记录
48
+ */
49
+ loadTokenCaches() {
50
+ this.loadUserTokenCache();
51
+ this.loadTenantTokenCache();
52
+ }
53
+ /**
54
+ * 加载用户token缓存
55
+ */
56
+ loadUserTokenCache() {
57
+ if (fs.existsSync(this.userTokenCacheFile)) {
58
+ try {
59
+ const raw = fs.readFileSync(this.userTokenCacheFile, 'utf-8');
60
+ const cacheData = JSON.parse(raw);
61
+ let loadedCount = 0;
62
+ for (const key in cacheData) {
63
+ if (key.startsWith('user_access_token:')) {
64
+ this.cache.set(key, cacheData[key]);
65
+ loadedCount++;
66
+ }
67
+ }
68
+ Logger.info(`已加载用户token缓存,共 ${loadedCount} 条记录`);
69
+ }
70
+ catch (error) {
71
+ Logger.warn('加载用户token缓存失败:', error);
72
+ }
73
+ }
74
+ else {
75
+ Logger.info('用户token缓存文件不存在,将创建新的缓存');
76
+ }
77
+ }
78
+ /**
79
+ * 加载租户token缓存
80
+ */
81
+ loadTenantTokenCache() {
82
+ if (fs.existsSync(this.tenantTokenCacheFile)) {
83
+ try {
84
+ const raw = fs.readFileSync(this.tenantTokenCacheFile, 'utf-8');
85
+ const cacheData = JSON.parse(raw);
86
+ let loadedCount = 0;
87
+ for (const key in cacheData) {
88
+ if (key.startsWith('tenant_access_token:')) {
89
+ this.cache.set(key, cacheData[key]);
90
+ loadedCount++;
91
+ }
92
+ }
93
+ Logger.info(`已加载租户token缓存,共 ${loadedCount} 条记录`);
94
+ }
95
+ catch (error) {
96
+ Logger.warn('加载租户token缓存失败:', error);
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * 根据key获取完整的用户token信息
102
+ * @param key 缓存键
103
+ * @returns 完整的用户token信息对象,如果未找到或refresh_token过期则返回null
104
+ */
105
+ getUserTokenInfo(key) {
106
+ const cacheKey = `user_access_token:${key}`;
107
+ const cacheItem = this.cache.get(cacheKey);
108
+ if (!cacheItem) {
109
+ Logger.debug(`用户token信息未找到: ${key}`);
110
+ return null;
111
+ }
112
+ const tokenInfo = cacheItem.data;
113
+ const now = Math.floor(Date.now() / 1000);
114
+ // 检查refresh_token是否过期(如果有的话)
115
+ if (tokenInfo.refresh_token && tokenInfo.refresh_token_expires_at) {
116
+ if (tokenInfo.refresh_token_expires_at < now) {
117
+ Logger.debug(`用户token的refresh_token已过期,从缓存中删除: ${key}`);
118
+ this.cache.delete(cacheKey);
119
+ this.saveUserTokenCache();
120
+ return null;
121
+ }
122
+ }
123
+ else {
124
+ // 如果没有refresh_token信息,检查缓存本身是否过期
125
+ if (Date.now() > cacheItem.expiresAt) {
126
+ Logger.debug(`用户token缓存已过期: ${key}`);
127
+ this.cache.delete(cacheKey);
128
+ this.saveUserTokenCache();
129
+ return null;
130
+ }
131
+ }
132
+ Logger.debug(`获取用户token信息成功: ${key}`);
133
+ return tokenInfo;
134
+ }
135
+ /**
136
+ * 根据key获取用户的access_token值
137
+ * @param key 缓存键
138
+ * @returns access_token字符串,如果未找到或已过期则返回null
139
+ */
140
+ getUserToken(key) {
141
+ const tokenInfo = this.getUserTokenInfo(key);
142
+ return tokenInfo ? tokenInfo.access_token : null;
143
+ }
144
+ /**
145
+ * 根据key获取租户token信息
146
+ * @param key 缓存键
147
+ * @returns 租户token信息,如果未找到或已过期则返回null
148
+ */
149
+ getTenantTokenInfo(key) {
150
+ const cacheKey = `tenant_access_token:${key}`;
151
+ const cacheItem = this.cache.get(cacheKey);
152
+ if (!cacheItem) {
153
+ Logger.debug(`租户token信息未找到: ${key}`);
154
+ return null;
155
+ }
156
+ // 检查是否过期
157
+ if (Date.now() > cacheItem.expiresAt) {
158
+ Logger.debug(`租户token信息已过期: ${key}`);
159
+ this.cache.delete(cacheKey);
160
+ this.saveTenantTokenCache();
161
+ return null;
162
+ }
163
+ Logger.debug(`获取租户token信息成功: ${key}`);
164
+ return cacheItem.data;
165
+ }
166
+ /**
167
+ * 删除租户token
168
+ * @param key 缓存键
169
+ * @returns 是否成功删除
170
+ */
171
+ removeTenantToken(key) {
172
+ const cacheKey = `tenant_access_token:${key}`;
173
+ const result = this.cache.delete(cacheKey);
174
+ if (result) {
175
+ this.saveUserTokenCache();
176
+ Logger.debug(`租户token删除成功: ${key}`);
177
+ }
178
+ return result;
179
+ }
180
+ /**
181
+ * 根据key获取租户的access_token值
182
+ * @param key 缓存键
183
+ * @returns app_access_token字符串,如果未找到或已过期则返回null
184
+ */
185
+ getTenantToken(key) {
186
+ const tokenInfo = this.getTenantTokenInfo(key);
187
+ return tokenInfo ? tokenInfo.app_access_token : null;
188
+ }
189
+ /**
190
+ * 缓存用户token信息
191
+ * @param key 缓存键
192
+ * @param tokenInfo 用户token信息
193
+ * @param customTtl 自定义TTL(秒),如果不提供则使用refresh_token的过期时间
194
+ * @returns 是否成功缓存
195
+ */
196
+ cacheUserToken(key, tokenInfo, customTtl) {
197
+ try {
198
+ const now = Date.now();
199
+ const cacheKey = `user_access_token:${key}`;
200
+ // 计算过期时间 - 优先使用refresh_token的过期时间,确保可以刷新
201
+ let expiresAt;
202
+ if (customTtl) {
203
+ expiresAt = now + (customTtl * 1000);
204
+ }
205
+ else if (tokenInfo.refresh_token_expires_at) {
206
+ // 使用refresh_token的过期时间,确保在refresh_token有效期内缓存不会被清除
207
+ expiresAt = tokenInfo.refresh_token_expires_at * 1000; // 转换为毫秒
208
+ Logger.debug(`使用refresh_token过期时间作为缓存过期时间: ${new Date(expiresAt).toISOString()}`);
209
+ }
210
+ else if (tokenInfo.expires_at) {
211
+ // 如果没有refresh_token_expires_at信息,降级使用access_token的过期时间
212
+ expiresAt = tokenInfo.expires_at * 1000;
213
+ Logger.warn(`没有refresh_token过期时间戳,使用access_token过期时间: ${new Date(expiresAt).toISOString()}`);
214
+ }
215
+ else {
216
+ // 最后的降级方案:如果没有任何过期时间信息,设置默认的2小时过期
217
+ expiresAt = now + (2 * 60 * 60 * 1000); // 2小时
218
+ Logger.warn(`没有过期时间信息,使用默认2小时作为缓存过期时间`);
219
+ }
220
+ const cacheItem = {
221
+ data: tokenInfo,
222
+ timestamp: now,
223
+ expiresAt: expiresAt
224
+ };
225
+ this.cache.set(cacheKey, cacheItem);
226
+ this.saveUserTokenCache();
227
+ Logger.debug(`用户token缓存成功: ${key}, 缓存过期时间: ${new Date(expiresAt).toISOString()}`);
228
+ return true;
229
+ }
230
+ catch (error) {
231
+ Logger.error(`缓存用户token失败: ${key}`, error);
232
+ return false;
233
+ }
234
+ }
235
+ /**
236
+ * 缓存租户token信息
237
+ * @param key 缓存键
238
+ * @param tokenInfo 租户token信息
239
+ * @param customTtl 自定义TTL(秒),如果不提供则使用token本身的过期时间
240
+ * @returns 是否成功缓存
241
+ */
242
+ cacheTenantToken(key, tokenInfo, customTtl) {
243
+ try {
244
+ const now = Date.now();
245
+ const cacheKey = `tenant_access_token:${key}`;
246
+ // 计算过期时间
247
+ let expiresAt;
248
+ if (customTtl) {
249
+ expiresAt = now + (customTtl * 1000);
250
+ }
251
+ else if (tokenInfo.expires_at) {
252
+ expiresAt = tokenInfo.expires_at * 1000; // 转换为毫秒
253
+ }
254
+ else {
255
+ // 如果没有过期时间信息,设置默认的2小时过期
256
+ expiresAt = now + (2 * 60 * 60 * 1000);
257
+ Logger.warn(`租户token没有过期时间信息,使用默认2小时`);
258
+ }
259
+ const cacheItem = {
260
+ data: tokenInfo,
261
+ timestamp: now,
262
+ expiresAt: expiresAt
263
+ };
264
+ this.cache.set(cacheKey, cacheItem);
265
+ this.saveTenantTokenCache();
266
+ Logger.debug(`租户token缓存成功: ${key}, 过期时间: ${new Date(expiresAt).toISOString()}`);
267
+ return true;
268
+ }
269
+ catch (error) {
270
+ Logger.error(`缓存租户token失败: ${key}`, error);
271
+ return false;
272
+ }
273
+ }
274
+ /**
275
+ * 检查用户token状态
276
+ * @param key 缓存键
277
+ * @returns token状态信息
278
+ */
279
+ checkUserTokenStatus(key) {
280
+ const tokenInfo = this.getUserTokenInfo(key);
281
+ if (!tokenInfo) {
282
+ return {
283
+ isValid: false,
284
+ isExpired: true,
285
+ canRefresh: false,
286
+ shouldRefresh: false
287
+ };
288
+ }
289
+ const now = Math.floor(Date.now() / 1000);
290
+ const isExpired = tokenInfo.expires_at ? tokenInfo.expires_at < now : false;
291
+ const timeToExpiry = tokenInfo.expires_at ? Math.max(0, tokenInfo.expires_at - now) : 0;
292
+ // 判断是否可以刷新
293
+ const canRefresh = !!(tokenInfo.refresh_token &&
294
+ tokenInfo.refresh_token_expires_at &&
295
+ tokenInfo.refresh_token_expires_at > now);
296
+ // 判断是否应该提前刷新(提前5分钟)
297
+ const shouldRefresh = timeToExpiry > 0 && timeToExpiry < 300 && canRefresh;
298
+ return {
299
+ isValid: !isExpired,
300
+ isExpired,
301
+ canRefresh,
302
+ shouldRefresh
303
+ };
304
+ }
305
+ /**
306
+ * 删除用户token
307
+ * @param key 缓存键
308
+ * @returns 是否成功删除
309
+ */
310
+ removeUserToken(key) {
311
+ const cacheKey = `user_access_token:${key}`;
312
+ const result = this.cache.delete(cacheKey);
313
+ if (result) {
314
+ this.saveUserTokenCache();
315
+ Logger.debug(`用户token删除成功: ${key}`);
316
+ }
317
+ return result;
318
+ }
319
+ /**
320
+ * 保存用户token缓存到文件
321
+ */
322
+ saveUserTokenCache() {
323
+ const cacheData = {};
324
+ for (const [key, value] of this.cache.entries()) {
325
+ if (key.startsWith('user_access_token:')) {
326
+ cacheData[key] = value;
327
+ }
328
+ }
329
+ try {
330
+ fs.writeFileSync(this.userTokenCacheFile, JSON.stringify(cacheData, null, 2), 'utf-8');
331
+ Logger.debug('用户token缓存已保存到文件');
332
+ }
333
+ catch (error) {
334
+ Logger.warn('保存用户token缓存失败:', error);
335
+ }
336
+ }
337
+ /**
338
+ * 保存租户token缓存到文件
339
+ */
340
+ saveTenantTokenCache() {
341
+ const cacheData = {};
342
+ for (const [key, value] of this.cache.entries()) {
343
+ if (key.startsWith('tenant_access_token:')) {
344
+ cacheData[key] = value;
345
+ }
346
+ }
347
+ try {
348
+ fs.writeFileSync(this.tenantTokenCacheFile, JSON.stringify(cacheData, null, 2), 'utf-8');
349
+ Logger.debug('租户token缓存已保存到文件');
350
+ }
351
+ catch (error) {
352
+ Logger.warn('保存租户token缓存失败:', error);
353
+ }
354
+ }
355
+ /**
356
+ * 清理过期缓存
357
+ * 对于用户token,只有在refresh_token过期时才清理
358
+ * 对于租户token,按缓存过期时间清理
359
+ * @returns 清理的数量
360
+ */
361
+ cleanExpiredTokens() {
362
+ const now = Date.now();
363
+ const nowSeconds = Math.floor(now / 1000);
364
+ let cleanedCount = 0;
365
+ const keysToDelete = [];
366
+ for (const [key, cacheItem] of this.cache.entries()) {
367
+ let shouldDelete = false;
368
+ if (key.startsWith('user_access_token:')) {
369
+ // 用户token:检查refresh_token是否过期
370
+ const tokenInfo = cacheItem.data;
371
+ if (tokenInfo.refresh_token && tokenInfo.refresh_token_expires_at) {
372
+ // 有refresh_token,只有refresh_token过期才删除
373
+ shouldDelete = tokenInfo.refresh_token_expires_at < nowSeconds;
374
+ if (shouldDelete) {
375
+ Logger.debug(`清理用户token - refresh_token已过期: ${key}`);
376
+ }
377
+ }
378
+ else {
379
+ // 没有refresh_token,按缓存过期时间删除
380
+ shouldDelete = cacheItem.expiresAt <= now;
381
+ if (shouldDelete) {
382
+ Logger.debug(`清理用户token - 无refresh_token且缓存过期: ${key}`);
383
+ }
384
+ }
385
+ }
386
+ else {
387
+ // 租户token或其他类型:按缓存过期时间删除
388
+ shouldDelete = cacheItem.expiresAt <= now;
389
+ if (shouldDelete) {
390
+ Logger.debug(`清理过期缓存: ${key}`);
391
+ }
392
+ }
393
+ if (shouldDelete) {
394
+ keysToDelete.push(key);
395
+ }
396
+ }
397
+ // 批量删除
398
+ keysToDelete.forEach(key => {
399
+ this.cache.delete(key);
400
+ cleanedCount++;
401
+ });
402
+ if (cleanedCount > 0) {
403
+ // 分别保存用户和租户缓存
404
+ this.saveUserTokenCache();
405
+ this.saveTenantTokenCache();
406
+ Logger.info(`清理过期token,删除了 ${cleanedCount} 条记录`);
407
+ }
408
+ return cleanedCount;
409
+ }
410
+ /**
411
+ * 启动缓存清理定时器
412
+ */
413
+ startCacheCleanupTimer() {
414
+ // 每5分钟清理一次过期缓存
415
+ setInterval(() => {
416
+ this.cleanExpiredTokens();
417
+ }, 5 * 60 * 1000);
418
+ Logger.info('Token缓存清理定时器已启动,每5分钟执行一次');
419
+ }
420
+ }
@@ -0,0 +1,104 @@
1
+ import { Logger } from '../logger.js';
2
+ /**
3
+ * 用户认证管理器
4
+ * 管理 sessionId 与 userKey 的映射关系
5
+ */
6
+ export class UserAuthManager {
7
+ /**
8
+ * 私有构造函数,用于单例模式
9
+ */
10
+ constructor() {
11
+ Object.defineProperty(this, "sessionToUserKey", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ }); // sessionId -> userKey
17
+ this.sessionToUserKey = new Map();
18
+ }
19
+ /**
20
+ * 获取用户认证管理器实例
21
+ * @returns 用户认证管理器实例
22
+ */
23
+ static getInstance() {
24
+ if (!UserAuthManager.instance) {
25
+ UserAuthManager.instance = new UserAuthManager();
26
+ }
27
+ return UserAuthManager.instance;
28
+ }
29
+ /**
30
+ * 创建用户会话
31
+ * @param sessionId 会话ID
32
+ * @param userKey 用户密钥
33
+ * @returns 是否创建成功
34
+ */
35
+ createSession(sessionId, userKey) {
36
+ if (!sessionId || !userKey) {
37
+ Logger.warn('创建会话失败:sessionId 或 userKey 为空');
38
+ return false;
39
+ }
40
+ this.sessionToUserKey.set(sessionId, userKey);
41
+ Logger.info(`创建用户会话:sessionId=${sessionId}, userKey=${userKey}`);
42
+ return true;
43
+ }
44
+ /**
45
+ * 根据 sessionId 获取 userKey
46
+ * @param sessionId 会话ID
47
+ * @returns 用户密钥,如果未找到则返回 null
48
+ */
49
+ getUserKeyBySessionId(sessionId) {
50
+ if (!sessionId) {
51
+ return null;
52
+ }
53
+ const userKey = this.sessionToUserKey.get(sessionId);
54
+ if (!userKey) {
55
+ Logger.debug(`未找到会话:${sessionId}`);
56
+ return null;
57
+ }
58
+ Logger.debug(`获取用户密钥:sessionId=${sessionId}, userKey=${userKey}`);
59
+ return userKey;
60
+ }
61
+ /**
62
+ * 删除会话
63
+ * @param sessionId 会话ID
64
+ * @returns 是否删除成功
65
+ */
66
+ removeSession(sessionId) {
67
+ if (!sessionId) {
68
+ return false;
69
+ }
70
+ const userKey = this.sessionToUserKey.get(sessionId);
71
+ if (!userKey) {
72
+ Logger.debug(`会话不存在:${sessionId}`);
73
+ return false;
74
+ }
75
+ this.sessionToUserKey.delete(sessionId);
76
+ Logger.info(`删除用户会话:sessionId=${sessionId}, userKey=${userKey}`);
77
+ return true;
78
+ }
79
+ /**
80
+ * 检查会话是否存在
81
+ * @param sessionId 会话ID
82
+ * @returns 会话是否存在
83
+ */
84
+ hasSession(sessionId) {
85
+ return this.sessionToUserKey.has(sessionId);
86
+ }
87
+ /**
88
+ * 获取所有会话统计信息
89
+ * @returns 会话统计信息
90
+ */
91
+ getStats() {
92
+ return {
93
+ totalSessions: this.sessionToUserKey.size
94
+ };
95
+ }
96
+ /**
97
+ * 清空所有会话
98
+ */
99
+ clearAllSessions() {
100
+ const count = this.sessionToUserKey.size;
101
+ this.sessionToUserKey.clear();
102
+ Logger.info(`清空所有会话,删除了 ${count} 个会话`);
103
+ }
104
+ }
@@ -0,0 +1,81 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ /**
3
+ * 用户上下文管理器
4
+ * 使用 AsyncLocalStorage 在异步调用链中传递用户信息
5
+ */
6
+ export class UserContextManager {
7
+ constructor() {
8
+ Object.defineProperty(this, "asyncLocalStorage", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ this.asyncLocalStorage = new AsyncLocalStorage();
15
+ }
16
+ /**
17
+ * 获取单例实例
18
+ */
19
+ static getInstance() {
20
+ if (!UserContextManager.instance) {
21
+ UserContextManager.instance = new UserContextManager();
22
+ }
23
+ return UserContextManager.instance;
24
+ }
25
+ /**
26
+ * 在指定上下文中运行回调函数
27
+ * @param context 用户上下文
28
+ * @param callback 回调函数
29
+ * @returns 回调函数的返回值
30
+ */
31
+ run(context, callback) {
32
+ return this.asyncLocalStorage.run(context, callback);
33
+ }
34
+ /**
35
+ * 获取当前上下文中的用户密钥
36
+ * @returns 用户密钥,如果不存在则返回空字符串
37
+ */
38
+ getUserKey() {
39
+ const context = this.asyncLocalStorage.getStore();
40
+ return context?.userKey || '';
41
+ }
42
+ /**
43
+ * 获取当前上下文中的基础URL
44
+ * @returns 基础URL,如果不存在则返回空字符串
45
+ */
46
+ getBaseUrl() {
47
+ const context = this.asyncLocalStorage.getStore();
48
+ return context?.baseUrl || '';
49
+ }
50
+ /**
51
+ * 获取当前完整的用户上下文
52
+ * @returns 用户上下文,如果不存在则返回 undefined
53
+ */
54
+ getContext() {
55
+ return this.asyncLocalStorage.getStore();
56
+ }
57
+ /**
58
+ * 检查是否存在用户上下文
59
+ * @returns 如果存在用户上下文则返回 true
60
+ */
61
+ hasContext() {
62
+ return this.asyncLocalStorage.getStore() !== undefined;
63
+ }
64
+ }
65
+ /**
66
+ * 获取协议
67
+ */
68
+ function getProtocol(req) {
69
+ if (req.secure || req.get('X-Forwarded-Proto') === 'https') {
70
+ return 'https';
71
+ }
72
+ return 'http';
73
+ }
74
+ /**
75
+ * 获取基础URL
76
+ */
77
+ export function getBaseUrl(req) {
78
+ const protocol = getProtocol(req);
79
+ const host = req.get('X-Forwarded-Host') || req.get('host');
80
+ return `${protocol}://${host}`;
81
+ }