befly 3.8.20 → 3.8.24

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,266 @@
1
+ /**
2
+ * DbHelper getTableColumns 方法单元测试
3
+ * 测试表字段查询、Redis 缓存、SQL 语法修复等功能
4
+ */
5
+
6
+ import { test, expect, mock, beforeEach, afterEach } from 'bun:test';
7
+ import { DbHelper } from '../lib/dbHelper.js';
8
+ import { Logger } from '../lib/logger.js';
9
+
10
+ // Mock Logger
11
+ const originalLoggerError = Logger.error;
12
+ let errorLogs: string[] = [];
13
+
14
+ beforeEach(() => {
15
+ errorLogs = [];
16
+ Logger.error = mock((msg: string) => {
17
+ errorLogs.push(msg);
18
+ });
19
+ });
20
+
21
+ afterEach(() => {
22
+ Logger.error = originalLoggerError;
23
+ });
24
+
25
+ // 创建 Mock Befly 上下文
26
+ function createMockBefly(sqlMock: any, redisMock?: any) {
27
+ return {
28
+ redis: redisMock || {
29
+ getObject: mock(async () => null),
30
+ setObject: mock(async () => true),
31
+ del: mock(async () => 1)
32
+ },
33
+ db: null
34
+ };
35
+ }
36
+
37
+ test('getTableColumns - 正常查询表字段', async () => {
38
+ const mockColumns = [{ Field: 'id' }, { Field: 'username' }, { Field: 'email' }, { Field: 'created_at' }];
39
+
40
+ const sqlMock = {
41
+ unsafe: mock(async (sql: string) => {
42
+ // 验证 SQL 语法正确(使用反引号)
43
+ expect(sql).toBe('SHOW COLUMNS FROM `users`');
44
+ return mockColumns;
45
+ })
46
+ };
47
+
48
+ const redisMock = {
49
+ getObject: mock(async () => null), // 缓存未命中
50
+ setObject: mock(async () => true),
51
+ del: mock(async () => 1)
52
+ };
53
+
54
+ const befly = createMockBefly(sqlMock, redisMock);
55
+ const dbHelper = new DbHelper(befly as any, sqlMock);
56
+
57
+ const columns = await (dbHelper as any).getTableColumns('users');
58
+
59
+ expect(columns).toEqual(['id', 'username', 'email', 'created_at']);
60
+ expect(sqlMock.unsafe).toHaveBeenCalledTimes(1);
61
+ expect(redisMock.getObject).toHaveBeenCalledWith('table:columns:users');
62
+ expect(redisMock.setObject).toHaveBeenCalled();
63
+ });
64
+
65
+ test('getTableColumns - Redis 缓存命中', async () => {
66
+ const cachedColumns = ['id', 'name', 'email'];
67
+ const redisMock = {
68
+ getObject: mock(async () => cachedColumns),
69
+ setObject: mock(async () => true),
70
+ del: mock(async () => 1)
71
+ };
72
+
73
+ const sqlMock = {
74
+ unsafe: mock(async () => {
75
+ throw new Error('不应该执行 SQL 查询');
76
+ })
77
+ };
78
+
79
+ const befly = createMockBefly(sqlMock, redisMock);
80
+ const dbHelper = new DbHelper(befly as any, sqlMock);
81
+
82
+ const columns = await (dbHelper as any).getTableColumns('users');
83
+
84
+ expect(columns).toEqual(cachedColumns);
85
+ expect(redisMock.getObject).toHaveBeenCalledWith('table:columns:users');
86
+ expect(sqlMock.unsafe).not.toHaveBeenCalled(); // SQL 不应该被调用
87
+ expect(redisMock.setObject).not.toHaveBeenCalled(); // 不需要写缓存
88
+ });
89
+
90
+ test('getTableColumns - 表不存在错误', async () => {
91
+ const sqlMock = {
92
+ unsafe: mock(async () => []) // 返回空结果
93
+ };
94
+
95
+ const redisMock = {
96
+ getObject: mock(async () => null),
97
+ setObject: mock(async () => true),
98
+ del: mock(async () => 1)
99
+ };
100
+
101
+ const befly = createMockBefly(sqlMock, redisMock);
102
+ const dbHelper = new DbHelper(befly as any, sqlMock);
103
+
104
+ try {
105
+ await (dbHelper as any).getTableColumns('non_existent_table');
106
+ expect(true).toBe(false); // 不应该执行到这里
107
+ } catch (error: any) {
108
+ expect(error.message).toContain('表 non_existent_table 不存在或没有字段');
109
+ }
110
+ });
111
+
112
+ test('getTableColumns - SQL 语法使用反引号', async () => {
113
+ const mockColumns = [{ Field: 'id' }];
114
+ let capturedSql = '';
115
+
116
+ const sqlMock = {
117
+ unsafe: mock(async (sql: string) => {
118
+ capturedSql = sql;
119
+ return mockColumns;
120
+ })
121
+ };
122
+
123
+ const befly = createMockBefly(sqlMock);
124
+ const dbHelper = new DbHelper(befly as any, sqlMock);
125
+
126
+ await (dbHelper as any).getTableColumns('addon_admin_user');
127
+
128
+ // 验证 SQL 语法
129
+ expect(capturedSql).toBe('SHOW COLUMNS FROM `addon_admin_user`');
130
+ expect(capturedSql).not.toContain('??'); // 不应该包含占位符
131
+ expect(capturedSql).toContain('`'); // 应该使用反引号
132
+ });
133
+
134
+ test('getTableColumns - 表名特殊字符处理', async () => {
135
+ const mockColumns = [{ Field: 'id' }];
136
+ const sqlMock = {
137
+ unsafe: mock(async () => mockColumns)
138
+ };
139
+
140
+ const befly = createMockBefly(sqlMock);
141
+ const dbHelper = new DbHelper(befly as any, sqlMock);
142
+
143
+ // 测试下划线表名
144
+ await (dbHelper as any).getTableColumns('addon_admin_user');
145
+ expect(sqlMock.unsafe).toHaveBeenLastCalledWith('SHOW COLUMNS FROM `addon_admin_user`');
146
+
147
+ // 测试普通表名
148
+ await (dbHelper as any).getTableColumns('users');
149
+ expect(sqlMock.unsafe).toHaveBeenLastCalledWith('SHOW COLUMNS FROM `users`');
150
+ });
151
+
152
+ test('getTableColumns - 缓存键格式正确', async () => {
153
+ const mockColumns = [{ Field: 'id' }];
154
+ const sqlMock = {
155
+ unsafe: mock(async () => mockColumns)
156
+ };
157
+
158
+ const redisMock = {
159
+ getObject: mock(async () => null),
160
+ setObject: mock(async (key: string, value: any, seconds: number) => {
161
+ // 验证缓存键格式
162
+ expect(key).toBe('table:columns:test_table');
163
+ // 验证缓存值格式
164
+ expect(Array.isArray(value)).toBe(true);
165
+ // 验证过期时间
166
+ expect(seconds).toBe(3600); // 1 小时
167
+ return true;
168
+ }),
169
+ del: mock(async () => 1)
170
+ };
171
+
172
+ const befly = createMockBefly(sqlMock, redisMock);
173
+ const dbHelper = new DbHelper(befly as any, sqlMock);
174
+
175
+ await (dbHelper as any).getTableColumns('test_table');
176
+
177
+ expect(redisMock.setObject).toHaveBeenCalled();
178
+ });
179
+
180
+ test('getTableColumns - 多次调用相同表(缓存效果)', async () => {
181
+ const mockColumns = [{ Field: 'id' }, { Field: 'name' }];
182
+ let sqlCallCount = 0;
183
+
184
+ const sqlMock = {
185
+ unsafe: mock(async () => {
186
+ sqlCallCount++;
187
+ return mockColumns;
188
+ })
189
+ };
190
+
191
+ const cache: Record<string, any> = {};
192
+ const redisMock = {
193
+ getObject: mock(async (key: string) => cache[key] || null),
194
+ setObject: mock(async (key: string, value: any) => {
195
+ cache[key] = value;
196
+ return true;
197
+ }),
198
+ del: mock(async () => 1)
199
+ };
200
+
201
+ const befly = createMockBefly(sqlMock, redisMock);
202
+ const dbHelper = new DbHelper(befly as any, sqlMock);
203
+
204
+ // 第一次调用 - 应该查询数据库
205
+ const columns1 = await (dbHelper as any).getTableColumns('users');
206
+ expect(columns1).toEqual(['id', 'name']);
207
+ expect(sqlCallCount).toBe(1);
208
+
209
+ // 第二次调用 - 应该从缓存读取
210
+ const columns2 = await (dbHelper as any).getTableColumns('users');
211
+ expect(columns2).toEqual(['id', 'name']);
212
+ expect(sqlCallCount).toBe(1); // SQL 调用次数不变
213
+
214
+ // 第三次调用 - 仍然从缓存读取
215
+ const columns3 = await (dbHelper as any).getTableColumns('users');
216
+ expect(columns3).toEqual(['id', 'name']);
217
+ expect(sqlCallCount).toBe(1);
218
+ });
219
+
220
+ test('getTableColumns - Redis 错误处理', async () => {
221
+ const mockColumns = [{ Field: 'id' }];
222
+ const sqlMock = {
223
+ unsafe: mock(async () => mockColumns)
224
+ };
225
+
226
+ const redisMock = {
227
+ getObject: mock(async () => {
228
+ throw new Error('Redis 连接失败');
229
+ }),
230
+ setObject: mock(async () => true),
231
+ del: mock(async () => 1)
232
+ };
233
+
234
+ const befly = createMockBefly(sqlMock, redisMock);
235
+ const dbHelper = new DbHelper(befly as any, sqlMock);
236
+
237
+ try {
238
+ await (dbHelper as any).getTableColumns('users');
239
+ expect(true).toBe(false); // 不应该执行到这里
240
+ } catch (error: any) {
241
+ expect(error.message).toContain('Redis 连接失败');
242
+ }
243
+ });
244
+
245
+ test('getTableColumns - 字段映射正确性', async () => {
246
+ const mockColumns = [
247
+ { Field: 'id', Type: 'int' },
248
+ { Field: 'user_name', Type: 'varchar' },
249
+ { Field: 'created_at', Type: 'timestamp' },
250
+ { Field: 'is_active', Type: 'tinyint' }
251
+ ];
252
+
253
+ const sqlMock = {
254
+ unsafe: mock(async () => mockColumns)
255
+ };
256
+
257
+ const befly = createMockBefly(sqlMock);
258
+ const dbHelper = new DbHelper(befly as any, sqlMock);
259
+
260
+ const columns = await (dbHelper as any).getTableColumns('users');
261
+
262
+ // 验证只提取 Field 字段
263
+ expect(columns).toEqual(['id', 'user_name', 'created_at', 'is_active']);
264
+ expect(columns.length).toBe(4);
265
+ expect(columns.every((col: string) => typeof col === 'string')).toBe(true);
266
+ });
@@ -0,0 +1,240 @@
1
+ /**
2
+ * DbHelper executeWithConn 方法单元测试
3
+ * 测试 SQL 执行、错误处理、慢查询日志等功能
4
+ */
5
+
6
+ import { test, expect, mock, beforeEach, afterEach } from 'bun:test';
7
+ import { DbHelper } from '../lib/dbHelper.js';
8
+ import { Logger } from '../lib/logger.js';
9
+
10
+ // Mock Logger
11
+ const originalLoggerError = Logger.error;
12
+ const originalLoggerWarn = Logger.warn;
13
+ let errorLogs: string[] = [];
14
+ let warnLogs: string[] = [];
15
+
16
+ beforeEach(() => {
17
+ errorLogs = [];
18
+ warnLogs = [];
19
+ Logger.error = mock((msg: string) => {
20
+ errorLogs.push(msg);
21
+ });
22
+ Logger.warn = mock((msg: string) => {
23
+ warnLogs.push(msg);
24
+ });
25
+ });
26
+
27
+ afterEach(() => {
28
+ Logger.error = originalLoggerError;
29
+ Logger.warn = originalLoggerWarn;
30
+ });
31
+
32
+ // 创建 Mock Befly 上下文
33
+ function createMockBefly(sqlMock: any) {
34
+ return {
35
+ redis: {
36
+ get: mock(async () => null),
37
+ set: mock(async () => true),
38
+ del: mock(async () => 1)
39
+ },
40
+ db: null
41
+ };
42
+ }
43
+
44
+ test('executeWithConn - 正常执行(无参数)', async () => {
45
+ const mockResult = [{ id: 1, name: 'test' }];
46
+ const sqlMock = {
47
+ unsafe: mock(async () => mockResult)
48
+ };
49
+
50
+ const befly = createMockBefly(sqlMock);
51
+ const dbHelper = new DbHelper(befly as any, sqlMock);
52
+
53
+ // 使用反射访问私有方法
54
+ const result = await (dbHelper as any).executeWithConn('SELECT * FROM users');
55
+
56
+ expect(result).toEqual(mockResult);
57
+ expect(sqlMock.unsafe).toHaveBeenCalledWith('SELECT * FROM users');
58
+ expect(errorLogs.length).toBe(0);
59
+ });
60
+
61
+ test('executeWithConn - 正常执行(带参数)', async () => {
62
+ const mockResult = [{ id: 1, email: 'test@example.com' }];
63
+ const sqlMock = {
64
+ unsafe: mock(async () => mockResult)
65
+ };
66
+
67
+ const befly = createMockBefly(sqlMock);
68
+ const dbHelper = new DbHelper(befly as any, sqlMock);
69
+
70
+ const result = await (dbHelper as any).executeWithConn('SELECT * FROM users WHERE id = ?', [1]);
71
+
72
+ expect(result).toEqual(mockResult);
73
+ expect(sqlMock.unsafe).toHaveBeenCalledWith('SELECT * FROM users WHERE id = ?', [1]);
74
+ expect(errorLogs.length).toBe(0);
75
+ });
76
+
77
+ test('executeWithConn - SQL 错误捕获', async () => {
78
+ const sqlError = new Error('You have an error in your SQL syntax');
79
+ const sqlMock = {
80
+ unsafe: mock(async () => {
81
+ throw sqlError;
82
+ })
83
+ };
84
+
85
+ const befly = createMockBefly(sqlMock);
86
+ const dbHelper = new DbHelper(befly as any, sqlMock);
87
+
88
+ try {
89
+ await (dbHelper as any).executeWithConn('SELECT * FROM invalid_table');
90
+ expect(true).toBe(false); // 不应该执行到这里
91
+ } catch (error: any) {
92
+ // 验证错误信息
93
+ expect(error.message).toContain('SQL执行失败');
94
+ expect(error.originalError).toBe(sqlError);
95
+ expect(error.sql).toBe('SELECT * FROM invalid_table');
96
+ expect(error.params).toEqual([]);
97
+ expect(error.duration).toBeGreaterThanOrEqual(0);
98
+
99
+ // 验证错误日志
100
+ expect(errorLogs.length).toBeGreaterThan(0);
101
+ expect(errorLogs.some((log) => log.includes('SQL 执行错误'))).toBe(true);
102
+ expect(errorLogs.some((log) => log.includes('SELECT * FROM invalid_table'))).toBe(true);
103
+ expect(errorLogs.some((log) => log.includes('You have an error in your SQL syntax'))).toBe(true);
104
+ }
105
+ });
106
+
107
+ test('executeWithConn - 错误日志包含完整信息', async () => {
108
+ const sqlMock = {
109
+ unsafe: mock(async () => {
110
+ throw new Error('Syntax error near "??"');
111
+ })
112
+ };
113
+
114
+ const befly = createMockBefly(sqlMock);
115
+ const dbHelper = new DbHelper(befly as any, sqlMock);
116
+
117
+ const testSql = 'SHOW COLUMNS FROM ??';
118
+ const testParams = ['users'];
119
+
120
+ try {
121
+ await (dbHelper as any).executeWithConn(testSql, testParams);
122
+ } catch (error: any) {
123
+ // 验证增强的错误对象
124
+ expect(error.sql).toBe(testSql);
125
+ expect(error.params).toEqual(testParams);
126
+ expect(typeof error.duration).toBe('number');
127
+
128
+ // 验证日志内容
129
+ const allLogs = errorLogs.join('\n');
130
+ expect(allLogs).toContain('SQL 语句:');
131
+ expect(allLogs).toContain('SHOW COLUMNS FROM ??');
132
+ expect(allLogs).toContain('参数列表:');
133
+ expect(allLogs).toContain('["users"]');
134
+ expect(allLogs).toContain('执行耗时:');
135
+ expect(allLogs).toContain('错误信息:');
136
+ expect(allLogs).toContain('Syntax error near "??"');
137
+ }
138
+ });
139
+
140
+ test('executeWithConn - 超长 SQL 截断', async () => {
141
+ const longSql = 'SELECT * FROM users WHERE ' + 'id = ? AND '.repeat(50) + 'name = ?';
142
+ const sqlMock = {
143
+ unsafe: mock(async () => {
144
+ throw new Error('Test error');
145
+ })
146
+ };
147
+
148
+ const befly = createMockBefly(sqlMock);
149
+ const dbHelper = new DbHelper(befly as any, sqlMock);
150
+
151
+ try {
152
+ await (dbHelper as any).executeWithConn(longSql);
153
+ } catch (error: any) {
154
+ // SQL 应该被截断
155
+ expect(error.sql).toBe(longSql); // 完整保存在错误对象中
156
+
157
+ // 日志中应该截断并加 ...
158
+ const sqlLog = errorLogs.find((log) => log.includes('SQL 语句:'));
159
+ expect(sqlLog).toBeDefined();
160
+ if (sqlLog) {
161
+ expect(sqlLog.length).toBeLessThan(longSql.length + 50); // 截断后应该更短
162
+ expect(sqlLog).toContain('...');
163
+ }
164
+ }
165
+ });
166
+
167
+ test('executeWithConn - 慢查询日志(>1000ms)', async () => {
168
+ const mockResult = [{ id: 1 }];
169
+ const sqlMock = {
170
+ unsafe: mock(async () => {
171
+ // 模拟慢查询
172
+ await new Promise((resolve) => setTimeout(resolve, 1100));
173
+ return mockResult;
174
+ })
175
+ };
176
+
177
+ const befly = createMockBefly(sqlMock);
178
+ const dbHelper = new DbHelper(befly as any, sqlMock);
179
+
180
+ const result = await (dbHelper as any).executeWithConn('SELECT SLEEP(1)');
181
+
182
+ expect(result).toEqual(mockResult);
183
+ expect(warnLogs.length).toBeGreaterThan(0);
184
+ expect(warnLogs.some((log) => log.includes('🐌 检测到慢查询'))).toBe(true);
185
+ expect(warnLogs.some((log) => log.includes('ms'))).toBe(true);
186
+ });
187
+
188
+ test('executeWithConn - 数据库未连接错误', async () => {
189
+ const befly = createMockBefly(null);
190
+ const dbHelper = new DbHelper(befly as any, null); // 没有 sql 实例
191
+
192
+ try {
193
+ await (dbHelper as any).executeWithConn('SELECT * FROM users');
194
+ expect(true).toBe(false); // 不应该执行到这里
195
+ } catch (error: any) {
196
+ expect(error.message).toBe('数据库连接未初始化');
197
+ }
198
+ });
199
+
200
+ test('executeWithConn - 空参数数组', async () => {
201
+ const mockResult = [{ count: 10 }];
202
+ const sqlMock = {
203
+ unsafe: mock(async () => mockResult)
204
+ };
205
+
206
+ const befly = createMockBefly(sqlMock);
207
+ const dbHelper = new DbHelper(befly as any, sqlMock);
208
+
209
+ const result = await (dbHelper as any).executeWithConn('SELECT COUNT(*) as count FROM users', []);
210
+
211
+ expect(result).toEqual(mockResult);
212
+ // 空数组应该走 else 分支(不传参数)
213
+ expect(sqlMock.unsafe).toHaveBeenCalledWith('SELECT COUNT(*) as count FROM users');
214
+ });
215
+
216
+ test('executeWithConn - 参数 JSON 序列化', async () => {
217
+ const sqlMock = {
218
+ unsafe: mock(async () => {
219
+ throw new Error('Test error');
220
+ })
221
+ };
222
+
223
+ const befly = createMockBefly(sqlMock);
224
+ const dbHelper = new DbHelper(befly as any, sqlMock);
225
+
226
+ const complexParams = [1, 'test', { nested: 'object' }, [1, 2, 3], null, undefined];
227
+
228
+ try {
229
+ await (dbHelper as any).executeWithConn('SELECT ?', complexParams);
230
+ } catch (error: any) {
231
+ // 验证参数被正确序列化
232
+ const paramsLog = errorLogs.find((log) => log.includes('参数列表:'));
233
+ expect(paramsLog).toBeDefined();
234
+ if (paramsLog) {
235
+ // JSON.stringify 应该能处理复杂参数
236
+ expect(paramsLog).toContain('参数列表:');
237
+ expect(() => JSON.parse(paramsLog.split('参数列表:')[1].trim())).not.toThrow();
238
+ }
239
+ }
240
+ });
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 验证 Redis 缓存的字段查询功能
3
+ */
4
+
5
+ console.log('\n========== Redis 缓存验证 ==========\n');
6
+
7
+ // 模拟 Redis 缓存逻辑
8
+ class MockRedis {
9
+ private cache: Map<string, { value: any; expire: number }> = new Map();
10
+
11
+ async getObject<T>(key: string): Promise<T | null> {
12
+ const cached = this.cache.get(key);
13
+ if (cached && cached.expire > Date.now()) {
14
+ console.log(`✅ Redis 缓存命中: ${key}`);
15
+ return cached.value as T;
16
+ }
17
+ console.log(`❌ Redis 缓存未命中: ${key}`);
18
+ return null;
19
+ }
20
+
21
+ async setObject(key: string, value: any, ttl: number): Promise<void> {
22
+ this.cache.set(key, {
23
+ value: value,
24
+ expire: Date.now() + ttl * 1000
25
+ });
26
+ console.log(`📝 写入 Redis 缓存: ${key} (TTL: ${ttl}s)`);
27
+ }
28
+ }
29
+
30
+ // 模拟数据库查询
31
+ async function queryDatabase(table: string): Promise<string[]> {
32
+ console.log(`🔍 查询数据库表结构: ${table}`);
33
+ // 模拟数据库延迟
34
+ await new Promise((resolve) => setTimeout(resolve, 3));
35
+ return ['id', 'name', 'email', 'password', 'salt', 'created_at'];
36
+ }
37
+
38
+ // 模拟 getTableColumns 方法
39
+ async function getTableColumns(redis: MockRedis, table: string): Promise<string[]> {
40
+ // 1. 先查 Redis 缓存
41
+ const cacheKey = `table:columns:${table}`;
42
+ let columns = await redis.getObject<string[]>(cacheKey);
43
+
44
+ if (columns && columns.length > 0) {
45
+ return columns;
46
+ }
47
+
48
+ // 2. 缓存未命中,查询数据库
49
+ columns = await queryDatabase(table);
50
+
51
+ // 3. 写入 Redis 缓存(1小时过期)
52
+ await redis.setObject(cacheKey, columns, 3600);
53
+
54
+ return columns;
55
+ }
56
+
57
+ async function test() {
58
+ const redis = new MockRedis();
59
+
60
+ console.log('【场景1】单进程多次查询\n');
61
+
62
+ // 第1次查询(缓存未命中)
63
+ console.log('--- 第1次查询 user 表 ---');
64
+ const start1 = Date.now();
65
+ const columns1 = await getTableColumns(redis, 'user');
66
+ const time1 = Date.now() - start1;
67
+ console.log(`结果: ${columns1.join(', ')}`);
68
+ console.log(`耗时: ${time1}ms\n`);
69
+
70
+ // 第2次查询(缓存命中)
71
+ console.log('--- 第2次查询 user 表 ---');
72
+ const start2 = Date.now();
73
+ const columns2 = await getTableColumns(redis, 'user');
74
+ const time2 = Date.now() - start2;
75
+ console.log(`结果: ${columns2.join(', ')}`);
76
+ console.log(`耗时: ${time2}ms\n`);
77
+
78
+ // 第3次查询(缓存命中)
79
+ console.log('--- 第3次查询 user 表 ---');
80
+ const start3 = Date.now();
81
+ const columns3 = await getTableColumns(redis, 'user');
82
+ const time3 = Date.now() - start3;
83
+ console.log(`结果: ${columns3.join(', ')}`);
84
+ console.log(`耗时: ${time3}ms\n`);
85
+
86
+ console.log('【场景2】模拟 PM2 cluster(多进程共享 Redis)\n');
87
+
88
+ // 模拟 Worker 1 查询
89
+ console.log('--- Worker 1 查询 article 表 ---');
90
+ const worker1Start = Date.now();
91
+ const worker1Columns = await getTableColumns(redis, 'article');
92
+ const worker1Time = Date.now() - worker1Start;
93
+ console.log(`结果: ${worker1Columns.join(', ')}`);
94
+ console.log(`耗时: ${worker1Time}ms\n`);
95
+
96
+ // 模拟 Worker 2 查询(共享 Redis 缓存)
97
+ console.log('--- Worker 2 查询 article 表 ---');
98
+ const worker2Start = Date.now();
99
+ const worker2Columns = await getTableColumns(redis, 'article');
100
+ const worker2Time = Date.now() - worker2Start;
101
+ console.log(`结果: ${worker2Columns.join(', ')}`);
102
+ console.log(`耗时: ${worker2Time}ms`);
103
+ console.log(`✅ Worker 2 直接使用 Worker 1 的缓存,无需再查数据库\n`);
104
+
105
+ // 模拟 Worker 3 查询(共享 Redis 缓存)
106
+ console.log('--- Worker 3 查询 article 表 ---');
107
+ const worker3Start = Date.now();
108
+ const worker3Columns = await getTableColumns(redis, 'article');
109
+ const worker3Time = Date.now() - worker3Start;
110
+ console.log(`结果: ${worker3Columns.join(', ')}`);
111
+ console.log(`耗时: ${worker3Time}ms`);
112
+ console.log(`✅ Worker 3 直接使用 Worker 1 的缓存,无需再查数据库\n`);
113
+
114
+ console.log('========== 验证完成 ==========\n');
115
+
116
+ console.log('📊 性能总结:');
117
+ console.log(`- 首次查询(数据库): ${time1}ms`);
118
+ console.log(`- 后续查询(Redis): ${time2}ms`);
119
+ console.log(`- 性能提升: ${(time1 / time2).toFixed(1)}x`);
120
+ console.log(`- PM2 cluster: ✅ 所有 worker 共享同一份 Redis 缓存`);
121
+ }
122
+
123
+ test();