create-dp-koa 1.1.1 → 1.1.3
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/package.json +1 -1
- package/template/scripts/sync-template.mjs +21 -0
- package/template/src/app.ts +1 -2
- package/template/src/{framework/plugins → plugins}/registry.ts +2 -2
- package/template/src/plugins/weboffice/http/routes.ts +2 -2
- package/template/src/plugins/weboffice/index.ts +3 -3
- package/template/src/plugins/weboffice/services/webofficeCallback.service.ts +3 -4
- package/template/src/utils/testDataInitializer.ts +1 -1
- package/template/tsconfig.json +6 -0
- package/template/src/annotations/decorators/ConfigManagement.ts +0 -9
- package/template/src/annotations/decorators/DistributedTracing.ts +0 -9
- package/template/src/annotations/decorators/EnterprisePerformance.ts +0 -9
- package/template/src/annotations/decorators/PerformanceMonitor.ts +0 -32
- package/template/src/annotations/decorators/SecurityAudit.ts +0 -9
- package/template/src/annotations/index.ts +0 -50
- package/template/src/annotations/processors/ConfigManagementProcessor.ts +0 -369
- package/template/src/annotations/processors/DistributedTracingProcessor.ts +0 -288
- package/template/src/annotations/processors/EnterprisePerformanceProcessor.ts +0 -189
- package/template/src/annotations/processors/PerformanceMonitorProcessor.ts +0 -101
- package/template/src/annotations/processors/SecurityAuditProcessor.ts +0 -345
- package/template/src/annotations/processors/SwaggerProcessor.ts +0 -612
- package/template/src/annotations/processors/index.ts +0 -10
- package/template/src/examples/InterceptorExampleRunner.ts +0 -284
- package/template/src/examples/ServiceInterceptorExample.ts +0 -214
- package/template/src/examples/cacheExamples.ts +0 -155
- package/template/src/framework/decorator/controller.ts +0 -311
- package/template/src/framework/decorator/processor/AnnotationDecorators.ts +0 -100
- package/template/src/framework/decorator/processor/AnnotationProcessor.ts +0 -160
- package/template/src/framework/decorator/processor/AnnotationProcessorConfig.ts +0 -45
- package/template/src/framework/decorator/processor/AnnotationRegistry.ts +0 -117
- package/template/src/framework/decorator/processor/AnnotationSystemInitializer.ts +0 -95
- package/template/src/framework/decorator/processor/ProcessorManager.ts +0 -76
- package/template/src/framework/decorator/processor/processors/CustomProcessors.ts +0 -126
- package/template/src/framework/decorator/processor/processors/DefaultProcessors.ts +0 -207
- package/template/src/framework/decorator/refactored/DecoratorFactory.ts +0 -99
- package/template/src/framework/decorator/refactored/DecoratorMetadataManager.ts +0 -125
- package/template/src/framework/decorator/refactored/DecoratorValidator.ts +0 -128
- package/template/src/framework/decorator/refactored/TypeSafeDecorators.ts +0 -139
- package/template/src/framework/decorator/refactored/index.ts +0 -98
- package/template/src/framework/decorator/swagger.ts +0 -150
- package/template/src/framework/interceptors/AdvancedServiceCallInterceptor.ts +0 -375
- package/template/src/framework/interceptors/ServiceCallInterceptor.ts +0 -348
- package/template/src/framework/interceptors/index.ts +0 -19
- package/template/src/framework/plugins/types.ts +0 -15
- package/template/src/framework/types/ServiceResult.ts +0 -151
- package/template/src/framework/types/index.ts +0 -16
- package/template/src/framework/utils/CacheManager.ts +0 -430
- package/template/src/framework/utils/CacheService.ts +0 -248
- package/template/src/framework/utils/DtoValidator.ts +0 -164
- package/template/src/framework/utils/MigrationHelper.ts +0 -179
- package/template/src/framework/utils/MigrationManager.ts +0 -256
- package/template/src/framework/utils/NewRouter.ts +0 -207
- package/template/src/framework/utils/TransactionManager.ts +0 -172
- package/template/src/framework/utils/bootstrap.ts +0 -445
- package/template/src/framework/utils/cache.ts +0 -269
- package/template/src/framework/utils/databaseConfig.ts +0 -148
- package/template/src/framework/utils/db.ts +0 -39
- package/template/src/framework/utils/dbMonitor.ts +0 -106
- package/template/src/framework/utils/function.ts +0 -61
- package/template/src/framework/utils/gracefulShutdown.ts +0 -131
- package/template/src/framework/utils/logger.ts +0 -388
- package/template/src/framework/utils/metrics.ts +0 -182
- package/template/src/framework/utils/router.ts +0 -417
- package/template/src/framework/utils/swagger.ts +0 -184
- package/template/src/framework/utils/testDb.ts +0 -19
- package/template/src/framework/utils/token.ts +0 -23
- package/template/src/framework/utils/transform.ts +0 -17
- package/template/src/libs/aokEmailSender.ts +0 -42
- package/template/src/libs/captcha.ts +0 -37
- package/template/src/libs/cos.ts +0 -45
- package/template/src/libs/mCache.ts +0 -7
- package/template/src/libs/serviceValidate.ts +0 -3
- package/template/src/libs/tecentSms.ts +0 -51
- package/template/src/middlewares/a.middleware.ts +0 -6
- package/template/src/middlewares/error.middleware.ts +0 -14
- package/template/src/middlewares/logging.middleware.ts +0 -187
- package/template/src/middlewares/static.middleware.ts +0 -79
- package/template/src/middlewares/swagger.middleware.ts +0 -70
- package/template/src/middlewares/token.middleware.ts +0 -32
- package/template/src/migrations/1700000000000-InitialDatabaseStructure.ts +0 -172
- package/template/src/migrations/index.ts +0 -6
- package/template/src/repository/base/BaseRepository.ts +0 -124
- package/template/src/repository/interfaces/IBaseRepository.ts +0 -67
- package/template/src/service/base.service.ts +0 -116
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { DataSourceOptions } from "typeorm";
|
|
2
|
-
import { logger } from "@src/framework/utils/logger";
|
|
3
|
-
import { APP_MIGRATIONS } from "@src/migrations";
|
|
4
|
-
import { isDebug } from "@src/framework/utils/function";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 数据库配置工具
|
|
8
|
-
* 框架级别的数据库配置管理
|
|
9
|
-
*/
|
|
10
|
-
export class DatabaseConfigManager {
|
|
11
|
-
private static instance: DatabaseConfigManager;
|
|
12
|
-
|
|
13
|
-
private constructor() {}
|
|
14
|
-
|
|
15
|
-
static getInstance(): DatabaseConfigManager {
|
|
16
|
-
if (!DatabaseConfigManager.instance) {
|
|
17
|
-
DatabaseConfigManager.instance = new DatabaseConfigManager();
|
|
18
|
-
}
|
|
19
|
-
return DatabaseConfigManager.instance;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 检查是否启用数据库
|
|
24
|
-
*/
|
|
25
|
-
isDatabaseEnabled(): boolean {
|
|
26
|
-
// 在内存数据库模式下也启用数据库
|
|
27
|
-
if (this.isMemoryDatabaseMode()) {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
return Number(process.env.db_enable) == 1;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 检查是否为内存数据库模式
|
|
35
|
-
*/
|
|
36
|
-
isMemoryDatabaseMode(): boolean {
|
|
37
|
-
return Number(process.env.db_memory) == 1;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 获取数据库类型(postgres | mysql),仅非内存模式时有效
|
|
42
|
-
*/
|
|
43
|
-
private getDbType(): 'postgres' | 'mysql' {
|
|
44
|
-
const t = (process.env.db_type || 'postgres').toLowerCase();
|
|
45
|
-
return t === 'mysql' ? 'mysql' : 'postgres';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 获取数据库配置
|
|
50
|
-
* @param entities 实体列表
|
|
51
|
-
* 内存模式:SQLite 内存(单元测试保持此方案);否则按 db_type 使用 PostgreSQL 或 MySQL
|
|
52
|
-
*/
|
|
53
|
-
getDatabaseConfig(entities: any[]): DataSourceOptions {
|
|
54
|
-
if (this.isMemoryDatabaseMode()) {
|
|
55
|
-
logger.info("启用内存数据库模式(SQLite)");
|
|
56
|
-
return this.getMemoryDatabaseConfig(entities);
|
|
57
|
-
}
|
|
58
|
-
const dbType = this.getDbType();
|
|
59
|
-
if (dbType === 'postgres') {
|
|
60
|
-
logger.info("启用 PostgreSQL 数据库模式");
|
|
61
|
-
return this.getPostgresDatabaseConfig(entities);
|
|
62
|
-
}
|
|
63
|
-
logger.info("启用 MySQL 数据库模式");
|
|
64
|
-
return this.getMySQLDatabaseConfig(entities);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 获取内存数据库配置
|
|
69
|
-
* 使用 SQLite 内存数据库,适合开发和测试环境
|
|
70
|
-
*/
|
|
71
|
-
private getMemoryDatabaseConfig(entities: any[]): DataSourceOptions {
|
|
72
|
-
// 使用 SQLite 内存数据库
|
|
73
|
-
return {
|
|
74
|
-
type: "sqlite",
|
|
75
|
-
database: ":memory:", // SQLite 内存数据库
|
|
76
|
-
entities: entities,
|
|
77
|
-
synchronize: true,
|
|
78
|
-
logging: false,
|
|
79
|
-
dropSchema: true, // 每次启动时重新创建表结构
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 获取 PostgreSQL 数据库配置(dev/debug 推荐)
|
|
85
|
-
*/
|
|
86
|
-
private getPostgresDatabaseConfig(entities: any[]): DataSourceOptions {
|
|
87
|
-
const debug = isDebug();
|
|
88
|
-
return {
|
|
89
|
-
type: "postgres",
|
|
90
|
-
host: process.env.db_host ?? "127.0.0.1",
|
|
91
|
-
port: parseInt(process.env.db_port || "5432", 10),
|
|
92
|
-
username: process.env.db_username ?? "admin",
|
|
93
|
-
password: process.env.db_password ?? "",
|
|
94
|
-
database: process.env.db_database ?? "mydb",
|
|
95
|
-
entities: entities,
|
|
96
|
-
// 非 debug(生产口径)关闭自动同步,避免与 migration 冲突
|
|
97
|
-
synchronize: debug,
|
|
98
|
-
logging: process.env.db_logging === "1",
|
|
99
|
-
migrations: APP_MIGRATIONS,
|
|
100
|
-
migrationsRun: false,
|
|
101
|
-
migrationsTableName: "migrations",
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 获取 MySQL 数据库配置
|
|
107
|
-
*/
|
|
108
|
-
private getMySQLDatabaseConfig(entities: any[]): DataSourceOptions {
|
|
109
|
-
const debug = isDebug();
|
|
110
|
-
return {
|
|
111
|
-
type: "mysql",
|
|
112
|
-
connectorPackage: "mysql2",
|
|
113
|
-
host: process.env?.db_host ?? "127.0.0.1",
|
|
114
|
-
port: parseInt(process.env.db_port || "3306", 10),
|
|
115
|
-
username: process.env?.db_username ?? "test",
|
|
116
|
-
password: process.env?.db_password ?? "test",
|
|
117
|
-
database: process.env?.db_database ?? "test",
|
|
118
|
-
entities: entities,
|
|
119
|
-
// 非 debug(生产口径)关闭自动同步,避免与 migration 冲突
|
|
120
|
-
synchronize: debug,
|
|
121
|
-
logging: false,
|
|
122
|
-
migrations: APP_MIGRATIONS,
|
|
123
|
-
migrationsRun: false,
|
|
124
|
-
migrationsTableName: "migrations",
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 获取数据库类型描述
|
|
130
|
-
*/
|
|
131
|
-
getDatabaseTypeDescription(): string {
|
|
132
|
-
if (this.isMemoryDatabaseMode()) {
|
|
133
|
-
return "内存数据库(SQLite)";
|
|
134
|
-
}
|
|
135
|
-
return this.getDbType() === "postgres" ? "PostgreSQL" : "MySQL";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 检查是否需要数据库监控
|
|
140
|
-
* 内存数据库不需要连接池监控
|
|
141
|
-
*/
|
|
142
|
-
needsDatabaseMonitoring(): boolean {
|
|
143
|
-
return this.isDatabaseEnabled() && !this.isMemoryDatabaseMode();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 导出单例实例
|
|
148
|
-
export const databaseConfigManager = DatabaseConfigManager.getInstance();
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { DataSource, DataSourceOptions } from "typeorm"
|
|
2
|
-
import { logger } from "@src/framework/utils/logger"
|
|
3
|
-
|
|
4
|
-
var _MysqlDataSource: DataSource | null = null;
|
|
5
|
-
|
|
6
|
-
export const initDb = async (option: DataSourceOptions) => {
|
|
7
|
-
_MysqlDataSource = new DataSource(option);
|
|
8
|
-
return _MysqlDataSource.initialize();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const getDataSource = (): DataSource | null => {
|
|
12
|
-
return _MysqlDataSource
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 关闭数据库连接
|
|
17
|
-
* 在应用关闭时调用,确保数据库连接池被正确释放
|
|
18
|
-
*/
|
|
19
|
-
export const closeDb = async (): Promise<void> => {
|
|
20
|
-
if (_MysqlDataSource && _MysqlDataSource.isInitialized) {
|
|
21
|
-
try {
|
|
22
|
-
await _MysqlDataSource.destroy();
|
|
23
|
-
_MysqlDataSource = null;
|
|
24
|
-
logger.info("数据库连接已关闭");
|
|
25
|
-
} catch (error) {
|
|
26
|
-
logger.error("关闭数据库连接时发生错误:", error as Error);
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
} else {
|
|
30
|
-
logger.info("数据库连接未初始化或已关闭");
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 检查数据库连接状态
|
|
36
|
-
*/
|
|
37
|
-
export const isDbConnected = (): boolean => {
|
|
38
|
-
return _MysqlDataSource !== null && _MysqlDataSource.isInitialized;
|
|
39
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { logger } from "@src/framework/utils/logger";
|
|
2
|
-
import { getDataSource, isDbConnected } from "@src/framework/utils/db";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 数据库连接监控工具
|
|
6
|
-
*/
|
|
7
|
-
export class DbMonitor {
|
|
8
|
-
private static instance: DbMonitor;
|
|
9
|
-
private monitorInterval: NodeJS.Timeout | null = null;
|
|
10
|
-
|
|
11
|
-
private constructor() {}
|
|
12
|
-
|
|
13
|
-
static getInstance(): DbMonitor {
|
|
14
|
-
if (!DbMonitor.instance) {
|
|
15
|
-
DbMonitor.instance = new DbMonitor();
|
|
16
|
-
}
|
|
17
|
-
return DbMonitor.instance;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 开始监控数据库连接状态
|
|
22
|
-
* @param intervalMs 监控间隔(毫秒),默认30秒
|
|
23
|
-
*/
|
|
24
|
-
startMonitoring(intervalMs: number = 30000): void {
|
|
25
|
-
if (this.monitorInterval) {
|
|
26
|
-
logger.warn("数据库监控已在运行中");
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this.monitorInterval = setInterval(async () => {
|
|
31
|
-
try {
|
|
32
|
-
const isConnected = isDbConnected();
|
|
33
|
-
const dataSource = getDataSource();
|
|
34
|
-
|
|
35
|
-
if (isConnected && dataSource) {
|
|
36
|
-
// 检查是否为内存数据库
|
|
37
|
-
const driver = (dataSource as any).driver;
|
|
38
|
-
if (driver && driver.options && driver.options.database === ':memory:') {
|
|
39
|
-
logger.debug("内存数据库连接正常");
|
|
40
|
-
} else {
|
|
41
|
-
await dataSource.query('SELECT 1');
|
|
42
|
-
const dbType = (dataSource as any).options?.type ?? 'mysql';
|
|
43
|
-
logger.debug(`${dbType} 数据库连接正常`);
|
|
44
|
-
}
|
|
45
|
-
} else {
|
|
46
|
-
logger.warn("数据库连接异常");
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
logger.error("数据库连接检查失败:", error as Error);
|
|
50
|
-
}
|
|
51
|
-
}, intervalMs);
|
|
52
|
-
|
|
53
|
-
logger.info(`数据库连接监控已启动,监控间隔: ${intervalMs}ms`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 停止监控
|
|
58
|
-
*/
|
|
59
|
-
stopMonitoring(): void {
|
|
60
|
-
if (this.monitorInterval) {
|
|
61
|
-
clearInterval(this.monitorInterval);
|
|
62
|
-
this.monitorInterval = null;
|
|
63
|
-
logger.info("数据库连接监控已停止");
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 获取数据库连接池状态
|
|
69
|
-
*/
|
|
70
|
-
async getConnectionPoolStatus(): Promise<any> {
|
|
71
|
-
const dataSource = getDataSource();
|
|
72
|
-
if (!dataSource || !dataSource.isInitialized) {
|
|
73
|
-
return { status: 'disconnected' };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
// 检查是否为内存数据库
|
|
78
|
-
const driver = (dataSource as any).driver;
|
|
79
|
-
if (driver && driver.options && driver.options.database === ':memory:') {
|
|
80
|
-
return {
|
|
81
|
-
status: 'connected',
|
|
82
|
-
type: 'memory',
|
|
83
|
-
message: '内存数据库模式'
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const dbType = (dataSource as any).options?.type ?? 'mysql';
|
|
88
|
-
if (driver && driver.pool) {
|
|
89
|
-
return {
|
|
90
|
-
status: 'connected',
|
|
91
|
-
type: dbType,
|
|
92
|
-
totalConnections: driver.pool._allConnections?.length || 0,
|
|
93
|
-
freeConnections: driver.pool._freeConnections?.length || 0,
|
|
94
|
-
usedConnections: driver.pool._usedConnections?.length || 0
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
return { status: 'connected', type: dbType, poolInfo: 'unavailable' };
|
|
98
|
-
} catch (error) {
|
|
99
|
-
logger.error("获取连接池状态失败:", error as Error);
|
|
100
|
-
return { status: 'error', error: error instanceof Error ? error.message : '未知错误' };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 导出单例实例
|
|
106
|
-
export const dbMonitor = DbMonitor.getInstance();
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import path from "path"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 判断当前是否为调试环境
|
|
5
|
-
*/
|
|
6
|
-
export function isDebug(): boolean {
|
|
7
|
-
let argv = new Set(process.argv)
|
|
8
|
-
if (argv.has("--env=debug")) {
|
|
9
|
-
return true
|
|
10
|
-
}
|
|
11
|
-
return false
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 与 NODE_ENV 解耦的运行环境标识(日志/配置元数据等)
|
|
16
|
-
* - 启动参数含 `--env=debug` → development
|
|
17
|
-
* - 否则 → production(非 debug 即生产口径)
|
|
18
|
-
*/
|
|
19
|
-
export function getRuntimeEnvironmentLabel(): 'development' | 'production' {
|
|
20
|
-
return isDebug() ? 'development' : 'production'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 根据文件夹获取项目目录的全路径
|
|
26
|
-
* @param relativePath
|
|
27
|
-
*/
|
|
28
|
-
export function getFullPath(relativePath: string): string {
|
|
29
|
-
return path.join(process.cwd(), relativePath)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 休眠
|
|
34
|
-
* @param time 毫秒
|
|
35
|
-
*/
|
|
36
|
-
export function sleep(time: number) {
|
|
37
|
-
return new Promise((resolve) => {
|
|
38
|
-
setTimeout(() => {
|
|
39
|
-
resolve("")
|
|
40
|
-
}, time);
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 判断Promise
|
|
46
|
-
* @param obj
|
|
47
|
-
* @returns
|
|
48
|
-
*/
|
|
49
|
-
export function isPromise(obj: any) {
|
|
50
|
-
return !!obj && typeof obj.then === 'function' && typeof obj.catch === 'function';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 生成一定范围内的随机数
|
|
56
|
-
* @param start
|
|
57
|
-
* @param end
|
|
58
|
-
*/
|
|
59
|
-
export function getRandoNumer(start: number, end: number): number {
|
|
60
|
-
return Math.floor(Math.random() * (end - start + 1)) + start
|
|
61
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { closeServer } from '@src/framework/utils/bootstrap';
|
|
2
|
-
|
|
3
|
-
export type ShutdownReason = 'SIGTERM' | 'SIGINT' | 'uncaughtException' | 'unhandledRejection';
|
|
4
|
-
|
|
5
|
-
export type ShutdownHook = () => void | Promise<void>;
|
|
6
|
-
|
|
7
|
-
export interface ShutdownLogger {
|
|
8
|
-
// 兼容项目内 EnterpriseLogger(通常为 info(message, context?) / error(message, error?, context?))
|
|
9
|
-
info: (message: string, context?: any) => void;
|
|
10
|
-
warn?: (message: string, context?: any) => void;
|
|
11
|
-
error: (message: string, error?: Error, context?: any) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface GracefulShutdownControllerOptions {
|
|
15
|
-
logger: ShutdownLogger;
|
|
16
|
-
/**
|
|
17
|
-
* 超时后强制退出,避免资源卡死导致进程无法停止
|
|
18
|
-
*/
|
|
19
|
-
timeoutMs?: number;
|
|
20
|
-
/**
|
|
21
|
-
* 关闭 HTTP server(stop accepting new connections)
|
|
22
|
-
* 默认为 true
|
|
23
|
-
*/
|
|
24
|
-
closeHttpServerFirst?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
type HookItem = { name: string; hook: ShutdownHook };
|
|
28
|
-
|
|
29
|
-
const toError = (value: unknown): Error => {
|
|
30
|
-
if (value instanceof Error) return value;
|
|
31
|
-
if (typeof value === 'string') return new Error(value);
|
|
32
|
-
|
|
33
|
-
if (value && typeof value === 'object') {
|
|
34
|
-
const maybeMessage = (value as any).message;
|
|
35
|
-
if (typeof maybeMessage === 'string' && maybeMessage.trim()) {
|
|
36
|
-
return new Error(maybeMessage);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
return new Error(JSON.stringify(value));
|
|
42
|
-
} catch {
|
|
43
|
-
return new Error(String(value));
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 优雅关闭控制器:
|
|
49
|
-
* - 统一注册 SIGTERM/SIGINT/uncaughtException/unhandledRejection
|
|
50
|
-
* - 确保只触发一次
|
|
51
|
-
* - 按注册顺序依次执行 hook(可用于清理 db/cache/monitor 等)
|
|
52
|
-
*/
|
|
53
|
-
export const createGracefulShutdownController = (options: GracefulShutdownControllerOptions) => {
|
|
54
|
-
const { logger, timeoutMs = 15_000, closeHttpServerFirst = true } = options;
|
|
55
|
-
|
|
56
|
-
let registered = false;
|
|
57
|
-
let shuttingDown = false;
|
|
58
|
-
const hooks: HookItem[] = [];
|
|
59
|
-
|
|
60
|
-
const addHook = (name: string, hook: ShutdownHook) => {
|
|
61
|
-
hooks.push({ name, hook });
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const runHooks = async (reason: ShutdownReason, err?: unknown) => {
|
|
65
|
-
if (shuttingDown) return;
|
|
66
|
-
shuttingDown = true;
|
|
67
|
-
|
|
68
|
-
logger.info(`收到 ${reason} 信号,开始优雅关闭...`);
|
|
69
|
-
if (err) {
|
|
70
|
-
logger.error(`触发优雅关闭时携带异常(${reason})`, toError(err));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const timer = setTimeout(() => {
|
|
74
|
-
logger.error(`优雅关闭超时(${timeoutMs}ms),强制退出`);
|
|
75
|
-
// eslint-disable-next-line n/no-process-exit
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}, timeoutMs);
|
|
78
|
-
// 让 timer 不阻止进程退出
|
|
79
|
-
timer.unref?.();
|
|
80
|
-
|
|
81
|
-
let exitCode = 0;
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
if (closeHttpServerFirst) {
|
|
85
|
-
try {
|
|
86
|
-
await closeServer();
|
|
87
|
-
} catch (e) {
|
|
88
|
-
exitCode = 1;
|
|
89
|
-
logger.error('关闭 HTTP Server 失败', toError(e));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
for (const item of hooks) {
|
|
94
|
-
try {
|
|
95
|
-
await item.hook();
|
|
96
|
-
} catch (e) {
|
|
97
|
-
exitCode = 1;
|
|
98
|
-
logger.error(`执行关闭钩子失败: ${item.name}`, toError(e));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
logger.info('优雅关闭完成');
|
|
103
|
-
} finally {
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
// eslint-disable-next-line n/no-process-exit
|
|
106
|
-
process.exit(exitCode);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const register = () => {
|
|
111
|
-
if (registered) return;
|
|
112
|
-
registered = true;
|
|
113
|
-
|
|
114
|
-
process.on('SIGTERM', () => runHooks('SIGTERM'));
|
|
115
|
-
process.on('SIGINT', () => runHooks('SIGINT'));
|
|
116
|
-
|
|
117
|
-
process.on('uncaughtException', (error) => {
|
|
118
|
-
logger.error('未捕获的异常:', error);
|
|
119
|
-
runHooks('uncaughtException', error);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
process.on('unhandledRejection', (reason) => {
|
|
123
|
-
logger.error('未处理的 Promise 拒绝:', toError(reason));
|
|
124
|
-
runHooks('unhandledRejection', reason);
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
return { addHook, register };
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
|