@onebots/core 0.5.0 → 1.0.4
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 +3 -3
- package/lib/__tests__/config-validator.test.d.ts +4 -0
- package/lib/__tests__/config-validator.test.js +152 -0
- package/lib/__tests__/di-container.test.d.ts +4 -0
- package/lib/__tests__/di-container.test.js +114 -0
- package/lib/__tests__/errors.test.d.ts +4 -0
- package/lib/__tests__/errors.test.js +111 -0
- package/lib/__tests__/integration.test.d.ts +5 -0
- package/lib/__tests__/integration.test.js +112 -0
- package/lib/__tests__/lifecycle.test.d.ts +4 -0
- package/lib/__tests__/lifecycle.test.js +163 -0
- package/lib/account.d.ts +4 -1
- package/lib/account.js +6 -3
- package/lib/adapter.d.ts +67 -1
- package/lib/adapter.js +31 -4
- package/lib/base-app.d.ts +30 -3
- package/lib/base-app.js +295 -142
- package/lib/circuit-breaker.d.ts +94 -0
- package/lib/circuit-breaker.js +206 -0
- package/lib/config-validator.d.ts +51 -0
- package/lib/config-validator.js +184 -0
- package/lib/connection-pool.d.ts +68 -0
- package/lib/connection-pool.js +202 -0
- package/lib/db.d.ts +2 -1
- package/lib/db.js +11 -2
- package/lib/di-container.d.ts +60 -0
- package/lib/di-container.js +103 -0
- package/lib/errors.d.ts +157 -0
- package/lib/errors.js +257 -0
- package/lib/index.d.ts +13 -4
- package/lib/index.js +17 -3
- package/lib/lifecycle.d.ts +75 -0
- package/lib/lifecycle.js +175 -0
- package/lib/logger.d.ts +76 -0
- package/lib/logger.js +156 -0
- package/lib/metrics.d.ts +80 -0
- package/lib/metrics.js +201 -0
- package/lib/middleware/index.d.ts +8 -0
- package/lib/middleware/index.js +8 -0
- package/lib/middleware/metrics-collector.d.ts +9 -0
- package/lib/middleware/metrics-collector.js +64 -0
- package/lib/middleware/rate-limit.d.ts +32 -0
- package/lib/middleware/rate-limit.js +149 -0
- package/lib/middleware/security-audit.d.ts +33 -0
- package/lib/middleware/security-audit.js +223 -0
- package/lib/middleware/token-manager.d.ts +73 -0
- package/lib/middleware/token-manager.js +186 -0
- package/lib/middleware/token-validator.d.ts +42 -0
- package/lib/middleware/token-validator.js +198 -0
- package/lib/protocol.d.ts +2 -3
- package/lib/protocol.js +4 -0
- package/lib/rate-limiter.d.ts +88 -0
- package/lib/rate-limiter.js +196 -0
- package/lib/registry.d.ts +27 -0
- package/lib/registry.js +40 -5
- package/lib/retry.d.ts +87 -0
- package/lib/retry.js +205 -0
- package/lib/router.d.ts +43 -6
- package/lib/router.js +139 -12
- package/lib/timestamp.d.ts +42 -0
- package/lib/timestamp.js +72 -0
- package/lib/types.d.ts +1 -0
- package/lib/types.js +2 -1
- package/lib/utils.d.ts +2 -1
- package/lib/utils.js +11 -19
- package/package.json +24 -9
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 熔断器模式实现
|
|
3
|
+
* 防止级联故障,提高系统稳定性
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from './logger.js';
|
|
6
|
+
const logger = createLogger('CircuitBreaker');
|
|
7
|
+
export var CircuitState;
|
|
8
|
+
(function (CircuitState) {
|
|
9
|
+
/** 关闭状态:正常处理请求 */
|
|
10
|
+
CircuitState["CLOSED"] = "CLOSED";
|
|
11
|
+
/** 开启状态:拒绝所有请求 */
|
|
12
|
+
CircuitState["OPEN"] = "OPEN";
|
|
13
|
+
/** 半开状态:尝试恢复,允许部分请求通过 */
|
|
14
|
+
CircuitState["HALF_OPEN"] = "HALF_OPEN";
|
|
15
|
+
})(CircuitState || (CircuitState = {}));
|
|
16
|
+
const DEFAULT_OPTIONS = {
|
|
17
|
+
failureThreshold: 5,
|
|
18
|
+
successThreshold: 2,
|
|
19
|
+
timeout: 60000, // 1 分钟
|
|
20
|
+
monitoringPeriod: 60000, // 1 分钟
|
|
21
|
+
minimumRequests: 10,
|
|
22
|
+
errorRateThreshold: 0.5, // 50%
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 熔断器类
|
|
26
|
+
*/
|
|
27
|
+
export class CircuitBreaker {
|
|
28
|
+
state = CircuitState.CLOSED;
|
|
29
|
+
failureCount = 0;
|
|
30
|
+
successCount = 0;
|
|
31
|
+
lastFailureTime = null;
|
|
32
|
+
records = [];
|
|
33
|
+
options;
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 执行函数,带熔断保护
|
|
39
|
+
*/
|
|
40
|
+
async execute(fn) {
|
|
41
|
+
// 检查状态
|
|
42
|
+
if (this.state === CircuitState.OPEN) {
|
|
43
|
+
// 检查是否可以进入半开状态
|
|
44
|
+
if (this.lastFailureTime && Date.now() - this.lastFailureTime >= this.options.timeout) {
|
|
45
|
+
this.state = CircuitState.HALF_OPEN;
|
|
46
|
+
this.successCount = 0;
|
|
47
|
+
logger.info('Circuit breaker entering HALF_OPEN state');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
throw new CircuitBreakerOpenError('Circuit breaker is OPEN');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// 执行函数
|
|
54
|
+
try {
|
|
55
|
+
const result = await fn();
|
|
56
|
+
this.onSuccess();
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.onFailure();
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 成功回调
|
|
66
|
+
*/
|
|
67
|
+
onSuccess() {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
this.records.push({ timestamp: now, success: true });
|
|
70
|
+
this.cleanupOldRecords(now);
|
|
71
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
72
|
+
this.successCount++;
|
|
73
|
+
if (this.successCount >= this.options.successThreshold) {
|
|
74
|
+
this.state = CircuitState.CLOSED;
|
|
75
|
+
this.failureCount = 0;
|
|
76
|
+
this.successCount = 0;
|
|
77
|
+
logger.info('Circuit breaker CLOSED: recovery successful');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (this.state === CircuitState.CLOSED) {
|
|
81
|
+
// 重置失败计数
|
|
82
|
+
this.failureCount = 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 失败回调
|
|
87
|
+
*/
|
|
88
|
+
onFailure() {
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
this.records.push({ timestamp: now, success: false });
|
|
91
|
+
this.cleanupOldRecords(now);
|
|
92
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
93
|
+
// 半开状态下失败,立即开启
|
|
94
|
+
this.state = CircuitState.OPEN;
|
|
95
|
+
this.lastFailureTime = now;
|
|
96
|
+
this.successCount = 0;
|
|
97
|
+
logger.warn('Circuit breaker OPEN: failed in HALF_OPEN state');
|
|
98
|
+
}
|
|
99
|
+
else if (this.state === CircuitState.CLOSED) {
|
|
100
|
+
this.failureCount++;
|
|
101
|
+
this.lastFailureTime = now;
|
|
102
|
+
// 检查是否应该开启熔断
|
|
103
|
+
const shouldOpen = this.shouldOpen(now);
|
|
104
|
+
if (shouldOpen) {
|
|
105
|
+
this.state = CircuitState.OPEN;
|
|
106
|
+
logger.warn('Circuit breaker OPEN: threshold exceeded', {
|
|
107
|
+
failureCount: this.failureCount,
|
|
108
|
+
errorRate: this.getErrorRate(now),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 判断是否应该开启熔断
|
|
115
|
+
*/
|
|
116
|
+
shouldOpen(now) {
|
|
117
|
+
// 检查连续失败次数
|
|
118
|
+
if (this.failureCount >= this.options.failureThreshold) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
// 检查错误率
|
|
122
|
+
const errorRate = this.getErrorRate(now);
|
|
123
|
+
if (errorRate >= this.options.errorRateThreshold) {
|
|
124
|
+
const recentRequests = this.getRecentRequests(now);
|
|
125
|
+
if (recentRequests.length >= this.options.minimumRequests) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 获取错误率
|
|
133
|
+
*/
|
|
134
|
+
getErrorRate(now) {
|
|
135
|
+
const recentRequests = this.getRecentRequests(now);
|
|
136
|
+
if (recentRequests.length === 0) {
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
const failures = recentRequests.filter(r => !r.success).length;
|
|
140
|
+
return failures / recentRequests.length;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 获取最近的请求记录
|
|
144
|
+
*/
|
|
145
|
+
getRecentRequests(now) {
|
|
146
|
+
const cutoff = now - this.options.monitoringPeriod;
|
|
147
|
+
return this.records.filter(r => r.timestamp >= cutoff);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 清理过期记录
|
|
151
|
+
*/
|
|
152
|
+
cleanupOldRecords(now) {
|
|
153
|
+
const cutoff = now - this.options.monitoringPeriod * 2;
|
|
154
|
+
this.records = this.records.filter(r => r.timestamp >= cutoff);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 获取当前状态
|
|
158
|
+
*/
|
|
159
|
+
getState() {
|
|
160
|
+
return this.state;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 获取统计信息
|
|
164
|
+
*/
|
|
165
|
+
getStats() {
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
const recentRequests = this.getRecentRequests(now);
|
|
168
|
+
const failures = recentRequests.filter(r => !r.success).length;
|
|
169
|
+
const errorRate = recentRequests.length > 0 ? failures / recentRequests.length : 0;
|
|
170
|
+
return {
|
|
171
|
+
state: this.state,
|
|
172
|
+
failureCount: this.failureCount,
|
|
173
|
+
successCount: this.successCount,
|
|
174
|
+
errorRate,
|
|
175
|
+
totalRequests: recentRequests.length,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 手动重置熔断器
|
|
180
|
+
*/
|
|
181
|
+
reset() {
|
|
182
|
+
this.state = CircuitState.CLOSED;
|
|
183
|
+
this.failureCount = 0;
|
|
184
|
+
this.successCount = 0;
|
|
185
|
+
this.lastFailureTime = null;
|
|
186
|
+
this.records = [];
|
|
187
|
+
logger.info('Circuit breaker manually reset');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 熔断器开启错误
|
|
192
|
+
*/
|
|
193
|
+
export class CircuitBreakerOpenError extends Error {
|
|
194
|
+
constructor(message) {
|
|
195
|
+
super(message);
|
|
196
|
+
this.name = 'CircuitBreakerOpenError';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 创建带熔断器的函数包装器
|
|
201
|
+
*/
|
|
202
|
+
export function withCircuitBreaker(fn, breaker) {
|
|
203
|
+
return ((...args) => {
|
|
204
|
+
return breaker.execute(() => fn(...args));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置验证系统
|
|
3
|
+
* 提供配置schema验证和默认值处理
|
|
4
|
+
*/
|
|
5
|
+
import { ValidationError } from './errors.js';
|
|
6
|
+
export { ValidationError };
|
|
7
|
+
export interface ValidationRule<T = any> {
|
|
8
|
+
required?: boolean;
|
|
9
|
+
type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
10
|
+
min?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
pattern?: RegExp;
|
|
13
|
+
enum?: any[];
|
|
14
|
+
validator?: (value: T) => boolean | string;
|
|
15
|
+
default?: T | (() => T);
|
|
16
|
+
transform?: (value: any) => T;
|
|
17
|
+
/** 用于表单展示的标签 */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** 用于表单展示的说明 */
|
|
20
|
+
description?: string;
|
|
21
|
+
/** 用于表单展示的占位提示 */
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface Schema {
|
|
25
|
+
[key: string]: ValidationRule | Schema;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 配置验证器
|
|
29
|
+
*/
|
|
30
|
+
export declare class ConfigValidator {
|
|
31
|
+
/**
|
|
32
|
+
* 验证配置对象
|
|
33
|
+
*/
|
|
34
|
+
static validate<T extends Record<string, any>>(config: T, schema: Schema, path?: string): T;
|
|
35
|
+
/**
|
|
36
|
+
* 检查类型
|
|
37
|
+
*/
|
|
38
|
+
private static checkType;
|
|
39
|
+
/**
|
|
40
|
+
* 判断是否为嵌套schema
|
|
41
|
+
*/
|
|
42
|
+
private static isSchema;
|
|
43
|
+
/**
|
|
44
|
+
* 验证并应用默认值
|
|
45
|
+
*/
|
|
46
|
+
static validateWithDefaults<T extends Record<string, any>>(config: Partial<T>, schema: Schema): T;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* BaseApp 配置 Schema
|
|
50
|
+
*/
|
|
51
|
+
export declare const BaseAppConfigSchema: Schema;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置验证系统
|
|
3
|
+
* 提供配置schema验证和默认值处理
|
|
4
|
+
*/
|
|
5
|
+
import { ValidationError } from './errors.js';
|
|
6
|
+
export { ValidationError };
|
|
7
|
+
/**
|
|
8
|
+
* 配置验证器
|
|
9
|
+
*/
|
|
10
|
+
export class ConfigValidator {
|
|
11
|
+
/**
|
|
12
|
+
* 验证配置对象
|
|
13
|
+
*/
|
|
14
|
+
static validate(config, schema, path = '') {
|
|
15
|
+
const result = { ...config };
|
|
16
|
+
const errors = [];
|
|
17
|
+
for (const [key, rule] of Object.entries(schema)) {
|
|
18
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
19
|
+
const value = config[key];
|
|
20
|
+
// 如果是嵌套schema,递归验证
|
|
21
|
+
if (this.isSchema(rule)) {
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
result[key] = this.validate(value || {}, rule, currentPath);
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const validationRule = rule;
|
|
28
|
+
// 检查必填字段
|
|
29
|
+
if (validationRule.required && (value === undefined || value === null)) {
|
|
30
|
+
if (validationRule.default !== undefined) {
|
|
31
|
+
result[key] = typeof validationRule.default === 'function'
|
|
32
|
+
? validationRule.default()
|
|
33
|
+
: validationRule.default;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
errors.push(`${currentPath} is required`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// 如果值为undefined且有默认值,使用默认值
|
|
40
|
+
if (value === undefined && validationRule.default !== undefined) {
|
|
41
|
+
result[key] = typeof validationRule.default === 'function'
|
|
42
|
+
? validationRule.default()
|
|
43
|
+
: validationRule.default;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// 如果值为undefined,跳过验证
|
|
47
|
+
if (value === undefined) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// 类型转换
|
|
51
|
+
if (validationRule.transform) {
|
|
52
|
+
try {
|
|
53
|
+
result[key] = validationRule.transform(value);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
errors.push(`${currentPath} transform failed: ${error.message}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const finalValue = result[key];
|
|
61
|
+
// transform 可能将空字符串等转为 undefined,视为可选字段未填,跳过后续类型与范围检查
|
|
62
|
+
if (finalValue === undefined) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// 类型检查
|
|
66
|
+
if (validationRule.type) {
|
|
67
|
+
const typeError = this.checkType(finalValue, validationRule.type, currentPath);
|
|
68
|
+
if (typeError) {
|
|
69
|
+
errors.push(typeError);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 数值范围检查
|
|
74
|
+
if (validationRule.type === 'number') {
|
|
75
|
+
if (validationRule.min !== undefined && finalValue < validationRule.min) {
|
|
76
|
+
errors.push(`${currentPath} must be >= ${validationRule.min}`);
|
|
77
|
+
}
|
|
78
|
+
if (validationRule.max !== undefined && finalValue > validationRule.max) {
|
|
79
|
+
errors.push(`${currentPath} must be <= ${validationRule.max}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 字符串长度检查
|
|
83
|
+
if (validationRule.type === 'string') {
|
|
84
|
+
if (validationRule.min !== undefined && finalValue.length < validationRule.min) {
|
|
85
|
+
errors.push(`${currentPath} length must be >= ${validationRule.min}`);
|
|
86
|
+
}
|
|
87
|
+
if (validationRule.max !== undefined && finalValue.length > validationRule.max) {
|
|
88
|
+
errors.push(`${currentPath} length must be <= ${validationRule.max}`);
|
|
89
|
+
}
|
|
90
|
+
if (validationRule.pattern && !validationRule.pattern.test(finalValue)) {
|
|
91
|
+
errors.push(`${currentPath} does not match pattern ${validationRule.pattern}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 枚举检查
|
|
95
|
+
if (validationRule.enum && !validationRule.enum.includes(finalValue)) {
|
|
96
|
+
errors.push(`${currentPath} must be one of: ${validationRule.enum.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
// 自定义验证器
|
|
99
|
+
if (validationRule.validator) {
|
|
100
|
+
const validationResult = validationRule.validator(finalValue);
|
|
101
|
+
if (validationResult !== true) {
|
|
102
|
+
errors.push(`${currentPath}: ${validationResult || 'validation failed'}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (errors.length > 0) {
|
|
107
|
+
throw new ValidationError('Configuration validation failed', {
|
|
108
|
+
context: {
|
|
109
|
+
errors,
|
|
110
|
+
path,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 检查类型
|
|
118
|
+
*/
|
|
119
|
+
static checkType(value, expectedType, path) {
|
|
120
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
121
|
+
if (actualType !== expectedType) {
|
|
122
|
+
return `${path} must be ${expectedType}, got ${actualType}`;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 判断是否为嵌套schema
|
|
128
|
+
*/
|
|
129
|
+
static isSchema(rule) {
|
|
130
|
+
return !('required' in rule) && !('type' in rule) && typeof rule === 'object';
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 验证并应用默认值
|
|
134
|
+
*/
|
|
135
|
+
static validateWithDefaults(config, schema) {
|
|
136
|
+
return this.validate(config, schema);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* BaseApp 配置 Schema
|
|
141
|
+
*/
|
|
142
|
+
export const BaseAppConfigSchema = {
|
|
143
|
+
port: {
|
|
144
|
+
type: 'number',
|
|
145
|
+
min: 1,
|
|
146
|
+
max: 65535,
|
|
147
|
+
default: 6727,
|
|
148
|
+
},
|
|
149
|
+
path: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
default: '',
|
|
152
|
+
},
|
|
153
|
+
database: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
default: 'onebots.db',
|
|
156
|
+
},
|
|
157
|
+
timeout: {
|
|
158
|
+
type: 'number',
|
|
159
|
+
min: 1,
|
|
160
|
+
default: 30,
|
|
161
|
+
},
|
|
162
|
+
username: {
|
|
163
|
+
type: 'string',
|
|
164
|
+
transform: (v) => (v != null && String(v).trim() !== '' ? String(v) : undefined),
|
|
165
|
+
},
|
|
166
|
+
password: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
transform: (v) => (v != null && String(v).trim() !== '' ? String(v) : undefined),
|
|
169
|
+
},
|
|
170
|
+
access_token: {
|
|
171
|
+
type: 'string',
|
|
172
|
+
transform: (v) => (v != null && String(v).trim() !== '' ? String(v) : undefined),
|
|
173
|
+
},
|
|
174
|
+
log_level: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
enum: ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'mark', 'off'],
|
|
177
|
+
default: 'info',
|
|
178
|
+
},
|
|
179
|
+
/** 站点根静态文件目录(相对 BaseApp.configDir 或绝对路径),用于可信域名校验文件等 */
|
|
180
|
+
public_static_dir: {
|
|
181
|
+
type: 'string',
|
|
182
|
+
transform: (v) => (v != null && String(v).trim() !== '' ? String(v).trim() : undefined),
|
|
183
|
+
},
|
|
184
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 连接池和资源管理
|
|
3
|
+
* 管理 HTTP 连接、WebSocket 连接等资源
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
export interface PoolConfig {
|
|
7
|
+
/** 最大连接数 */
|
|
8
|
+
maxConnections?: number;
|
|
9
|
+
/** 最小连接数 */
|
|
10
|
+
minConnections?: number;
|
|
11
|
+
/** 连接超时时间(毫秒) */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** 空闲连接超时时间(毫秒) */
|
|
14
|
+
idleTimeout?: number;
|
|
15
|
+
/** 连接创建函数 */
|
|
16
|
+
create: () => Promise<any>;
|
|
17
|
+
/** 连接销毁函数 */
|
|
18
|
+
destroy: (connection: any) => Promise<void>;
|
|
19
|
+
/** 连接验证函数 */
|
|
20
|
+
validate?: (connection: any) => boolean | Promise<boolean>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 连接池类
|
|
24
|
+
*/
|
|
25
|
+
export declare class ConnectionPool extends EventEmitter {
|
|
26
|
+
private pool;
|
|
27
|
+
private waiting;
|
|
28
|
+
private config;
|
|
29
|
+
private cleanupInterval?;
|
|
30
|
+
constructor(config: PoolConfig);
|
|
31
|
+
/**
|
|
32
|
+
* 获取连接
|
|
33
|
+
*/
|
|
34
|
+
acquire(): Promise<any>;
|
|
35
|
+
/**
|
|
36
|
+
* 释放连接
|
|
37
|
+
*/
|
|
38
|
+
release(connection: any): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* 销毁连接
|
|
41
|
+
*/
|
|
42
|
+
destroy(connection: any): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* 启动清理任务
|
|
45
|
+
*/
|
|
46
|
+
private startCleanup;
|
|
47
|
+
/**
|
|
48
|
+
* 清理空闲连接
|
|
49
|
+
*/
|
|
50
|
+
private cleanup;
|
|
51
|
+
/**
|
|
52
|
+
* 获取池状态
|
|
53
|
+
*/
|
|
54
|
+
getStatus(): {
|
|
55
|
+
total: number;
|
|
56
|
+
inUse: number;
|
|
57
|
+
idle: number;
|
|
58
|
+
waiting: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* 清空连接池
|
|
62
|
+
*/
|
|
63
|
+
clear(): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* 关闭连接池
|
|
66
|
+
*/
|
|
67
|
+
close(): Promise<void>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 连接池和资源管理
|
|
3
|
+
* 管理 HTTP 连接、WebSocket 连接等资源
|
|
4
|
+
*/
|
|
5
|
+
import { createLogger } from './logger.js';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
const logger = createLogger('ConnectionPool');
|
|
8
|
+
/**
|
|
9
|
+
* 连接池类
|
|
10
|
+
*/
|
|
11
|
+
export class ConnectionPool extends EventEmitter {
|
|
12
|
+
pool = [];
|
|
13
|
+
waiting = [];
|
|
14
|
+
config;
|
|
15
|
+
cleanupInterval;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
super();
|
|
18
|
+
this.config = {
|
|
19
|
+
maxConnections: config.maxConnections || 10,
|
|
20
|
+
minConnections: config.minConnections || 0,
|
|
21
|
+
timeout: config.timeout || 30000,
|
|
22
|
+
idleTimeout: config.idleTimeout || 60000,
|
|
23
|
+
create: config.create,
|
|
24
|
+
destroy: config.destroy,
|
|
25
|
+
validate: config.validate || (() => true),
|
|
26
|
+
};
|
|
27
|
+
// 启动清理任务
|
|
28
|
+
this.startCleanup();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 获取连接
|
|
32
|
+
*/
|
|
33
|
+
async acquire() {
|
|
34
|
+
// 尝试从池中获取可用连接
|
|
35
|
+
const available = this.pool.find(c => !c.inUse);
|
|
36
|
+
if (available) {
|
|
37
|
+
// 验证连接
|
|
38
|
+
const isValid = await this.config.validate(available.connection);
|
|
39
|
+
if (isValid) {
|
|
40
|
+
available.inUse = true;
|
|
41
|
+
available.lastUsedAt = Date.now();
|
|
42
|
+
return available.connection;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// 连接无效,移除它
|
|
46
|
+
this.pool = this.pool.filter(c => c !== available);
|
|
47
|
+
await this.config.destroy(available.connection);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 检查是否可以创建新连接
|
|
51
|
+
if (this.pool.length < this.config.maxConnections) {
|
|
52
|
+
try {
|
|
53
|
+
const connection = await this.config.create();
|
|
54
|
+
const pooled = {
|
|
55
|
+
connection,
|
|
56
|
+
createdAt: Date.now(),
|
|
57
|
+
lastUsedAt: Date.now(),
|
|
58
|
+
inUse: true,
|
|
59
|
+
};
|
|
60
|
+
this.pool.push(pooled);
|
|
61
|
+
this.emit('connection:created', connection);
|
|
62
|
+
return connection;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger.error('Failed to create connection', { error });
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// 等待可用连接
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timeout = setTimeout(() => {
|
|
72
|
+
const index = this.waiting.findIndex(w => w.reject === reject);
|
|
73
|
+
if (index !== -1) {
|
|
74
|
+
this.waiting.splice(index, 1);
|
|
75
|
+
}
|
|
76
|
+
reject(new Error('Connection acquisition timeout'));
|
|
77
|
+
}, this.config.timeout);
|
|
78
|
+
this.waiting.push({
|
|
79
|
+
resolve: (connection) => {
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
resolve(connection);
|
|
82
|
+
},
|
|
83
|
+
reject: (error) => {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
reject(error);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 释放连接
|
|
92
|
+
*/
|
|
93
|
+
async release(connection) {
|
|
94
|
+
const pooled = this.pool.find(c => c.connection === connection);
|
|
95
|
+
if (!pooled) {
|
|
96
|
+
logger.warn('Attempted to release connection not in pool');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!pooled.inUse) {
|
|
100
|
+
logger.warn('Attempted to release connection that is not in use');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
pooled.inUse = false;
|
|
104
|
+
pooled.lastUsedAt = Date.now();
|
|
105
|
+
// 检查是否有等待的请求
|
|
106
|
+
if (this.waiting.length > 0) {
|
|
107
|
+
const waiter = this.waiting.shift();
|
|
108
|
+
waiter.resolve(connection);
|
|
109
|
+
pooled.inUse = true;
|
|
110
|
+
pooled.lastUsedAt = Date.now();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 销毁连接
|
|
115
|
+
*/
|
|
116
|
+
async destroy(connection) {
|
|
117
|
+
const pooled = this.pool.find(c => c.connection === connection);
|
|
118
|
+
if (!pooled) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.pool = this.pool.filter(c => c !== connection);
|
|
122
|
+
try {
|
|
123
|
+
await this.config.destroy(connection);
|
|
124
|
+
this.emit('connection:destroyed', connection);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
logger.error('Failed to destroy connection', { error });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 启动清理任务
|
|
132
|
+
*/
|
|
133
|
+
startCleanup() {
|
|
134
|
+
this.cleanupInterval = setInterval(() => {
|
|
135
|
+
this.cleanup();
|
|
136
|
+
}, 60000); // 每分钟清理一次
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 清理空闲连接
|
|
140
|
+
*/
|
|
141
|
+
async cleanup() {
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const toRemove = [];
|
|
144
|
+
for (const pooled of this.pool) {
|
|
145
|
+
if (!pooled.inUse) {
|
|
146
|
+
const idleTime = now - pooled.lastUsedAt;
|
|
147
|
+
if (idleTime > this.config.idleTimeout) {
|
|
148
|
+
// 检查是否超过最小连接数
|
|
149
|
+
if (this.pool.length > this.config.minConnections) {
|
|
150
|
+
toRemove.push(pooled);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 移除过期连接
|
|
156
|
+
for (const pooled of toRemove) {
|
|
157
|
+
await this.destroy(pooled.connection);
|
|
158
|
+
}
|
|
159
|
+
if (toRemove.length > 0) {
|
|
160
|
+
logger.debug(`Cleaned up ${toRemove.length} idle connections`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 获取池状态
|
|
165
|
+
*/
|
|
166
|
+
getStatus() {
|
|
167
|
+
return {
|
|
168
|
+
total: this.pool.length,
|
|
169
|
+
inUse: this.pool.filter(c => c.inUse).length,
|
|
170
|
+
idle: this.pool.filter(c => !c.inUse).length,
|
|
171
|
+
waiting: this.waiting.length,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 清空连接池
|
|
176
|
+
*/
|
|
177
|
+
async clear() {
|
|
178
|
+
// 取消所有等待的请求
|
|
179
|
+
for (const waiter of this.waiting) {
|
|
180
|
+
waiter.reject(new Error('Pool cleared'));
|
|
181
|
+
}
|
|
182
|
+
this.waiting = [];
|
|
183
|
+
// 销毁所有连接
|
|
184
|
+
const connections = [...this.pool];
|
|
185
|
+
for (const pooled of connections) {
|
|
186
|
+
await this.destroy(pooled.connection);
|
|
187
|
+
}
|
|
188
|
+
this.pool = [];
|
|
189
|
+
logger.info('Connection pool cleared');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 关闭连接池
|
|
193
|
+
*/
|
|
194
|
+
async close() {
|
|
195
|
+
if (this.cleanupInterval) {
|
|
196
|
+
clearInterval(this.cleanupInterval);
|
|
197
|
+
this.cleanupInterval = undefined;
|
|
198
|
+
}
|
|
199
|
+
await this.clear();
|
|
200
|
+
this.emit('close');
|
|
201
|
+
}
|
|
202
|
+
}
|