@myassis/gateway 1.0.0
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.
- package/README.md +194 -0
- package/dist/.env +6 -0
- package/dist/api/index.js +182 -0
- package/dist/config/index.js +41 -0
- package/dist/index.js +183 -0
- package/dist/middleware/auth.js +53 -0
- package/dist/middleware/errorHandler.js +20 -0
- package/dist/routes/agent.js +513 -0
- package/dist/routes/auth.js +172 -0
- package/dist/routes/chat.js +45 -0
- package/dist/routes/config.js +21 -0
- package/dist/routes/models.js +123 -0
- package/dist/routes/service.js +240 -0
- package/dist/routes/settings.js +101 -0
- package/dist/routes/skillHub.js +126 -0
- package/dist/routes/skills.js +159 -0
- package/dist/routes/tasks.js +149 -0
- package/dist/routes/upload.js +129 -0
- package/dist/routes/version.js +66 -0
- package/dist/services/HMSPushService.js +24 -0
- package/dist/services/LocalTaskService.js +223 -0
- package/dist/services/NotificationService.js +242 -0
- package/dist/services/ServiceManager.js +348 -0
- package/dist/services/TaskSchedulerService.js +195 -0
- package/dist/services/TaskService.js +240 -0
- package/dist/services/WebSocketService.js +236 -0
- package/dist/services/agent/Agent.js +120 -0
- package/dist/services/agent/AgentManager.js +265 -0
- package/dist/services/agent/AgentStore.js +73 -0
- package/dist/services/dataService.js +293 -0
- package/dist/services/index.js +15 -0
- package/dist/services/llm/LLMClient.js +724 -0
- package/dist/services/memory/MemoryManager.js +117 -0
- package/dist/services/model/ModelCapabilities.js +141 -0
- package/dist/services/model/index.js +4 -0
- package/dist/services/models.js +16 -0
- package/dist/services/session/MigrationManager.js +176 -0
- package/dist/services/session/Session.js +733 -0
- package/dist/services/session/SessionManager.js +255 -0
- package/dist/services/session/SessionStore.js +186 -0
- package/dist/services/session/index.js +3 -0
- package/dist/services/skills.js +34 -0
- package/dist/services/systemPrompt.js +150 -0
- package/dist/services/task/PushTokenStore.js +124 -0
- package/dist/services/task/TaskStore.js +143 -0
- package/dist/services/tools/calculator.js +27 -0
- package/dist/services/tools/edit.js +318 -0
- package/dist/services/tools/exec.js +119 -0
- package/dist/services/tools/fetch.js +155 -0
- package/dist/services/tools/file.js +315 -0
- package/dist/services/tools/index.js +48 -0
- package/dist/services/tools/keyboard.js +145 -0
- package/dist/services/tools/model.js +86 -0
- package/dist/services/tools/mouse.js +55 -0
- package/dist/services/tools/screenshot.js +19 -0
- package/dist/services/tools/search.js +53 -0
- package/dist/services/tools/skill.js +108 -0
- package/dist/services/tools/task.js +110 -0
- package/dist/services/tools/types.js +1 -0
- package/dist/services/tools/webFetch.js +34 -0
- package/dist/stores/authStore.js +178 -0
- package/dist/stores/index.js +6 -0
- package/dist/stores/memoryStore.js +191 -0
- package/dist/stores/persistStore.js +317 -0
- package/package.json +94 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway 认证存储模块
|
|
3
|
+
* 负责管理 Token 和用户信息的内存存储与持久化
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { getLogger } from '@pocketclaw/shared';
|
|
9
|
+
import { sessionManager } from '@/services/session';
|
|
10
|
+
const logger = getLogger('authStore');
|
|
11
|
+
// 认证数据存储文件路径(使用应用数据目录,支持 STORAGE_DIR 环境变量)
|
|
12
|
+
const AUTH_STORAGE_DIR = process.env.STORAGE_DIR || path.join(os.homedir(), 'myclaw-gateway-storage');
|
|
13
|
+
const AUTH_STORAGE_FILE = path.join(AUTH_STORAGE_DIR, 'auth', 'auth.json');
|
|
14
|
+
// 内存中的认证数据
|
|
15
|
+
let auth = null;
|
|
16
|
+
// 确保存储目录存在
|
|
17
|
+
function ensureStorageDir() {
|
|
18
|
+
const dir = path.dirname(AUTH_STORAGE_FILE);
|
|
19
|
+
if (!fs.existsSync(dir)) {
|
|
20
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 从本地文件加载认证数据
|
|
24
|
+
function loadFromFile() {
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(AUTH_STORAGE_FILE)) {
|
|
27
|
+
const data = fs.readFileSync(AUTH_STORAGE_FILE, 'utf-8');
|
|
28
|
+
const authData = JSON.parse(data);
|
|
29
|
+
// 检查 token 是否过期
|
|
30
|
+
if (authData.expiresAt && authData.expiresAt < Date.now()) {
|
|
31
|
+
logger.debug('Token expired, clearing...');
|
|
32
|
+
clearAuth();
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
logger.debug('Auth data loaded from file');
|
|
36
|
+
return authData;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('Failed to load auth from file:', error);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
// 保存认证数据到本地文件
|
|
45
|
+
function saveAuthToFile(authData) {
|
|
46
|
+
ensureStorageDir();
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(AUTH_STORAGE_FILE, JSON.stringify(authData, null, 2), 'utf-8');
|
|
49
|
+
logger.debug('Auth data saved to file');
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error('Failed to save auth to file:', error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 清除本地认证文件
|
|
56
|
+
function clearAuthFile() {
|
|
57
|
+
try {
|
|
58
|
+
if (fs.existsSync(AUTH_STORAGE_FILE)) {
|
|
59
|
+
fs.unlinkSync(AUTH_STORAGE_FILE);
|
|
60
|
+
logger.debug('Auth file cleared');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error('Failed to clear auth file:', error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 清除认证数据
|
|
68
|
+
function clearAuth() {
|
|
69
|
+
auth = null;
|
|
70
|
+
clearAuthFile();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 认证存储管理器
|
|
74
|
+
*/
|
|
75
|
+
export const authStore = {
|
|
76
|
+
/**
|
|
77
|
+
* 初始化:从本地加载认证数据到内存
|
|
78
|
+
* 应在 Gateway 启动时调用
|
|
79
|
+
*/
|
|
80
|
+
load() {
|
|
81
|
+
auth = loadFromFile();
|
|
82
|
+
if (auth) {
|
|
83
|
+
logger.debug('Initialized with user:', auth.user.nickname);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
logger.debug('Initialized, no valid auth data');
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
/**
|
|
90
|
+
* 保存认证数据(内存 + 本地)
|
|
91
|
+
*/
|
|
92
|
+
save(authData) {
|
|
93
|
+
auth = authData;
|
|
94
|
+
saveAuthToFile(authData);
|
|
95
|
+
// 用户登录后初始化 SessionManager
|
|
96
|
+
sessionManager.initialize();
|
|
97
|
+
logger.debug('Auth saved for user:', authData.user.nickname);
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* 获取当前认证数据
|
|
101
|
+
*/
|
|
102
|
+
get() {
|
|
103
|
+
return auth;
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* 获取用户信息
|
|
107
|
+
*/
|
|
108
|
+
getUser() {
|
|
109
|
+
return auth?.user || null;
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* 获取 Token(自动检查过期)
|
|
113
|
+
*/
|
|
114
|
+
getToken() {
|
|
115
|
+
if (!auth) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
// 检查是否过期(预留 60 秒缓冲时间)
|
|
119
|
+
if (auth.expiresAt && auth.expiresAt < Date.now() + 60000) {
|
|
120
|
+
logger.debug('Token expired');
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return auth.token;
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* 获取刷新 Token
|
|
127
|
+
*/
|
|
128
|
+
getRefreshToken() {
|
|
129
|
+
return auth?.refreshToken || null;
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* 更新 Token
|
|
133
|
+
*/
|
|
134
|
+
updateToken(token, expiresIn, refreshToken) {
|
|
135
|
+
if (auth) {
|
|
136
|
+
auth.token = token;
|
|
137
|
+
if (expiresIn) {
|
|
138
|
+
auth.expiresIn = expiresIn;
|
|
139
|
+
auth.expiresAt = Date.now() + expiresIn * 1000;
|
|
140
|
+
}
|
|
141
|
+
if (refreshToken) {
|
|
142
|
+
auth.refreshToken = refreshToken;
|
|
143
|
+
}
|
|
144
|
+
saveAuthToFile(auth);
|
|
145
|
+
logger.debug('Token updated');
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* 获取用户 ID
|
|
150
|
+
*/
|
|
151
|
+
getUserId() {
|
|
152
|
+
return auth?.user?.id || null;
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* 是否已认证
|
|
156
|
+
*/
|
|
157
|
+
isAuthenticated() {
|
|
158
|
+
return this.getToken() !== null;
|
|
159
|
+
},
|
|
160
|
+
/**
|
|
161
|
+
* 清除认证数据(内存 + 本地)
|
|
162
|
+
*/
|
|
163
|
+
clear() {
|
|
164
|
+
clearAuth();
|
|
165
|
+
logger.debug('Auth cleared');
|
|
166
|
+
},
|
|
167
|
+
/**
|
|
168
|
+
* 更新用户信息
|
|
169
|
+
*/
|
|
170
|
+
updateUser(userData) {
|
|
171
|
+
if (auth) {
|
|
172
|
+
auth.user = { ...auth.user, ...userData };
|
|
173
|
+
saveAuthToFile(auth);
|
|
174
|
+
logger.debug('User updated:', auth.user?.nickname || auth.user?.email);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
export default authStore;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway 内存存储模块
|
|
3
|
+
* 用于缓存用户会话、API 响应等临时数据
|
|
4
|
+
*/
|
|
5
|
+
// 内存缓存
|
|
6
|
+
const cache = new Map();
|
|
7
|
+
// 默认过期时间(毫秒)
|
|
8
|
+
const DEFAULT_TTL = 5 * 60 * 1000; // 5分钟
|
|
9
|
+
// 用户会话缓存 TTL
|
|
10
|
+
const SESSION_TTL = 30 * 60 * 1000; // 30分钟
|
|
11
|
+
// API 响应缓存 TTL
|
|
12
|
+
const API_CACHE_TTL = 2 * 60 * 1000; // 2分钟
|
|
13
|
+
// 清理过期缓存(每分钟执行一次)
|
|
14
|
+
setInterval(() => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
cache.forEach((item, key) => {
|
|
17
|
+
if (item.expiresAt < now) {
|
|
18
|
+
cache.delete(key);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}, 60 * 1000);
|
|
22
|
+
/**
|
|
23
|
+
* 内存存储管理器
|
|
24
|
+
*/
|
|
25
|
+
export const memoryStore = {
|
|
26
|
+
/**
|
|
27
|
+
* 设置缓存
|
|
28
|
+
*/
|
|
29
|
+
set(key, value, ttl = DEFAULT_TTL) {
|
|
30
|
+
const expiresAt = Date.now() + ttl;
|
|
31
|
+
cache.set(key, { value, expiresAt });
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* 获取缓存
|
|
35
|
+
*/
|
|
36
|
+
get(key) {
|
|
37
|
+
const item = cache.get(key);
|
|
38
|
+
if (!item) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (Date.now() > item.expiresAt) {
|
|
42
|
+
cache.delete(key);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return item.value;
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* 删除缓存
|
|
49
|
+
*/
|
|
50
|
+
delete(key) {
|
|
51
|
+
cache.delete(key);
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* 清空所有缓存
|
|
55
|
+
*/
|
|
56
|
+
clear() {
|
|
57
|
+
cache.clear();
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* 检查键是否存在
|
|
61
|
+
*/
|
|
62
|
+
has(key) {
|
|
63
|
+
const item = cache.get(key);
|
|
64
|
+
if (!item)
|
|
65
|
+
return false;
|
|
66
|
+
if (Date.now() > item.expiresAt) {
|
|
67
|
+
cache.delete(key);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
// ==================== 用户会话相关 ====================
|
|
73
|
+
/**
|
|
74
|
+
* 缓存用户会话
|
|
75
|
+
*/
|
|
76
|
+
setSession(userId, sessionData) {
|
|
77
|
+
this.set(`session:${userId}`, sessionData, SESSION_TTL);
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* 获取用户会话
|
|
81
|
+
*/
|
|
82
|
+
getSession(userId) {
|
|
83
|
+
const key = `session:${userId}`;
|
|
84
|
+
const item = cache.get(key);
|
|
85
|
+
if (!item)
|
|
86
|
+
return null;
|
|
87
|
+
if (Date.now() > item.expiresAt) {
|
|
88
|
+
cache.delete(key);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return item.value;
|
|
92
|
+
},
|
|
93
|
+
/**
|
|
94
|
+
* 删除用户会话
|
|
95
|
+
*/
|
|
96
|
+
deleteSession(userId) {
|
|
97
|
+
this.delete(`session:${userId}`);
|
|
98
|
+
},
|
|
99
|
+
// ==================== API 缓存相关 ====================
|
|
100
|
+
/**
|
|
101
|
+
* 缓存 API 响应
|
|
102
|
+
*/
|
|
103
|
+
setApiCache(key, data) {
|
|
104
|
+
this.set(`api:${key}`, data, API_CACHE_TTL);
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* 获取 API 缓存
|
|
108
|
+
*/
|
|
109
|
+
getApiCache(key) {
|
|
110
|
+
const item = cache.get(`api:${key}`);
|
|
111
|
+
if (!item)
|
|
112
|
+
return null;
|
|
113
|
+
if (Date.now() > item.expiresAt) {
|
|
114
|
+
cache.delete(`api:${key}`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return item.value;
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* 清除用户相关的 API 缓存
|
|
121
|
+
*/
|
|
122
|
+
clearUserApiCache(userId) {
|
|
123
|
+
// 清理用户相关的缓存
|
|
124
|
+
const keysToDelete = [];
|
|
125
|
+
cache.forEach((_, key) => {
|
|
126
|
+
if (key.startsWith(`user:${userId}:`) || key.startsWith(`skills:${userId}`)) {
|
|
127
|
+
keysToDelete.push(key);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
keysToDelete.forEach(key => cache.delete(key));
|
|
131
|
+
},
|
|
132
|
+
// ==================== 技能相关缓存 ====================
|
|
133
|
+
/**
|
|
134
|
+
* 缓存技能列表
|
|
135
|
+
*/
|
|
136
|
+
setSkillsCache(userId, skills) {
|
|
137
|
+
this.set(`skills:${userId}`, skills, API_CACHE_TTL);
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* 获取技能列表缓存
|
|
141
|
+
*/
|
|
142
|
+
getSkillsCache(userId) {
|
|
143
|
+
const item = cache.get(`skills:${userId}`);
|
|
144
|
+
if (!item)
|
|
145
|
+
return null;
|
|
146
|
+
if (Date.now() > item.expiresAt) {
|
|
147
|
+
cache.delete(`skills:${userId}`);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return item.value;
|
|
151
|
+
},
|
|
152
|
+
/**
|
|
153
|
+
* 清除技能缓存
|
|
154
|
+
*/
|
|
155
|
+
clearSkillsCache(userId) {
|
|
156
|
+
this.delete(`skills:${userId}`);
|
|
157
|
+
},
|
|
158
|
+
// ==================== 技能库缓存 ====================
|
|
159
|
+
/**
|
|
160
|
+
* 缓存技能库列表
|
|
161
|
+
*/
|
|
162
|
+
setSkillHubCache(key, data) {
|
|
163
|
+
this.set(`skillhub:list:${key}`, data, API_CACHE_TTL);
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* 获取技能库列表缓存
|
|
167
|
+
*/
|
|
168
|
+
getSkillHubCache(key) {
|
|
169
|
+
const item = cache.get(`skillhub:list:${key}`);
|
|
170
|
+
if (!item)
|
|
171
|
+
return null;
|
|
172
|
+
if (Date.now() > item.expiresAt) {
|
|
173
|
+
cache.delete(`skillhub:list:${key}`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return item.value;
|
|
177
|
+
},
|
|
178
|
+
/**
|
|
179
|
+
* 清除技能库缓存
|
|
180
|
+
*/
|
|
181
|
+
clearSkillHubCache() {
|
|
182
|
+
const keysToDelete = [];
|
|
183
|
+
cache.forEach((_, key) => {
|
|
184
|
+
if (key.startsWith('skillhub:')) {
|
|
185
|
+
keysToDelete.push(key);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
keysToDelete.forEach(key => cache.delete(key));
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
export default memoryStore;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway 持久化存储模块
|
|
3
|
+
* 用于存储配置、限流数据等需要持久化的数据
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { getLogger } from '@pocketclaw/shared';
|
|
9
|
+
const logger = getLogger('persistStore');
|
|
10
|
+
// 存储文件路径(跨平台兼容)
|
|
11
|
+
const STORAGE_DIR = process.env.STORAGE_DIR || path.join(os.homedir(), 'myclaw-gateway-storage');
|
|
12
|
+
const STORAGE_FILE = path.join(STORAGE_DIR, 'persist.json');
|
|
13
|
+
// 确保存储目录存在
|
|
14
|
+
function ensureStorageDir() {
|
|
15
|
+
if (!fs.existsSync(STORAGE_DIR)) {
|
|
16
|
+
fs.mkdirSync(STORAGE_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// 读取存储文件
|
|
20
|
+
function readStorage() {
|
|
21
|
+
ensureStorageDir();
|
|
22
|
+
try {
|
|
23
|
+
logger.debug(`Reading storage from ${STORAGE_FILE}`);
|
|
24
|
+
if (fs.existsSync(STORAGE_FILE)) {
|
|
25
|
+
const data = fs.readFileSync(STORAGE_FILE, 'utf-8');
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
logger.error(`Failed to read storage: ${error}`);
|
|
31
|
+
}
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
// 写入存储文件
|
|
35
|
+
function writeStorage(data) {
|
|
36
|
+
ensureStorageDir();
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(STORAGE_FILE, JSON.stringify(data, null, 2), 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logger.error(`Failed to write storage: ${error}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 持久化存储管理器
|
|
46
|
+
*/
|
|
47
|
+
export const persistStore = {
|
|
48
|
+
/**
|
|
49
|
+
* 获取值
|
|
50
|
+
*/
|
|
51
|
+
get(key, defaultValue) {
|
|
52
|
+
const storage = readStorage();
|
|
53
|
+
const value = storage[key];
|
|
54
|
+
return value !== undefined ? value : (defaultValue ?? null);
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* 设置值
|
|
58
|
+
*/
|
|
59
|
+
set(key, value) {
|
|
60
|
+
const storage = readStorage();
|
|
61
|
+
storage[key] = value;
|
|
62
|
+
writeStorage(storage);
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* 删除值
|
|
66
|
+
*/
|
|
67
|
+
delete(key) {
|
|
68
|
+
const storage = readStorage();
|
|
69
|
+
delete storage[key];
|
|
70
|
+
writeStorage(storage);
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* 清空所有数据
|
|
74
|
+
*/
|
|
75
|
+
clear() {
|
|
76
|
+
writeStorage({});
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* 获取所有键
|
|
80
|
+
*/
|
|
81
|
+
keys() {
|
|
82
|
+
return Object.keys(readStorage());
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* 检查键是否存在
|
|
86
|
+
*/
|
|
87
|
+
has(key) {
|
|
88
|
+
const storage = readStorage();
|
|
89
|
+
return key in storage;
|
|
90
|
+
},
|
|
91
|
+
// ==================== 限流相关 ====================
|
|
92
|
+
/**
|
|
93
|
+
* 获取请求计数(用于限流)
|
|
94
|
+
*/
|
|
95
|
+
getRateLimitCount(identifier, windowMs = 60000) {
|
|
96
|
+
const key = `ratelimit:${identifier}`;
|
|
97
|
+
const data = this.get(key);
|
|
98
|
+
if (!data || Date.now() > data.resetAt) {
|
|
99
|
+
// 窗口已过期,重置计数
|
|
100
|
+
const resetAt = Date.now() + windowMs;
|
|
101
|
+
this.set(key, { count: 1, resetAt });
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
return data.count;
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* 增加请求计数
|
|
108
|
+
*/
|
|
109
|
+
incrementRateLimit(identifier, windowMs = 60000) {
|
|
110
|
+
const key = `ratelimit:${identifier}`;
|
|
111
|
+
const data = this.get(key);
|
|
112
|
+
if (!data || Date.now() > data.resetAt) {
|
|
113
|
+
const resetAt = Date.now() + windowMs;
|
|
114
|
+
this.set(key, { count: 1, resetAt });
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
const newCount = data.count + 1;
|
|
118
|
+
this.set(key, { count: newCount, resetAt: data.resetAt });
|
|
119
|
+
return newCount;
|
|
120
|
+
},
|
|
121
|
+
// ==================== 配置管理 ====================
|
|
122
|
+
/**
|
|
123
|
+
* 获取网关配置
|
|
124
|
+
*/
|
|
125
|
+
getConfig(key, defaultValue) {
|
|
126
|
+
const value = this.get(`config:${key}`);
|
|
127
|
+
return (value ?? defaultValue);
|
|
128
|
+
},
|
|
129
|
+
/**
|
|
130
|
+
* 设置网关配置
|
|
131
|
+
*/
|
|
132
|
+
setConfig(key, value) {
|
|
133
|
+
this.set(`config:${key}`, value);
|
|
134
|
+
},
|
|
135
|
+
// ==================== 服务健康状态 ====================
|
|
136
|
+
/**
|
|
137
|
+
* 获取服务健康状态
|
|
138
|
+
*/
|
|
139
|
+
getServiceHealth(service) {
|
|
140
|
+
return this.get(`health:${service}`, {
|
|
141
|
+
healthy: true,
|
|
142
|
+
lastCheck: Date.now(),
|
|
143
|
+
failures: 0
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* 更新服务健康状态
|
|
148
|
+
*/
|
|
149
|
+
setServiceHealth(service, healthy) {
|
|
150
|
+
const current = this.getServiceHealth(service);
|
|
151
|
+
const failures = healthy ? 0 : current.failures + 1;
|
|
152
|
+
this.set(`health:${service}`, {
|
|
153
|
+
healthy,
|
|
154
|
+
lastCheck: Date.now(),
|
|
155
|
+
failures
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
// ==================== 用户相关 ====================
|
|
159
|
+
/**
|
|
160
|
+
* 记录用户最后活跃时间
|
|
161
|
+
*/
|
|
162
|
+
setUserLastActive(userId) {
|
|
163
|
+
this.set(`user:${userId}:lastActive`, Date.now());
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* 获取用户最后活跃时间
|
|
167
|
+
*/
|
|
168
|
+
getUserLastActive(userId) {
|
|
169
|
+
const value = this.get(`user:${userId}:lastActive`);
|
|
170
|
+
return (value ?? 0);
|
|
171
|
+
},
|
|
172
|
+
// ==================== 模型 API Key 管理 ====================
|
|
173
|
+
/**
|
|
174
|
+
* 生成 API Key 存储 Key
|
|
175
|
+
* 格式: apikey:{modelId} (雪花ID已唯一)
|
|
176
|
+
*/
|
|
177
|
+
_getApiKeyKey(modelId) {
|
|
178
|
+
return `apikey:${modelId}`;
|
|
179
|
+
},
|
|
180
|
+
/**
|
|
181
|
+
* 存储模型 API Key
|
|
182
|
+
* @param modelId 模型ID(雪花ID,唯一)
|
|
183
|
+
* @param apiKey API Key 值
|
|
184
|
+
*/
|
|
185
|
+
setModelApiKey(modelId, apiKey) {
|
|
186
|
+
const key = this._getApiKeyKey(modelId);
|
|
187
|
+
this.set(key, {
|
|
188
|
+
apiKey,
|
|
189
|
+
updatedAt: Date.now()
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
/**
|
|
193
|
+
* 获取模型 API Key
|
|
194
|
+
* @param modelId 模型ID(雪花ID,唯一)
|
|
195
|
+
* @returns API Key 或 null
|
|
196
|
+
*/
|
|
197
|
+
getModelApiKey(modelId) {
|
|
198
|
+
const key = this._getApiKeyKey(modelId);
|
|
199
|
+
const data = this.get(key);
|
|
200
|
+
logger.debug('PersistStore', `Getting API Key for modelId: ${modelId}, found: ${data ? 'yes' : 'no'}`);
|
|
201
|
+
return data?.apiKey ?? null;
|
|
202
|
+
},
|
|
203
|
+
/**
|
|
204
|
+
* 删除模型 API Key
|
|
205
|
+
* @param modelId 模型ID
|
|
206
|
+
*/
|
|
207
|
+
deleteModelApiKey(modelId) {
|
|
208
|
+
const key = this._getApiKeyKey(modelId);
|
|
209
|
+
this.delete(key);
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* 检查模型 API Key 是否存在
|
|
213
|
+
* @param modelId 模型ID
|
|
214
|
+
*/
|
|
215
|
+
hasModelApiKey(modelId) {
|
|
216
|
+
const key = this._getApiKeyKey(modelId);
|
|
217
|
+
return this.has(key);
|
|
218
|
+
},
|
|
219
|
+
// ==================== 技能 API Key 管理 ====================
|
|
220
|
+
/**
|
|
221
|
+
* 存储技能 API Key(本地存储,不上传服务端)
|
|
222
|
+
* @param skillId 技能ID(雪花ID,唯一)
|
|
223
|
+
* @param apiKey API Key 值
|
|
224
|
+
* @param apiBaseUrl 可选的 API Base URL
|
|
225
|
+
*/
|
|
226
|
+
setSkillApiKey(skillId, apiKey) {
|
|
227
|
+
const key = `skill_apikey:${skillId}`;
|
|
228
|
+
this.set(key, {
|
|
229
|
+
apiKey,
|
|
230
|
+
updatedAt: Date.now()
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
/**
|
|
234
|
+
* 获取技能 API Key
|
|
235
|
+
* @param skillId 技能ID
|
|
236
|
+
* @returns API Key 配置或 null
|
|
237
|
+
*/
|
|
238
|
+
getSkillApiKey(skillId) {
|
|
239
|
+
const key = `skill_apikey:${skillId}`;
|
|
240
|
+
const data = this.get(key);
|
|
241
|
+
return data ? { apiKey: data.apiKey } : null;
|
|
242
|
+
},
|
|
243
|
+
/**
|
|
244
|
+
* 删除技能 API Key
|
|
245
|
+
* @param skillId 技能ID
|
|
246
|
+
*/
|
|
247
|
+
deleteSkillApiKey(skillId) {
|
|
248
|
+
const key = `skill_apikey:${skillId}`;
|
|
249
|
+
this.delete(key);
|
|
250
|
+
},
|
|
251
|
+
/**
|
|
252
|
+
* 检查技能 API Key 是否存在
|
|
253
|
+
* @param skillId 技能ID
|
|
254
|
+
*/
|
|
255
|
+
hasSkillApiKey(skillId) {
|
|
256
|
+
const key = `skill_apikey:${skillId}`;
|
|
257
|
+
return this.has(key);
|
|
258
|
+
},
|
|
259
|
+
// ==================== 本地用户设置(不上传服务端) ====================
|
|
260
|
+
/**
|
|
261
|
+
* 获取本地用户设置
|
|
262
|
+
*/
|
|
263
|
+
getLocalUserSetting(key, defaultValue) {
|
|
264
|
+
const settings = (this.get('localUserSetting', {}) || {});
|
|
265
|
+
return settings[key] ?? (defaultValue ?? null);
|
|
266
|
+
},
|
|
267
|
+
/**
|
|
268
|
+
* 设置本地用户设置
|
|
269
|
+
*/
|
|
270
|
+
setLocalUserSetting(key, value) {
|
|
271
|
+
const settings = (this.get('localUserSetting', {}) || {});
|
|
272
|
+
settings[key] = value;
|
|
273
|
+
this.set('localUserSetting', settings);
|
|
274
|
+
},
|
|
275
|
+
/**
|
|
276
|
+
* 获取所有本地用户设置
|
|
277
|
+
*/
|
|
278
|
+
getAllLocalUserSettings() {
|
|
279
|
+
return (this.get('localUserSetting', {}) || {});
|
|
280
|
+
},
|
|
281
|
+
/**
|
|
282
|
+
* 批量设置本地用户设置
|
|
283
|
+
*/
|
|
284
|
+
setLocalUserSettings(settings) {
|
|
285
|
+
const current = (this.get('localUserSetting', {}) || {});
|
|
286
|
+
this.set('localUserSetting', { ...current, ...settings });
|
|
287
|
+
},
|
|
288
|
+
/**
|
|
289
|
+
* 删除本地用户设置
|
|
290
|
+
*/
|
|
291
|
+
deleteLocalUserSetting(key) {
|
|
292
|
+
const settings = (this.get('localUserSetting', {}) || {});
|
|
293
|
+
delete settings[key];
|
|
294
|
+
this.set('localUserSetting', settings);
|
|
295
|
+
},
|
|
296
|
+
/**
|
|
297
|
+
* 清理不活跃用户(30天未活跃)
|
|
298
|
+
*/
|
|
299
|
+
cleanupInactiveUsers() {
|
|
300
|
+
const storage = readStorage();
|
|
301
|
+
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
302
|
+
let cleaned = 0;
|
|
303
|
+
Object.keys(storage).forEach(key => {
|
|
304
|
+
if (key.startsWith('user:') && key.endsWith(':lastActive')) {
|
|
305
|
+
if (storage[key] < thirtyDaysAgo) {
|
|
306
|
+
delete storage[key];
|
|
307
|
+
cleaned++;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
if (cleaned > 0) {
|
|
312
|
+
writeStorage(storage);
|
|
313
|
+
}
|
|
314
|
+
return cleaned;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
export default persistStore;
|