befly 2.3.3 → 3.0.1

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.
Files changed (87) hide show
  1. package/checks/conflict.ts +329 -0
  2. package/checks/table.ts +252 -0
  3. package/config/env.ts +218 -0
  4. package/config/fields.ts +55 -0
  5. package/config/regexAliases.ts +51 -0
  6. package/config/reserved.ts +96 -0
  7. package/main.ts +47 -0
  8. package/package.json +26 -11
  9. package/plugins/db.ts +60 -0
  10. package/plugins/logger.ts +28 -0
  11. package/plugins/redis.ts +47 -0
  12. package/scripts/syncDb/apply.ts +171 -0
  13. package/scripts/syncDb/constants.ts +71 -0
  14. package/scripts/syncDb/ddl.ts +189 -0
  15. package/scripts/syncDb/helpers.ts +173 -0
  16. package/scripts/syncDb/index.ts +203 -0
  17. package/scripts/syncDb/schema.ts +199 -0
  18. package/scripts/syncDb/sqlite.ts +50 -0
  19. package/scripts/syncDb/state.ts +106 -0
  20. package/scripts/syncDb/table.ts +214 -0
  21. package/scripts/syncDb/tableCreate.ts +148 -0
  22. package/scripts/syncDb/tests/constants.test.ts +105 -0
  23. package/scripts/syncDb/tests/ddl.test.ts +134 -0
  24. package/scripts/syncDb/tests/helpers.test.ts +70 -0
  25. package/scripts/syncDb/types.ts +92 -0
  26. package/scripts/syncDb/version.ts +73 -0
  27. package/scripts/syncDb.ts +10 -0
  28. package/tsconfig.json +58 -0
  29. package/types/addon.d.ts +53 -0
  30. package/types/api.d.ts +249 -0
  31. package/types/befly.d.ts +230 -0
  32. package/types/common.d.ts +215 -0
  33. package/types/context.d.ts +7 -0
  34. package/types/crypto.d.ts +23 -0
  35. package/types/database.d.ts +273 -0
  36. package/types/index.d.ts +450 -0
  37. package/types/index.ts +438 -0
  38. package/types/jwt.d.ts +99 -0
  39. package/types/logger.d.ts +43 -0
  40. package/types/plugin.d.ts +109 -0
  41. package/types/redis.d.ts +46 -0
  42. package/types/tool.d.ts +67 -0
  43. package/types/validator.d.ts +43 -0
  44. package/types/validator.ts +43 -0
  45. package/utils/colors.ts +221 -0
  46. package/utils/crypto.ts +308 -0
  47. package/utils/database.ts +348 -0
  48. package/utils/dbHelper.ts +713 -0
  49. package/utils/helper.ts +812 -0
  50. package/utils/index.ts +33 -0
  51. package/utils/jwt.ts +493 -0
  52. package/utils/logger.ts +191 -0
  53. package/utils/redisHelper.ts +321 -0
  54. package/utils/requestContext.ts +167 -0
  55. package/utils/sqlBuilder.ts +611 -0
  56. package/utils/validate.ts +493 -0
  57. package/utils/{xml.js → xml.ts} +100 -74
  58. package/.npmrc +0 -3
  59. package/.prettierignore +0 -2
  60. package/.prettierrc +0 -11
  61. package/apis/health/info.js +0 -49
  62. package/apis/tool/tokenCheck.js +0 -29
  63. package/bin/befly.js +0 -109
  64. package/bunfig.toml +0 -3
  65. package/checks/table.js +0 -206
  66. package/config/env.js +0 -64
  67. package/main.js +0 -579
  68. package/plugins/db.js +0 -46
  69. package/plugins/logger.js +0 -14
  70. package/plugins/redis.js +0 -32
  71. package/plugins/tool.js +0 -8
  72. package/scripts/syncDb.js +0 -752
  73. package/scripts/syncDev.js +0 -96
  74. package/system.js +0 -118
  75. package/tables/common.json +0 -16
  76. package/tables/tool.json +0 -6
  77. package/utils/api.js +0 -27
  78. package/utils/colors.js +0 -83
  79. package/utils/crypto.js +0 -260
  80. package/utils/index.js +0 -334
  81. package/utils/jwt.js +0 -387
  82. package/utils/logger.js +0 -143
  83. package/utils/redisHelper.js +0 -74
  84. package/utils/sqlBuilder.js +0 -498
  85. package/utils/sqlManager.js +0 -471
  86. package/utils/tool.js +0 -31
  87. package/utils/validate.js +0 -226
