@taicode/common-server 1.0.9 → 1.0.11
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/output/logger/logger.d.ts +33 -0
- package/output/logger/logger.d.ts.map +1 -0
- package/output/logger/logger.js +65 -0
- package/output/logger/logger.test.d.ts +2 -0
- package/output/logger/logger.test.d.ts.map +1 -0
- package/output/logger/logger.test.js +87 -0
- package/output/redis-queue/batch-redis-queue.d.ts +136 -0
- package/output/redis-queue/batch-redis-queue.d.ts.map +1 -0
- package/output/redis-queue/batch-redis-queue.js +573 -0
- package/output/redis-queue/batch-redis-queue.test.d.ts +2 -0
- package/output/redis-queue/batch-redis-queue.test.d.ts.map +1 -0
- package/output/redis-queue/batch-redis-queue.test.js +243 -0
- package/output/redis-queue/index.d.ts +5 -0
- package/output/redis-queue/index.d.ts.map +1 -0
- package/output/redis-queue/index.js +2 -0
- package/output/redis-queue/redis-queue.d.ts +129 -0
- package/output/redis-queue/redis-queue.d.ts.map +1 -0
- package/output/redis-queue/redis-queue.js +547 -0
- package/output/redis-queue/redis-queue.test.d.ts +2 -0
- package/output/redis-queue/redis-queue.test.d.ts.map +1 -0
- package/output/redis-queue/redis-queue.test.js +234 -0
- package/output/redis-queue/types.d.ts +70 -0
- package/output/redis-queue/types.d.ts.map +1 -0
- package/output/redis-queue/types.js +1 -0
- package/package.json +9 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FastifyBaseLogger } from 'fastify';
|
|
2
|
+
import { Bindings, ChildLoggerOptions } from 'fastify/types/logger';
|
|
3
|
+
import { LevelWithSilentOrString, LogFn } from 'pino';
|
|
4
|
+
import { LoggerOptions } from '@taicode/common-base/logger';
|
|
5
|
+
export declare class FastifyLogger implements FastifyBaseLogger {
|
|
6
|
+
private logger;
|
|
7
|
+
private _msgPrefix?;
|
|
8
|
+
constructor(options?: LoggerOptions);
|
|
9
|
+
/**
|
|
10
|
+
* 创建子 logger
|
|
11
|
+
*/
|
|
12
|
+
child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger;
|
|
13
|
+
/**
|
|
14
|
+
* Pino 兼容的 level getter/setter
|
|
15
|
+
*/
|
|
16
|
+
get level(): LevelWithSilentOrString;
|
|
17
|
+
set level(newLevel: LevelWithSilentOrString);
|
|
18
|
+
/**
|
|
19
|
+
* 获取消息前缀
|
|
20
|
+
*/
|
|
21
|
+
get msgPrefix(): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* 日志方法实现
|
|
24
|
+
*/
|
|
25
|
+
fatal: LogFn;
|
|
26
|
+
error: LogFn;
|
|
27
|
+
warn: LogFn;
|
|
28
|
+
info: LogFn;
|
|
29
|
+
debug: LogFn;
|
|
30
|
+
trace: LogFn;
|
|
31
|
+
silent: LogFn;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../source/logger/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACnE,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,MAAM,CAAA;AACrD,OAAO,EAAU,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAEnE,qBAAa,aAAc,YAAW,iBAAiB;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAC,CAAQ;gBAEf,OAAO,CAAC,EAAE,aAAa;IAInC;;OAEG;IACH,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,iBAAiB;IAgB1E;;OAEG;IACH,IAAI,KAAK,IAAI,uBAAuB,CAEnC;IAED,IAAI,KAAK,CAAC,QAAQ,EAAE,uBAAuB,EAK1C;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;OAEG;IACH,KAAK,EAAE,KAAK,CAEX;IAED,KAAK,EAAE,KAAK,CAEX;IAED,IAAI,EAAE,KAAK,CAEV;IAED,IAAI,EAAE,KAAK,CAEV;IAED,KAAK,EAAE,KAAK,CAEX;IAED,KAAK,EAAE,KAAK,CAEX;IAED,MAAM,EAAE,KAAK,CAEZ;CACF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Logger } from '@taicode/common-base/logger';
|
|
2
|
+
export class FastifyLogger {
|
|
3
|
+
logger;
|
|
4
|
+
_msgPrefix;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.logger = new Logger(options);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 创建子 logger
|
|
10
|
+
*/
|
|
11
|
+
child(bindings, options) {
|
|
12
|
+
const childOptions = {
|
|
13
|
+
level: options?.level,
|
|
14
|
+
transports: this.logger.getTransports(),
|
|
15
|
+
};
|
|
16
|
+
const childLogger = new FastifyLogger(childOptions);
|
|
17
|
+
// 如果 bindings 包含 msgPrefix,保存它
|
|
18
|
+
if (bindings && typeof bindings === 'object' && 'msgPrefix' in bindings) {
|
|
19
|
+
childLogger._msgPrefix = bindings.msgPrefix;
|
|
20
|
+
}
|
|
21
|
+
return childLogger;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Pino 兼容的 level getter/setter
|
|
25
|
+
*/
|
|
26
|
+
get level() {
|
|
27
|
+
return this.logger.level;
|
|
28
|
+
}
|
|
29
|
+
set level(newLevel) {
|
|
30
|
+
// 过滤掉 'silent' 因为我们的 Logger 不支持它作为 level
|
|
31
|
+
if (newLevel !== 'silent') {
|
|
32
|
+
this.logger.level = newLevel;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 获取消息前缀
|
|
37
|
+
*/
|
|
38
|
+
get msgPrefix() {
|
|
39
|
+
return this._msgPrefix;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 日志方法实现
|
|
43
|
+
*/
|
|
44
|
+
fatal = (...args) => {
|
|
45
|
+
this.logger.fatal(...args);
|
|
46
|
+
};
|
|
47
|
+
error = (...args) => {
|
|
48
|
+
this.logger.error(...args);
|
|
49
|
+
};
|
|
50
|
+
warn = (...args) => {
|
|
51
|
+
this.logger.warn(...args);
|
|
52
|
+
};
|
|
53
|
+
info = (...args) => {
|
|
54
|
+
this.logger.info(...args);
|
|
55
|
+
};
|
|
56
|
+
debug = (...args) => {
|
|
57
|
+
this.logger.debug(...args);
|
|
58
|
+
};
|
|
59
|
+
trace = (...args) => {
|
|
60
|
+
this.logger.trace(...args);
|
|
61
|
+
};
|
|
62
|
+
silent = (...args) => {
|
|
63
|
+
this.logger.silent();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.test.d.ts","sourceRoot":"","sources":["../../source/logger/logger.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { FastifyLogger } from './logger';
|
|
3
|
+
describe('FastifyLogger', () => {
|
|
4
|
+
let printMock;
|
|
5
|
+
let loggerOptions;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
printMock = vi.fn();
|
|
8
|
+
loggerOptions = {
|
|
9
|
+
print: printMock,
|
|
10
|
+
level: 'info'
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
it('应该创建一个 FastifyLogger 实例', () => {
|
|
14
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
15
|
+
expect(logger).toBeInstanceOf(FastifyLogger);
|
|
16
|
+
});
|
|
17
|
+
it('应该支持所有日志级别方法', () => {
|
|
18
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
19
|
+
logger.trace('trace message');
|
|
20
|
+
logger.debug('debug message');
|
|
21
|
+
logger.info('info message');
|
|
22
|
+
logger.warn('warn message');
|
|
23
|
+
logger.error('error message');
|
|
24
|
+
logger.fatal('fatal message');
|
|
25
|
+
// 由于默认级别是 info,trace 和 debug 不应该被调用
|
|
26
|
+
expect(printMock).not.toHaveBeenCalledWith('trace', 'trace message');
|
|
27
|
+
expect(printMock).not.toHaveBeenCalledWith('debug', 'debug message');
|
|
28
|
+
expect(printMock).toHaveBeenCalledWith('info', 'info message');
|
|
29
|
+
expect(printMock).toHaveBeenCalledWith('warn', 'warn message');
|
|
30
|
+
expect(printMock).toHaveBeenCalledWith('error', 'error message');
|
|
31
|
+
expect(printMock).toHaveBeenCalledWith('fatal', 'fatal message');
|
|
32
|
+
});
|
|
33
|
+
it('应该支持 silent 方法', () => {
|
|
34
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
35
|
+
// silent 方法不应该抛出错误
|
|
36
|
+
expect(() => logger.silent('test')).not.toThrow();
|
|
37
|
+
// silent 方法不应该调用 print
|
|
38
|
+
expect(printMock).not.toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
it('应该支持 level getter/setter', () => {
|
|
41
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
42
|
+
expect(logger.level).toBe('info');
|
|
43
|
+
logger.level = 'warn';
|
|
44
|
+
expect(logger.level).toBe('warn');
|
|
45
|
+
// 验证新级别生效
|
|
46
|
+
printMock.mockClear();
|
|
47
|
+
logger.info('info message');
|
|
48
|
+
logger.warn('warn message');
|
|
49
|
+
expect(printMock).not.toHaveBeenCalledWith('info', 'info message');
|
|
50
|
+
expect(printMock).toHaveBeenCalledWith('warn', 'warn message');
|
|
51
|
+
});
|
|
52
|
+
it('应该忽略 silent 级别设置', () => {
|
|
53
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
54
|
+
logger.level = 'silent';
|
|
55
|
+
// silent 不是有效的 logger 级别,所以应该保持原来的级别
|
|
56
|
+
expect(logger.level).toBe('info');
|
|
57
|
+
});
|
|
58
|
+
it('应该支持创建子 logger', () => {
|
|
59
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
60
|
+
const childLogger = logger.child({ requestId: '123' });
|
|
61
|
+
expect(childLogger).toBeInstanceOf(FastifyLogger);
|
|
62
|
+
expect(childLogger).not.toBe(logger);
|
|
63
|
+
});
|
|
64
|
+
it('应该在子 logger 中保存 msgPrefix', () => {
|
|
65
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
66
|
+
const childLogger = logger.child({ msgPrefix: '[Request] ' });
|
|
67
|
+
expect(childLogger.msgPrefix).toBe('[Request] ');
|
|
68
|
+
});
|
|
69
|
+
it('子 logger 应该继承父 logger 的配置', () => {
|
|
70
|
+
const logger = new FastifyLogger({ ...loggerOptions, level: 'warn' });
|
|
71
|
+
const childLogger = logger.child({}, { level: 'debug' });
|
|
72
|
+
// 子 logger 应该使用传入的 level
|
|
73
|
+
expect(childLogger.level).toBe('debug');
|
|
74
|
+
});
|
|
75
|
+
it('应该支持多个参数的日志输出', () => {
|
|
76
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
77
|
+
logger.info('message');
|
|
78
|
+
expect(printMock).toHaveBeenCalledWith('info', 'message');
|
|
79
|
+
});
|
|
80
|
+
it('应该正确处理错误对象', () => {
|
|
81
|
+
const logger = new FastifyLogger(loggerOptions);
|
|
82
|
+
const error = new Error('test error');
|
|
83
|
+
logger.error(error);
|
|
84
|
+
// 验证 print 被调用,不验证具体的格式化内容
|
|
85
|
+
expect(printMock).toHaveBeenCalledWith('error', expect.stringContaining('test error'));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { BatchTaskQueueConfig, TaskData, Task, QueueStats } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 批量任务队列类(泛型)
|
|
4
|
+
*
|
|
5
|
+
* 提供基于 Redis 的批量任务队列功能,支持:
|
|
6
|
+
* - 批量处理任务(每次处理多条)
|
|
7
|
+
* - 任务入队和持久化
|
|
8
|
+
* - 自动重试机制
|
|
9
|
+
* - 任务状态追踪
|
|
10
|
+
* - 分布式消费
|
|
11
|
+
*
|
|
12
|
+
* @template T 任务数据类型
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* interface EmailTask {
|
|
17
|
+
* to: string
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* const queue = new BatchRedisQueue<EmailTask>({
|
|
21
|
+
* redisUrl: 'redis://localhost:6379',
|
|
22
|
+
* queueKey: 'email-batch-queue',
|
|
23
|
+
* batchSize: 50,
|
|
24
|
+
* handler: async (dataList) => {
|
|
25
|
+
* await sendEmailsBatch(dataList.map(d => d.to))
|
|
26
|
+
* return catchIt(() => {})
|
|
27
|
+
* }
|
|
28
|
+
* })
|
|
29
|
+
*
|
|
30
|
+
* // 连接 Redis(自动启动消费者)
|
|
31
|
+
* await queue.connect()
|
|
32
|
+
*
|
|
33
|
+
* // 入队任务(自动开始处理)
|
|
34
|
+
* await queue.enqueue([
|
|
35
|
+
* { to: 'user1@example.com' },
|
|
36
|
+
* { to: 'user2@example.com' },
|
|
37
|
+
* ])
|
|
38
|
+
*
|
|
39
|
+
* // 获取队列统计信息(O(1) 时间复杂度)
|
|
40
|
+
* const stats = await queue.statistics()
|
|
41
|
+
* console.log(stats) // { pending: 100, processing: 50, completed: 200, failed: 5 }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class BatchRedisQueue<T extends TaskData = TaskData> {
|
|
45
|
+
private consumerRunning;
|
|
46
|
+
private redis;
|
|
47
|
+
private consumerInterval;
|
|
48
|
+
private recoveryInterval;
|
|
49
|
+
private processingBatches;
|
|
50
|
+
private readonly config;
|
|
51
|
+
private readonly handler;
|
|
52
|
+
private readonly failedQueue;
|
|
53
|
+
private readonly pendingQueue;
|
|
54
|
+
private readonly processingQueue;
|
|
55
|
+
private readonly completedQueue;
|
|
56
|
+
constructor(config: BatchTaskQueueConfig<T>);
|
|
57
|
+
/**
|
|
58
|
+
* 连接 Redis 并自动启动消费者
|
|
59
|
+
*/
|
|
60
|
+
connect(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* 断开 Redis 连接并停止消费者
|
|
63
|
+
*/
|
|
64
|
+
disconnect(): void;
|
|
65
|
+
/**
|
|
66
|
+
* 将任务推入队列(支持单个或批量)
|
|
67
|
+
* @param data 任务数据(单个或数组)。可以在 data 中包含 `id` 字段来实现幂等性
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // 普通任务,自动生成 ID
|
|
71
|
+
* await queue.enqueue({ to: 'user@example.com' })
|
|
72
|
+
*
|
|
73
|
+
* // 幂等任务,手动指定 ID,重复提交会被忽略
|
|
74
|
+
* await queue.enqueue({ id: 'email-123', to: 'user@example.com' })
|
|
75
|
+
* await queue.enqueue({ id: 'email-123', to: 'user@example.com' }) // 会被跳过
|
|
76
|
+
*/
|
|
77
|
+
enqueue(data: T[]): Promise<string[]>;
|
|
78
|
+
enqueue(data: T): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* 获取任务详情
|
|
81
|
+
*/
|
|
82
|
+
getTask(taskId: string): Promise<Task<T> | null>;
|
|
83
|
+
/**
|
|
84
|
+
* 更新任务状态并移动到对应队列(原子操作)
|
|
85
|
+
*/
|
|
86
|
+
private applyStatus;
|
|
87
|
+
/**
|
|
88
|
+
* 批量更新任务状态
|
|
89
|
+
*/
|
|
90
|
+
private applyStatusBatch;
|
|
91
|
+
/**
|
|
92
|
+
* 根据状态获取对应的队列键
|
|
93
|
+
*/
|
|
94
|
+
private getQueueByStatus;
|
|
95
|
+
/**
|
|
96
|
+
* 恢复超时的任务
|
|
97
|
+
* 检查 processing 队列中的任务,将超时的任务重试或标记为失败
|
|
98
|
+
*/
|
|
99
|
+
private recoverStalledTasks;
|
|
100
|
+
/**
|
|
101
|
+
* 批量处理任务
|
|
102
|
+
*/
|
|
103
|
+
private processBatch;
|
|
104
|
+
/**
|
|
105
|
+
* 启动恢复机制(内部方法,自动调用)
|
|
106
|
+
*/
|
|
107
|
+
private startRecovery;
|
|
108
|
+
/**
|
|
109
|
+
* 停止恢复机制(内部方法,自动调用)
|
|
110
|
+
*/
|
|
111
|
+
private stopRecovery;
|
|
112
|
+
/**
|
|
113
|
+
* 启动消费者(内部方法,自动调用)
|
|
114
|
+
*/
|
|
115
|
+
private startConsumer;
|
|
116
|
+
/**
|
|
117
|
+
* 停止消费者(内部方法,自动调用)
|
|
118
|
+
*/
|
|
119
|
+
private stopConsumer;
|
|
120
|
+
/**
|
|
121
|
+
* 获取队列统计信息
|
|
122
|
+
* 返回队列中各种状态任务的详细数量
|
|
123
|
+
*
|
|
124
|
+
* 使用分离队列设计,O(1) 时间复杂度
|
|
125
|
+
*/
|
|
126
|
+
statistics(): Promise<QueueStats>;
|
|
127
|
+
/**
|
|
128
|
+
* 清空所有队列
|
|
129
|
+
*/
|
|
130
|
+
clear(): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* 健康检查
|
|
133
|
+
*/
|
|
134
|
+
health(): Promise<boolean>;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=batch-redis-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-redis-queue.d.ts","sourceRoot":"","sources":["../../source/redis-queue/batch-redis-queue.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAU,QAAQ,EAAE,IAAI,EAAoB,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,qBAAa,eAAe,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IACxD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,iBAAiB,CAAI;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoD;IAC3E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAG7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;gBAE3B,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IA0C3C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAY9B;;OAEG;IACH,UAAU,IAAI,IAAI;IAUlB;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IACrC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAkEvC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAWtD;;OAEG;YACW,WAAW;IA8BzB;;OAEG;YACW,gBAAgB;IAI9B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;;OAGG;YACW,mBAAmB;IA6EjC;;OAEG;YACW,YAAY;IA8F1B;;OAEG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,aAAa;IA2ErB;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IA0BvC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CAMjC"}
|