Skip to content

Commit 167bdab

Browse files
authored
refactor(coze): 重构 CozeApiService 使用官方 SDK 并模块化拆分 (#502)
- 为什么改:提升代码可维护性和模块化程度,优化扣子API服务的架构设计 - 改了什么: - 将原本的单体 CozeApiService.ts 拆分为三个独立模块 - 新增 cache.ts:独立的缓存管理器,支持 TTL 和模式匹配清除 - 新增 client.ts:封装 Coze 客户端创建逻辑,支持中英文环境 - 新增 service.ts:核心业务逻辑,整合缓存和客户端功能 - 更新 index.ts:统一导出新模块的接口 - 优化 CozeApiHandler.ts:增加详细的日志记录,更新导入路径 - 影响范围: - 删除 services/CozeApiService.ts 文件(167行) - 新增 lib/coze/ 目录下的三个模块文件(共189行) - 更新 services/index.ts 移除对旧文件的导出 - Handler 层通过新路径导入服务,功能保持不变 - 验证方式: - API 接口保持向后兼容,无需修改前端调用 - 缓存功能正常工作,支持按模式清除 - 日志记录更加详细,便于调试和监控 - 代码结构更清晰,便于单元测试和维护
1 parent d9e4d93 commit 167bdab

File tree

6 files changed

+124
-89
lines changed

6 files changed

+124
-89
lines changed

apps/backend/handlers/CozeApiHandler.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
* 提供扣子工作空间和工作流相关的 RESTful API 接口
44
*/
55

6+
import { CozeApiService } from "@/lib/coze";
67
import { logger } from "@root/Logger";
78
import { configManager } from "@root/configManager";
89
import type { CozeWorkflowsParams } from "@root/types/coze";
9-
import { CozeApiService } from "@services/CozeApiService";
1010
import type { Context } from "hono";
1111

1212
/**
@@ -136,8 +136,9 @@ export class CozeApiHandler {
136136
}
137137

138138
const cozeApiService = getCozeApiService();
139-
const workspaces = await cozeApiService.getWorkspaces();
140139

140+
logger.info("调用 Coze API 获取工作空间列表");
141+
const workspaces = await cozeApiService.getWorkspaces();
141142
logger.info(`成功获取 ${workspaces.length} 个工作空间`);
142143

143144
return c.json(
@@ -251,8 +252,11 @@ export class CozeApiHandler {
251252
};
252253

253254
const cozeApiService = getCozeApiService();
254-
const result = await cozeApiService.getWorkflows(params);
255255

256+
logger.info(
257+
`开始获取工作空间 ${workspace_id} 的工作流列表,页码: ${page_num},每页: ${page_size}`
258+
);
259+
const result = await cozeApiService.getWorkflows(params);
256260
logger.info(
257261
`成功获取工作空间 ${workspace_id}${result.items.length} 个工作流`
258262
);
@@ -354,7 +358,9 @@ export class CozeApiHandler {
354358
const pattern = c.req.query("pattern"); // 可选的缓存模式参数
355359

356360
const cozeApiService = getCozeApiService();
361+
357362
const statsBefore = cozeApiService.getCacheStats();
363+
logger.info(`开始清除缓存${pattern ? ` (模式: ${pattern})` : ""}`);
358364

359365
cozeApiService.clearCache(pattern);
360366

apps/backend/lib/coze/cache.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* 扣子 API 缓存管理器
3+
* 提供带 TTL 的内存缓存功能
4+
*/
5+
6+
import type { CacheItem } from "@root/types/coze";
7+
8+
/**
9+
* 缓存管理类
10+
*/
11+
export class CozeApiCache {
12+
private cache = new Map<string, CacheItem>();
13+
14+
// 缓存过期时间配置(毫秒)
15+
private readonly TTL = {
16+
workspaces: 30 * 60 * 1000, // 工作空间缓存30分钟
17+
workflows: 5 * 60 * 1000, // 工作流缓存5分钟
18+
};
19+
20+
/**
21+
* 设置缓存
22+
*/
23+
set<T>(key: string, data: T, type: "workspaces" | "workflows"): void {
24+
this.cache.set(key, {
25+
data,
26+
timestamp: Date.now(),
27+
ttl: this.TTL[type],
28+
});
29+
}
30+
31+
/**
32+
* 获取缓存
33+
*/
34+
get<T>(key: string): T | null {
35+
const item = this.cache.get(key);
36+
if (!item) {
37+
return null;
38+
}
39+
40+
// 检查是否过期
41+
if (Date.now() - item.timestamp > item.ttl) {
42+
this.cache.delete(key);
43+
return null;
44+
}
45+
46+
return item.data as T;
47+
}
48+
49+
/**
50+
* 清除缓存
51+
*/
52+
clear(pattern?: string): void {
53+
if (!pattern) {
54+
this.cache.clear();
55+
return;
56+
}
57+
58+
// 清除匹配模式的缓存
59+
for (const key of this.cache.keys()) {
60+
if (key.includes(pattern)) {
61+
this.cache.delete(key);
62+
}
63+
}
64+
}
65+
66+
/**
67+
* 获取缓存统计信息
68+
*/
69+
getStats(): { size: number; keys: string[] } {
70+
return {
71+
size: this.cache.size,
72+
keys: Array.from(this.cache.keys()),
73+
};
74+
}
75+
}

apps/backend/lib/coze/client.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* 扣子 API 客户端封装
3+
* 提供统一的客户端创建和配置
4+
*/
5+
6+
import { CozeAPI } from "@/lib/coze";
7+
import config from "./config";
8+
9+
export type Language = "zh" | "en";
10+
11+
/**
12+
* 创建 Coze API 客户端
13+
* @param token - API 访问令牌
14+
* @param language - API 环境语言,默认为 "zh"(中文),可选 "en"(英文)
15+
*/
16+
export function createCozeClient(
17+
token: string,
18+
language: Language = "zh"
19+
): CozeAPI {
20+
if (!token || typeof token !== "string" || token.trim() === "") {
21+
throw new Error("扣子 API Token 不能为空");
22+
}
23+
24+
const env = config[language] || config.zh;
25+
26+
return new CozeAPI({
27+
baseURL: env.COZE_BASE_URL,
28+
token: token.trim(),
29+
baseWsURL: env.COZE_BASE_WS_URL,
30+
debug: false,
31+
});
32+
}

apps/backend/lib/coze/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
export { default as config } from "./config";
22
export * from "@coze/api";
3+
export { createCozeClient } from "./client";
4+
export { CozeApiCache } from "./cache";
5+
export { CozeApiService } from "./service";
Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,104 +4,25 @@
44
*/
55

66
import type { WorkSpace } from "@/lib/coze";
7-
import { CozeAPI, config } from "@/lib/coze";
87
import type {
9-
CacheItem,
108
CozeWorkflowsData,
119
CozeWorkflowsParams,
1210
CozeWorkflowsResponse,
1311
} from "@root/types/coze";
14-
import { logger } from "../Logger";
15-
16-
/**
17-
* 缓存管理类
18-
*/
19-
class CozeApiCache {
20-
private cache = new Map<string, CacheItem>();
21-
22-
// 缓存过期时间配置(毫秒)
23-
private readonly TTL = {
24-
workspaces: 30 * 60 * 1000, // 工作空间缓存30分钟
25-
workflows: 5 * 60 * 1000, // 工作流缓存5分钟
26-
};
27-
28-
/**
29-
* 设置缓存
30-
*/
31-
set<T>(key: string, data: T, type: "workspaces" | "workflows"): void {
32-
this.cache.set(key, {
33-
data,
34-
timestamp: Date.now(),
35-
ttl: this.TTL[type],
36-
});
37-
}
38-
39-
/**
40-
* 获取缓存
41-
*/
42-
get<T>(key: string): T | null {
43-
const item = this.cache.get(key);
44-
if (!item) {
45-
return null;
46-
}
47-
48-
// 检查是否过期
49-
if (Date.now() - item.timestamp > item.ttl) {
50-
this.cache.delete(key);
51-
return null;
52-
}
53-
54-
return item.data as T;
55-
}
56-
57-
/**
58-
* 清除缓存
59-
*/
60-
clear(pattern?: string): void {
61-
if (!pattern) {
62-
this.cache.clear();
63-
return;
64-
}
65-
66-
// 清除匹配模式的缓存
67-
for (const key of this.cache.keys()) {
68-
if (key.includes(pattern)) {
69-
this.cache.delete(key);
70-
}
71-
}
72-
}
73-
74-
/**
75-
* 获取缓存统计信息
76-
*/
77-
getStats(): { size: number; keys: string[] } {
78-
return {
79-
size: this.cache.size,
80-
keys: Array.from(this.cache.keys()),
81-
};
82-
}
83-
}
12+
import { CozeApiCache } from "./cache";
13+
import { createCozeClient } from "./client";
8414

8515
/**
8616
* 扣子 API 服务类
8717
*/
8818
export class CozeApiService {
8919
private cache = new CozeApiCache();
90-
private token: string;
91-
private client: CozeAPI;
20+
private token: string; // 保留 token 字段用于可能的后续扩展(如 token 刷新)
21+
private client: ReturnType<typeof createCozeClient>;
9222

9323
constructor(token: string) {
94-
if (!token || typeof token !== "string" || token.trim() === "") {
95-
throw new Error("扣子 API Token 不能为空");
96-
}
9724
this.token = token.trim();
98-
99-
this.client = new CozeAPI({
100-
baseURL: config.zh.COZE_BASE_URL,
101-
token: this.token,
102-
baseWsURL: config.zh.COZE_BASE_WS_URL,
103-
debug: false,
104-
});
25+
this.client = createCozeClient(this.token);
10526
}
10627

10728
/**
@@ -155,7 +76,6 @@ export class CozeApiService {
15576
*/
15677
clearCache(pattern?: string): void {
15778
this.cache.clear(pattern);
158-
logger.info(`清除扣子 API 缓存${pattern ? ` (模式: ${pattern})` : ""}`);
15979
}
16080

16181
/**

apps/backend/services/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ export * from "./ErrorHandler.js";
88

99
// CustomMCPHandler 导出 - 避免冲突的 ToolCallResult
1010
export { CustomMCPHandler } from "./CustomMCPHandler.js";
11-
export * from "./CozeApiService.js";

0 commit comments

Comments
 (0)