@@ -0,0 +1,191 @@
1
+ /**
2
+ * 日志系统 - TypeScript 版本
3
+ * 提供分级日志记录和文件管理功能
4
+ */
5
+
6
+ import path from 'path';
7
+ import { appendFile, stat } from 'node:fs/promises';
8
+ import { formatDate } from './index.js';
9
+ import { Colors } from './colors.js';
10
+ import { Env } from '../config/env.js';
11
+ import type { LogLevel } from '../types/common.js';
12
+
13
+ /**
14
+ * 日志消息类型
15
+ */
16
+ type LogMessage = string | number | boolean | null | undefined | Record<string, any> | any[];
17
+
18
+ /**
19
+ * 日志器类
20
+ */
21
+ export class Logger {
22
+ /** 日志目录 */
23
+ static logDir: string = Env.LOG_DIR || 'logs';
24
+
25
+ /** 单个日志文件最大大小(字节) */
26
+ static maxFileSize: number = Env.LOG_MAX_SIZE || 50 * 1024 * 1024; // 50MB
27
+
28
+ /** 当前使用的日志文件缓存 */
29
+ private static currentFiles: Map<string, string> = new Map();
30
+
31
+ /**
32
+ * 记录日志
33
+ * @param level - 日志级别
34
+ * @param message - 日志消息
35
+ */
36
+ static async log(level: LogLevel, message: LogMessage): Promise<void> {
37
+ // debug 日志特殊处理:仅当 LOG_DEBUG=1 时才记录
38
+ if (level === 'debug' && Env.LOG_DEBUG !== 1) return;
39
+
40
+ // 格式化消息
41
+ const timestamp = formatDate();
42
+ const colorMap = {
43
+ info: Colors.greenBright,
44
+ debug: Colors.cyanBright,
45
+ warn: Colors.yellowBright,
46
+ error: Colors.redBright
47
+ };
48
+
49
+ // 处理消息内容
50
+ let content = '';
51
+ if (typeof message === 'object' && message !== null && Object.keys(message).length > 0) {
52
+ content = JSON.stringify(message, null, 0).replace(/\\"/g, '"');
53
+ } else {
54
+ content = String(message);
55
+ }
56
+
57
+ // 带颜色的控制台消息
58
+ const coloredLevelStr = colorMap[level](level.toUpperCase().padStart(5));
59
+ const coloredMessage = `[${timestamp}] ${coloredLevelStr} - ${content}`;
60
+
61
+ // 控制台输出
62
+ if (Env.LOG_TO_CONSOLE === 1) {
63
+ console.log(coloredMessage);
64
+ }
65
+
66
+ // 文件输出(去除 ANSI 颜色代码)
67
+ const plainLevelStr = level.toUpperCase().padStart(5);
68
+ const plainMessage = `[${timestamp}] ${plainLevelStr} - ${content}`;
69
+ await this.writeToFile(plainMessage, level);
70
+ }
71
+
72
+ /**
73
+ * 写入日志文件
74
+ * @param message - 格式化后的消息
75
+ * @param level - 日志级别
76
+ */
77
+ static async writeToFile(message: string, level: LogLevel = 'info'): Promise<void> {
78
+ try {
79
+ // 确定文件前缀
80
+ const prefix = level === 'debug' ? 'debug' : new Date().toISOString().split('T')[0];
81
+
82
+ // 检查缓存的当前文件是否仍然可用
83
+ let currentLogFile = this.currentFiles.get(prefix);
84
+
85
+ if (currentLogFile) {
86
+ try {
87
+ const stats = await stat(currentLogFile);
88
+ if (stats.size >= this.maxFileSize) {
89
+ this.currentFiles.delete(prefix);
90
+ currentLogFile = undefined;
91
+ }
92
+ } catch {
93
+ this.currentFiles.delete(prefix);
94
+ currentLogFile = undefined;
95
+ }
96
+ }
97
+
98
+ // 查找或创建新文件
99
+ if (!currentLogFile) {
100
+ const glob = new Bun.Glob(`${prefix}.*.log`);
101
+ const files = await Array.fromAsync(glob.scan(this.logDir));
102
+
103
+ // 按索引排序并查找可用文件
104
+ const getIndex = (f: string) => parseInt(f.match(/\.(\d+)\.log$/)?.[1] || '0');
105
+ files.sort((a, b) => getIndex(a) - getIndex(b));
106
+
107
+ let foundFile = false;
108
+ for (let i = files.length - 1; i >= 0; i--) {
109
+ const filePath = path.join(this.logDir, files[i]);
110
+ try {
111
+ const stats = await stat(filePath);
112
+ if (stats.size < this.maxFileSize) {
113
+ currentLogFile = filePath;
114
+ foundFile = true;
115
+ break;
116
+ }
117
+ } catch {
118
+ continue;
119
+ }
120
+ }
121
+
122
+ // 没有可用文件,创建新文件
123
+ if (!foundFile) {
124
+ const maxIndex = files.length > 0 ? Math.max(...files.map(getIndex)) : -1;
125
+ currentLogFile = path.join(this.logDir, `${prefix}.${maxIndex + 1}.log`);
126
+ }
127
+
128
+ this.currentFiles.set(prefix, currentLogFile);
129
+ }
130
+
131
+ await appendFile(currentLogFile, message + '\n', 'utf8');
132
+ } catch (error: any) {
133
+ console.error('写入日志文件失败:', error?.message || error);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * 记录错误日志
139
+ * @param name - 错误名称/位置
140
+ * @param error - 错误对象或消息
141
+ */
142
+ static async error(name: string, error?: any): Promise<void> {
143
+ if (!error) {
144
+ return this.log('error', name);
145
+ }
146
+
147
+ // 构建错误消息
148
+ const parts = [name];
149
+ if (error?.message || error?.stack) {
150
+ if (error.message) parts.push(error.message);
151
+ if (error.stack) parts.push('\n' + error.stack);
152
+ } else {
153
+ const errorStr = typeof error === 'object' ? JSON.stringify(error) : String(error);
154
+ parts.push(errorStr);
155
+ }
156
+
157
+ await this.log('error', parts.join(' - '));
158
+ }
159
+
160
+ /**
161
+ * 记录警告日志
162
+ * @param message - 日志消息
163
+ */
164
+ static async warn(message: LogMessage): Promise<void> {
165
+ await this.log('warn', message);
166
+ }
167
+
168
+ /**
169
+ * 记录信息日志
170
+ * @param message - 日志消息
171
+ */
172
+ static async info(message: LogMessage): Promise<void> {
173
+ await this.log('info', message);
174
+ }
175
+
176
+ /**
177
+ * 记录调试日志
178
+ * 受 LOG_DEBUG 环境变量控制,仅当 LOG_DEBUG=1 时才记录
179
+ * @param message - 日志消息
180
+ */
181
+ static async debug(message: LogMessage): Promise<void> {
182
+ await this.log('debug', message);
183
+ }
184
+
185
+ /**
186
+ * 清除文件缓存
187
+ */
188
+ static clearCache(): void {
189
+ this.currentFiles.clear();
190
+ }
191
+ }
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Redis 助手 - TypeScript 版本
3
+ * 提供 Redis 操作的便捷方法
4
+ */
5
+
6
+ import { RedisClient } from 'bun';
7
+ import { Env } from '../config/env.js';
8
+ import { Logger } from './logger.js';
9
+ import { getRedis } from './database.js';
10
+
11
+ /**
12
+ * Redis 键前缀
13
+ */
14
+ const prefix = Env.REDIS_KEY_PREFIX ? `${Env.REDIS_KEY_PREFIX}:` : '';
15
+
16
+ /**
17
+ * 获取 Redis 客户端
18
+ * @returns Redis 客户端实例
19
+ * @throws 如果客户端未初始化
20
+ */
21
+ function getClient(): RedisClient {
22
+ const client = getRedis();
23
+ if (!client) {
24
+ throw new Error('Redis 客户端未初始化,请先调用 initDatabase() 或 initRedisOnly()');
25
+ }
26
+ return client;
27
+ }
28
+
29
+ /**
30
+ * Redis 助手对象
31
+ */
32
+ export const RedisHelper = {
33
+ /**
34
+ * 设置对象到 Redis
35
+ * @param key - 键名
36
+ * @param obj - 对象
37
+ * @param ttl - 过期时间(秒)
38
+ * @returns 操作结果
39
+ */
40
+ async setObject<T = any>(key: string, obj: T, ttl: number | null = null): Promise<string | null> {
41
+ try {
42
+ const client = getClient();
43
+ const data = JSON.stringify(obj);
44
+ const pkey = `${prefix}${key}`;
45
+
46
+ if (ttl) {
47
+ return await client.setEx(pkey, ttl, data);
48
+ }
49
+ return await client.set(pkey, data);
50
+ } catch (error: any) {
51
+ Logger.error('Redis setObject 错误', error);
52
+ return null;
53
+ }
54
+ },
55
+
56
+ /**
57
+ * 从 Redis 获取对象
58
+ * @param key - 键名
59
+ * @returns 对象或 null
60
+ */
61
+ async getObject<T = any>(key: string): Promise<T | null> {
62
+ try {
63
+ const client = getClient();
64
+ const pkey = `${prefix}${key}`;
65
+ const data = await client.get(pkey);
66
+ return data ? JSON.parse(data) : null;
67
+ } catch (error: any) {
68
+ Logger.error('Redis getObject 错误', error);
69
+ return null;
70
+ }
71
+ },
72
+
73
+ /**
74
+ * 从 Redis 删除对象
75
+ * @param key - 键名
76
+ */
77
+ async delObject(key: string): Promise<void> {
78
+ try {
79
+ const client = getClient();
80
+ const pkey = `${prefix}${key}`;
81
+ await client.del(pkey);
82
+ } catch (error: any) {
83
+ Logger.error('Redis delObject 错误', error);
84
+ }
85
+ },
86
+
87
+ /**
88
+ * 生成基于时间的唯一 ID
89
+ * 格式: 秒级时间戳(10位) + 4位自增 = 14位纯数字
90
+ * 容量: 10000/秒 = 864,000,000/天
91
+ * 范围: 到 2286年9月
92
+ * @returns 唯一 ID (14位纯数字)
93
+ */
94
+ async genTimeID(): Promise<number> {
95
+ const client = getClient();
96
+ const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
97
+ const key = `${prefix}time_id_counter:${timestamp}`;
98
+
99
+ const counter = await client.incr(key);
100
+ await client.expire(key, 1);
101
+
102
+ const counterSuffix = (counter % 10000).toString().padStart(4, '0');
103
+
104
+ return Number(`${timestamp}${counterSuffix}`);
105
+ },
106
+
107
+ /**
108
+ * 批量生成基于时间的唯一 ID
109
+ * 格式: 秒级时间戳(10位) + 4位自增 = 14位纯数字
110
+ * @param count - 需要生成的 ID 数量
111
+ * @returns ID 数组 (14位纯数字)
112
+ */
113
+ async genTimeIDBatch(count: number): Promise<number[]> {
114
+ if (count <= 0) {
115
+ return [];
116
+ }
117
+
118
+ // 限制单次批量生成数量(每秒最多10000个)
119
+ const MAX_BATCH_SIZE = 10000;
120
+ if (count > MAX_BATCH_SIZE) {
121
+ throw new Error(`批量大小 ${count} 超过最大限制 ${MAX_BATCH_SIZE}`);
122
+ }
123
+
124
+ const client = getClient();
125
+ const timestamp = Math.floor(Date.now() / 1000); // 秒级时间戳
126
+ const key = `${prefix}time_id_counter:${timestamp}`;
127
+
128
+ // 使用 INCRBY 一次性获取 N 个连续计数
129
+ const startCounter = await client.incrBy(key, count);
130
+ await client.expire(key, 1);
131
+
132
+ // 生成 ID 数组
133
+ const ids: number[] = [];
134
+ for (let i = 0; i < count; i++) {
135
+ const counter = startCounter - count + i + 1; // 计算每个 ID 的计数值
136
+ const counterSuffix = (counter % 10000).toString().padStart(4, '0');
137
+ ids.push(Number(`${timestamp}${counterSuffix}`));
138
+ }
139
+
140
+ return ids;
141
+ },
142
+
143
+ /**
144
+ * 设置字符串值
145
+ * @param key - 键名
146
+ * @param value - 值
147
+ * @param ttl - 过期时间(秒)
148
+ */
149
+ async setString(key: string, value: string, ttl: number | null = null): Promise<string | null> {
150
+ try {
151
+ const client = getClient();
152
+ const pkey = `${prefix}${key}`;
153
+ if (ttl) {
154
+ return await client.setEx(pkey, ttl, value);
155
+ }
156
+ return await client.set(pkey, value);
157
+ } catch (error: any) {
158
+ Logger.error('Redis setString 错误', error);
159
+ return null;
160
+ }
161
+ },
162
+
163
+ /**
164
+ * 获取字符串值
165
+ * @param key - 键名
166
+ */
167
+ async getString(key: string): Promise<string | null> {
168
+ try {
169
+ const client = getClient();
170
+ const pkey = `${prefix}${key}`;
171
+ return await client.get(pkey);
172
+ } catch (error: any) {
173
+ Logger.error('Redis getString 错误', error);
174
+ return null;
175
+ }
176
+ },
177
+
178
+ /**
179
+ * 检查键是否存在
180
+ * @param key - 键名
181
+ */
182
+ async exists(key: string): Promise<number> {
183
+ try {
184
+ const client = getClient();
185
+ const pkey = `${prefix}${key}`;
186
+ return await client.exists(pkey);
187
+ } catch (error: any) {
188
+ Logger.error('Redis exists 错误', error);
189
+ return 0;
190
+ }
191
+ },
192
+
193
+ /**
194
+ * 设置过期时间
195
+ * @param key - 键名
196
+ * @param seconds - 秒数
197
+ */
198
+ async expire(key: string, seconds: number): Promise<number> {
199
+ try {
200
+ const client = getClient();
201
+ const pkey = `${prefix}${key}`;
202
+ return await client.expire(pkey, seconds);
203
+ } catch (error: any) {
204
+ Logger.error('Redis expire 错误', error);
205
+ return 0;
206
+ }
207
+ },
208
+
209
+ /**
210
+ * 获取剩余过期时间
211
+ * @param key - 键名
212
+ */
213
+ async ttl(key: string): Promise<number> {
214
+ try {
215
+ const client = getClient();
216
+ const pkey = `${prefix}${key}`;
217
+ return await client.ttl(pkey);
218
+ } catch (error: any) {
219
+ Logger.error('Redis ttl 错误', error);
220
+ return -1;
221
+ }
222
+ },
223
+
224
+ /**
225
+ * 向 Set 中添加一个或多个成员
226
+ * @param key - 键名
227
+ * @param members - 成员数组
228
+ * @returns 成功添加的成员数量
229
+ */
230
+ async sadd(key: string, members: string[]): Promise<number> {
231
+ try {
232
+ if (members.length === 0) return 0;
233
+
234
+ const client = getClient();
235
+ const pkey = `${prefix}${key}`;
236
+ return await client.sadd(pkey, ...members);
237
+ } catch (error: any) {
238
+ Logger.error('Redis sadd 错误', error);
239
+ return 0;
240
+ }
241
+ },
242
+
243
+ /**
244
+ * 判断成员是否在 Set 中
245
+ * @param key - 键名
246
+ * @param member - 成员
247
+ * @returns 1 表示存在,0 表示不存在
248
+ */
249
+ async sismember(key: string, member: string): Promise<number> {
250
+ try {
251
+ const client = getClient();
252
+ const pkey = `${prefix}${key}`;
253
+ return await client.sismember(pkey, member);
254
+ } catch (error: any) {
255
+ Logger.error('Redis sismember 错误', error);
256
+ return 0;
257
+ }
258
+ },
259
+
260
+ /**
261
+ * 获取 Set 的成员数量
262
+ * @param key - 键名
263
+ * @returns 成员数量
264
+ */
265
+ async scard(key: string): Promise<number> {
266
+ try {
267
+ const client = getClient();
268
+ const pkey = `${prefix}${key}`;
269
+ return await client.scard(pkey);
270
+ } catch (error: any) {
271
+ Logger.error('Redis scard 错误', error);
272
+ return 0;
273
+ }
274
+ },
275
+
276
+ /**
277
+ * 获取 Set 的所有成员
278
+ * @param key - 键名
279
+ * @returns 成员数组
280
+ */
281
+ async smembers(key: string): Promise<string[]> {
282
+ try {
283
+ const client = getClient();
284
+ const pkey = `${prefix}${key}`;
285
+ return await client.smembers(pkey);
286
+ } catch (error: any) {
287
+ Logger.error('Redis smembers 错误', error);
288
+ return [];
289
+ }
290
+ },
291
+
292
+ /**
293
+ * 删除键
294
+ * @param key - 键名
295
+ * @returns 删除的键数量
296
+ */
297
+ async del(key: string): Promise<number> {
298
+ try {
299
+ const client = getClient();
300
+ const pkey = `${prefix}${key}`;
301
+ return await client.del(pkey);
302
+ } catch (error: any) {
303
+ Logger.error('Redis del 错误', error);
304
+ return 0;
305
+ }
306
+ },
307
+
308
+ /**
309
+ * 测试 Redis 连接
310
+ * @returns ping 响应结果
311
+ */
312
+ async ping(): Promise<string> {
313
+ try {
314
+ const client = getClient();
315
+ return await client.ping();
316
+ } catch (error: any) {
317
+ Logger.error('Redis ping 错误', error);
318
+ throw error;
319
+ }
320
+ }
321
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * 请求上下文类
3
+ * 提供统一的请求数据访问接口和类型安全
4
+ */
5
+
6
+ /**
7
+ * 标准请求上下文类
8
+ */
9
+ export class RequestContext {
10
+ /** 原始请求对象 */
11
+ public readonly request: Request;
12
+
13
+ /** 请求开始时间(毫秒) */
14
+ public readonly startTime: number;
15
+
16
+ /** 请求体参数 (GET/POST 解析后的数据) */
17
+ public body: Record<string, any> = {};
18
+
19
+ /** 用户信息 (认证后填充) */
20
+ public user: Record<string, any> = {};
21
+
22
+ /** 自定义状态 (用于在中间件间传递数据) */
23
+ public state: Record<string, any> = {};
24
+
25
+ /** 响应数据 (由 handler 设置) */
26
+ public response?: any;
27
+
28
+ constructor(req: Request) {
29
+ this.request = req;
30
+ this.startTime = Date.now();
31
+ }
32
+
33
+ // ========== 便捷访问器 ==========
34
+
35
+ /**
36
+ * 获取请求方法
37
+ */
38
+ get method(): string {
39
+ return this.request.method;
40
+ }
41
+
42
+ /**
43
+ * 获取请求 URL
44
+ */
45
+ get url(): string {
46
+ return this.request.url;
47
+ }
48
+
49
+ /**
50
+ * 获取请求头对象
51
+ */
52
+ get headers(): Headers {
53
+ return this.request.headers;
54
+ }
55
+
56
+ /**
57
+ * 获取客户端 IP 地址
58
+ */
59
+ get ip(): string | null {
60
+ return this.request.headers.get('x-forwarded-for') || this.request.headers.get('x-real-ip') || this.request.headers.get('cf-connecting-ip') || null;
61
+ }
62
+
63
+ /**
64
+ * 获取 User-Agent
65
+ */
66
+ get userAgent(): string | null {
67
+ return this.request.headers.get('user-agent');
68
+ }
69
+
70
+ /**
71
+ * 获取 Content-Type
72
+ */
73
+ get contentType(): string | null {
74
+ return this.request.headers.get('content-type');
75
+ }
76
+
77
+ // ========== 工具方法 ==========
78
+
79
+ /**
80
+ * 获取请求头
81
+ * @param name - 请求头名称
82
+ */
83
+ header(name: string): string | null {
84
+ return this.request.headers.get(name);
85
+ }
86
+
87
+ /**
88
+ * 获取参数值
89
+ * @param key - 参数键名
90
+ */
91
+ get(key: string): any {
92
+ return this.body[key];
93
+ }
94
+
95
+ /**
96
+ * 设置参数值
97
+ * @param key - 参数键名
98
+ * @param value - 参数值
99
+ */
100
+ set(key: string, value: any): void {
101
+ this.body[key] = value;
102
+ }
103
+
104
+ /**
105
+ * 检查参数是否存在
106
+ * @param key - 参数键名
107
+ */
108
+ has(key: string): boolean {
109
+ return key in this.body;
110
+ }
111
+
112
+ /**
113
+ * 获取所有参数
114
+ */
115
+ all(): Record<string, any> {
116
+ return { ...this.body };
117
+ }
118
+
119
+ /**
120
+ * 计算请求耗时(毫秒)
121
+ */
122
+ getElapsedTime(): number {
123
+ return Date.now() - this.startTime;
124
+ }
125
+
126
+ /**
127
+ * 获取格式化的请求信息(用于日志)
128
+ */
129
+ getRequestInfo(): Record<string, any> {
130
+ return {
131
+ method: this.method,
132
+ url: this.url,
133
+ ip: this.ip,
134
+ userAgent: this.userAgent,
135
+ elapsedTime: this.getElapsedTime()
136
+ };
137
+ }
138
+
139
+ /**
140
+ * 设置响应数据
141
+ * @param data - 响应数据
142
+ */
143
+ setResponse(data: any): void {
144
+ this.response = data;
145
+ }
146
+
147
+ /**
148
+ * 判断用户是否已登录
149
+ */
150
+ isAuthenticated(): boolean {
151
+ return !!this.user.id;
152
+ }
153
+
154
+ /**
155
+ * 获取用户 ID
156
+ */
157
+ getUserId(): number | null {
158
+ return this.user.id || null;
159
+ }
160
+
161
+ /**
162
+ * 获取用户角色
163
+ */
164
+ getUserRole(): string | null {
165
+ return this.user.role || this.user.roleType || null;
166
+ }
167
+ }