nsgm-cli 2.1.21 → 2.1.23
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 +40 -0
- package/client/components/Button.tsx +0 -2
- package/client/layout/index.tsx +2 -4
- package/client/utils/common.ts +12 -10
- package/client/utils/fetch.ts +1 -1
- package/client/utils/menu.tsx +0 -1
- package/client/utils/sso.ts +13 -3
- package/generation/client/utils/menu.tsx +0 -1
- package/jest.config.js +4 -4
- package/lib/generate_create.js +9 -0
- package/lib/generators/dataloader-generator.d.ts +12 -0
- package/lib/generators/dataloader-generator.js +221 -0
- package/lib/generators/resolver-generator.d.ts +2 -1
- package/lib/generators/resolver-generator.js +117 -24
- package/lib/generators/schema-generator.js +1 -0
- package/lib/index.js +11 -8
- package/lib/server/dataloaders/index.d.ts +38 -0
- package/lib/server/dataloaders/index.js +33 -0
- package/lib/server/dataloaders/template-dataloader.d.ts +48 -0
- package/lib/server/dataloaders/template-dataloader.js +131 -0
- package/lib/server/debug/dataloader-debug.d.ts +63 -0
- package/lib/server/debug/dataloader-debug.js +192 -0
- package/lib/server/graphql.js +9 -0
- package/lib/server/utils/dataloader-monitor.d.ts +87 -0
- package/lib/server/utils/dataloader-monitor.js +199 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils.js +1 -1
- package/next-env.d.ts +1 -0
- package/next-i18next.config.js +7 -5
- package/next.config.js +34 -112
- package/package.json +6 -3
- package/pages/_app.tsx +7 -7
- package/pages/_document.tsx +0 -1
- package/pages/_error.tsx +0 -1
- package/pages/api/sso/ticketCheck.ts +117 -0
- package/pages/index.tsx +10 -3
- package/pages/login.tsx +41 -11
- package/pages/template/manage.tsx +16 -2
- package/server/apis/sso.js +22 -4
- package/server/modules/template/resolver.js +101 -21
- package/server/modules/template/schema.js +1 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dataLoaderDebugSchema = exports.dataLoaderStatsResolver = void 0;
|
|
4
|
+
exports.createDataLoaderDebugRoutes = createDataLoaderDebugRoutes;
|
|
5
|
+
const dataloader_monitor_1 = require("../utils/dataloader-monitor");
|
|
6
|
+
/**
|
|
7
|
+
* DataLoader 调试和监控 API
|
|
8
|
+
* 在开发环境中提供 DataLoader 性能监控接口
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 获取 DataLoader 统计信息的 GraphQL resolver
|
|
12
|
+
*/
|
|
13
|
+
exports.dataLoaderStatsResolver = {
|
|
14
|
+
// 查询 DataLoader 统计信息
|
|
15
|
+
dataLoaderStats: async (_, context) => {
|
|
16
|
+
try {
|
|
17
|
+
const health = (0, dataloader_monitor_1.getDataLoaderHealth)(context);
|
|
18
|
+
const allStats = dataloader_monitor_1.DataLoaderMonitor.getAllStats();
|
|
19
|
+
return {
|
|
20
|
+
status: health.status,
|
|
21
|
+
score: health.score,
|
|
22
|
+
summary: allStats.summary,
|
|
23
|
+
loaders: health.loaders,
|
|
24
|
+
recommendations: health.recommendations,
|
|
25
|
+
contextStats: health.contextStats,
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error("获取 DataLoader 统计信息失败:", error);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
// 重置 DataLoader 统计信息
|
|
35
|
+
resetDataLoaderStats: async () => {
|
|
36
|
+
try {
|
|
37
|
+
dataloader_monitor_1.DataLoaderMonitor.resetStats();
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
message: "DataLoader 统计信息已重置",
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error("重置 DataLoader 统计信息失败:", error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
// 清除 DataLoader 缓存
|
|
50
|
+
clearDataLoaderCache: async (_, context) => {
|
|
51
|
+
try {
|
|
52
|
+
if (context?.dataloaders?.template) {
|
|
53
|
+
context.dataloaders.template.clearAll();
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
message: "DataLoader 缓存已清除",
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error("清除 DataLoader 缓存失败:", error);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* DataLoader 调试 Schema
|
|
69
|
+
*/
|
|
70
|
+
exports.dataLoaderDebugSchema = {
|
|
71
|
+
query: `
|
|
72
|
+
dataLoaderStats: DataLoaderStatsResult
|
|
73
|
+
`,
|
|
74
|
+
mutation: `
|
|
75
|
+
resetDataLoaderStats: DataLoaderActionResult
|
|
76
|
+
clearDataLoaderCache: DataLoaderActionResult
|
|
77
|
+
`,
|
|
78
|
+
type: `
|
|
79
|
+
type DataLoaderStatsResult {
|
|
80
|
+
status: String
|
|
81
|
+
score: Int
|
|
82
|
+
summary: DataLoaderSummary
|
|
83
|
+
loaders: [DataLoaderEfficiency]
|
|
84
|
+
recommendations: [String]
|
|
85
|
+
contextStats: DataLoaderContextStats
|
|
86
|
+
timestamp: String
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type DataLoaderSummary {
|
|
90
|
+
totalLoaders: Int
|
|
91
|
+
totalRequests: Int
|
|
92
|
+
totalBatchRequests: Int
|
|
93
|
+
totalCacheHits: Int
|
|
94
|
+
totalCacheMisses: Int
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type DataLoaderEfficiency {
|
|
98
|
+
loader: String
|
|
99
|
+
hitRate: String
|
|
100
|
+
batchEfficiency: String
|
|
101
|
+
averageBatchSize: String
|
|
102
|
+
totalRequests: Int
|
|
103
|
+
lastActivity: String
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type DataLoaderContextStats {
|
|
107
|
+
template: DataLoaderCacheInfo
|
|
108
|
+
timestamp: String
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type DataLoaderCacheInfo {
|
|
112
|
+
byId: DataLoaderCacheDetail
|
|
113
|
+
byName: DataLoaderCacheDetail
|
|
114
|
+
searchByName: DataLoaderCacheDetail
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type DataLoaderCacheDetail {
|
|
118
|
+
cacheMap: Int
|
|
119
|
+
name: String
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type DataLoaderActionResult {
|
|
123
|
+
success: Boolean
|
|
124
|
+
message: String
|
|
125
|
+
timestamp: String
|
|
126
|
+
}
|
|
127
|
+
`,
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Express 路由:DataLoader 调试接口
|
|
131
|
+
*/
|
|
132
|
+
function createDataLoaderDebugRoutes(app) {
|
|
133
|
+
// 仅在开发环境中启用调试接口
|
|
134
|
+
if (process.env.NODE_ENV === "development") {
|
|
135
|
+
// 获取 DataLoader 统计信息
|
|
136
|
+
app.get("/debug/dataloader/stats", (_req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
const health = (0, dataloader_monitor_1.getDataLoaderHealth)();
|
|
139
|
+
const allStats = dataloader_monitor_1.DataLoaderMonitor.getAllStats();
|
|
140
|
+
res.json({
|
|
141
|
+
health,
|
|
142
|
+
stats: allStats,
|
|
143
|
+
timestamp: new Date().toISOString(),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
res.status(500).json({
|
|
148
|
+
error: "Failed to get DataLoader stats",
|
|
149
|
+
message: error instanceof Error ? error.message : String(error),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// 重置统计信息
|
|
154
|
+
app.post("/debug/dataloader/reset", (_req, res) => {
|
|
155
|
+
try {
|
|
156
|
+
dataloader_monitor_1.DataLoaderMonitor.resetStats();
|
|
157
|
+
res.json({
|
|
158
|
+
success: true,
|
|
159
|
+
message: "DataLoader stats reset successfully",
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
res.status(500).json({
|
|
165
|
+
error: "Failed to reset DataLoader stats",
|
|
166
|
+
message: error instanceof Error ? error.message : String(error),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// 打印性能报告
|
|
171
|
+
app.post("/debug/dataloader/report", (_req, res) => {
|
|
172
|
+
try {
|
|
173
|
+
dataloader_monitor_1.DataLoaderMonitor.printPerformanceReport();
|
|
174
|
+
res.json({
|
|
175
|
+
success: true,
|
|
176
|
+
message: "Performance report printed to console",
|
|
177
|
+
timestamp: new Date().toISOString(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
res.status(500).json({
|
|
182
|
+
error: "Failed to print performance report",
|
|
183
|
+
message: error instanceof Error ? error.message : String(error),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
console.log("🔧 DataLoader 调试接口已启用:");
|
|
188
|
+
console.log(" GET /debug/dataloader/stats - 获取统计信息");
|
|
189
|
+
console.log(" POST /debug/dataloader/reset - 重置统计信息");
|
|
190
|
+
console.log(" POST /debug/dataloader/report - 打印性能报告");
|
|
191
|
+
}
|
|
192
|
+
}
|
package/lib/server/graphql.js
CHANGED
|
@@ -9,6 +9,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const lodash_1 = __importDefault(require("lodash"));
|
|
10
10
|
const path_1 = require("path");
|
|
11
11
|
const date_1 = __importDefault(require("./plugins/date"));
|
|
12
|
+
const dataloaders_1 = require("./dataloaders");
|
|
12
13
|
// 缓存已生成的 schema 和 resolvers
|
|
13
14
|
let cachedSchema = null;
|
|
14
15
|
let cachedResolvers = null;
|
|
@@ -177,6 +178,14 @@ const handler = (command) => {
|
|
|
177
178
|
return (0, express_1.createHandler)({
|
|
178
179
|
schema: (0, graphql_1.buildSchema)(schemaStr),
|
|
179
180
|
rootValue: resolversV,
|
|
181
|
+
// 为每个请求创建新的 DataLoader 上下文,确保请求隔离和缓存正确性
|
|
182
|
+
context: (_req, _params) => {
|
|
183
|
+
const context = (0, dataloaders_1.createDataLoaderContext)();
|
|
184
|
+
if (command === "dev") {
|
|
185
|
+
console.log("🚀 GraphQL DataLoader 上下文已创建");
|
|
186
|
+
}
|
|
187
|
+
return context;
|
|
188
|
+
},
|
|
180
189
|
});
|
|
181
190
|
};
|
|
182
191
|
exports.default = handler;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DataLoaderContext } from "../dataloaders";
|
|
2
|
+
/**
|
|
3
|
+
* DataLoader 性能监控和调试工具
|
|
4
|
+
*/
|
|
5
|
+
export declare class DataLoaderMonitor {
|
|
6
|
+
private static requestStats;
|
|
7
|
+
/**
|
|
8
|
+
* 记录 DataLoader 请求统计
|
|
9
|
+
*/
|
|
10
|
+
static recordRequest(loaderName: string, isBatch: boolean, batchSize?: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* 记录缓存命中/未命中
|
|
13
|
+
*/
|
|
14
|
+
static recordCacheResult(loaderName: string, isHit: boolean): void;
|
|
15
|
+
/**
|
|
16
|
+
* 获取所有 DataLoader 统计信息
|
|
17
|
+
*/
|
|
18
|
+
static getAllStats(): {
|
|
19
|
+
summary: {
|
|
20
|
+
totalLoaders: number;
|
|
21
|
+
totalRequests: number;
|
|
22
|
+
totalBatchRequests: number;
|
|
23
|
+
totalCacheHits: number;
|
|
24
|
+
totalCacheMisses: number;
|
|
25
|
+
};
|
|
26
|
+
loaders: {
|
|
27
|
+
[k: string]: {
|
|
28
|
+
totalRequests: number;
|
|
29
|
+
batchRequests: number;
|
|
30
|
+
cacheHits: number;
|
|
31
|
+
cacheMisses: number;
|
|
32
|
+
averageBatchSize: number;
|
|
33
|
+
lastActivity: Date;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
generatedAt: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* 获取 DataLoader 缓存效率报告
|
|
40
|
+
*/
|
|
41
|
+
static getCacheEfficiencyReport(): any[];
|
|
42
|
+
/**
|
|
43
|
+
* 重置统计信息
|
|
44
|
+
*/
|
|
45
|
+
static resetStats(): void;
|
|
46
|
+
/**
|
|
47
|
+
* 打印性能报告到控制台
|
|
48
|
+
*/
|
|
49
|
+
static printPerformanceReport(): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 创建 DataLoader 性能中间件
|
|
53
|
+
*/
|
|
54
|
+
export declare function createDataLoaderPerformanceMiddleware(): (req: any, res: any, next: any) => void;
|
|
55
|
+
/**
|
|
56
|
+
* 获取 DataLoader 健康状态
|
|
57
|
+
*/
|
|
58
|
+
export declare function getDataLoaderHealth(context?: DataLoaderContext): {
|
|
59
|
+
status: string;
|
|
60
|
+
score: number;
|
|
61
|
+
summary: {
|
|
62
|
+
totalLoaders: number;
|
|
63
|
+
totalRequests: number;
|
|
64
|
+
totalBatchRequests: number;
|
|
65
|
+
totalCacheHits: number;
|
|
66
|
+
totalCacheMisses: number;
|
|
67
|
+
};
|
|
68
|
+
loaders: any[];
|
|
69
|
+
recommendations: string[];
|
|
70
|
+
contextStats: {
|
|
71
|
+
template: {
|
|
72
|
+
byId: {
|
|
73
|
+
cacheMap: any;
|
|
74
|
+
name: string;
|
|
75
|
+
};
|
|
76
|
+
byName: {
|
|
77
|
+
cacheMap: any;
|
|
78
|
+
name: string;
|
|
79
|
+
};
|
|
80
|
+
searchByName: {
|
|
81
|
+
cacheMap: any;
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
timestamp: string;
|
|
86
|
+
} | null;
|
|
87
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DataLoaderMonitor = void 0;
|
|
4
|
+
exports.createDataLoaderPerformanceMiddleware = createDataLoaderPerformanceMiddleware;
|
|
5
|
+
exports.getDataLoaderHealth = getDataLoaderHealth;
|
|
6
|
+
const dataloaders_1 = require("../dataloaders");
|
|
7
|
+
/**
|
|
8
|
+
* DataLoader 性能监控和调试工具
|
|
9
|
+
*/
|
|
10
|
+
class DataLoaderMonitor {
|
|
11
|
+
/**
|
|
12
|
+
* 记录 DataLoader 请求统计
|
|
13
|
+
*/
|
|
14
|
+
static recordRequest(loaderName, isBatch, batchSize) {
|
|
15
|
+
const stats = this.requestStats.get(loaderName) || {
|
|
16
|
+
totalRequests: 0,
|
|
17
|
+
batchRequests: 0,
|
|
18
|
+
cacheHits: 0,
|
|
19
|
+
cacheMisses: 0,
|
|
20
|
+
averageBatchSize: 0,
|
|
21
|
+
lastActivity: new Date(),
|
|
22
|
+
};
|
|
23
|
+
stats.totalRequests++;
|
|
24
|
+
if (isBatch) {
|
|
25
|
+
stats.batchRequests++;
|
|
26
|
+
if (batchSize) {
|
|
27
|
+
stats.averageBatchSize = (stats.averageBatchSize + batchSize) / 2;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
stats.lastActivity = new Date();
|
|
31
|
+
this.requestStats.set(loaderName, stats);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 记录缓存命中/未命中
|
|
35
|
+
*/
|
|
36
|
+
static recordCacheResult(loaderName, isHit) {
|
|
37
|
+
const stats = this.requestStats.get(loaderName);
|
|
38
|
+
if (stats) {
|
|
39
|
+
if (isHit) {
|
|
40
|
+
stats.cacheHits++;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
stats.cacheMisses++;
|
|
44
|
+
}
|
|
45
|
+
this.requestStats.set(loaderName, stats);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取所有 DataLoader 统计信息
|
|
50
|
+
*/
|
|
51
|
+
static getAllStats() {
|
|
52
|
+
const stats = Object.fromEntries(this.requestStats);
|
|
53
|
+
return {
|
|
54
|
+
summary: {
|
|
55
|
+
totalLoaders: this.requestStats.size,
|
|
56
|
+
totalRequests: Array.from(this.requestStats.values()).reduce((sum, stat) => sum + stat.totalRequests, 0),
|
|
57
|
+
totalBatchRequests: Array.from(this.requestStats.values()).reduce((sum, stat) => sum + stat.batchRequests, 0),
|
|
58
|
+
totalCacheHits: Array.from(this.requestStats.values()).reduce((sum, stat) => sum + stat.cacheHits, 0),
|
|
59
|
+
totalCacheMisses: Array.from(this.requestStats.values()).reduce((sum, stat) => sum + stat.cacheMisses, 0),
|
|
60
|
+
},
|
|
61
|
+
loaders: stats,
|
|
62
|
+
generatedAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 获取 DataLoader 缓存效率报告
|
|
67
|
+
*/
|
|
68
|
+
static getCacheEfficiencyReport() {
|
|
69
|
+
const report = [];
|
|
70
|
+
this.requestStats.forEach((stats, loaderName) => {
|
|
71
|
+
const totalCacheRequests = stats.cacheHits + stats.cacheMisses;
|
|
72
|
+
const hitRate = totalCacheRequests > 0 ? (stats.cacheHits / totalCacheRequests) * 100 : 0;
|
|
73
|
+
const batchEfficiency = stats.totalRequests > 0 ? (stats.batchRequests / stats.totalRequests) * 100 : 0;
|
|
74
|
+
report.push({
|
|
75
|
+
loader: loaderName,
|
|
76
|
+
hitRate: `${hitRate.toFixed(2)}%`,
|
|
77
|
+
batchEfficiency: `${batchEfficiency.toFixed(2)}%`,
|
|
78
|
+
averageBatchSize: stats.averageBatchSize.toFixed(2),
|
|
79
|
+
totalRequests: stats.totalRequests,
|
|
80
|
+
lastActivity: stats.lastActivity.toISOString(),
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
return report.sort((a, b) => b.totalRequests - a.totalRequests);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 重置统计信息
|
|
87
|
+
*/
|
|
88
|
+
static resetStats() {
|
|
89
|
+
this.requestStats.clear();
|
|
90
|
+
console.log("📊 DataLoader 统计信息已重置");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 打印性能报告到控制台
|
|
94
|
+
*/
|
|
95
|
+
static printPerformanceReport() {
|
|
96
|
+
const stats = this.getAllStats();
|
|
97
|
+
const efficiency = this.getCacheEfficiencyReport();
|
|
98
|
+
console.log("\n📊 DataLoader 性能报告");
|
|
99
|
+
console.log("========================");
|
|
100
|
+
console.log(`总加载器数量: ${stats.summary.totalLoaders}`);
|
|
101
|
+
console.log(`总请求数: ${stats.summary.totalRequests}`);
|
|
102
|
+
console.log(`批量请求数: ${stats.summary.totalBatchRequests}`);
|
|
103
|
+
console.log(`缓存命中数: ${stats.summary.totalCacheHits}`);
|
|
104
|
+
console.log(`缓存未命中数: ${stats.summary.totalCacheMisses}`);
|
|
105
|
+
if (stats.summary.totalCacheHits + stats.summary.totalCacheMisses > 0) {
|
|
106
|
+
const overallHitRate = ((stats.summary.totalCacheHits / (stats.summary.totalCacheHits + stats.summary.totalCacheMisses)) *
|
|
107
|
+
100).toFixed(2);
|
|
108
|
+
console.log(`总体缓存命中率: ${overallHitRate}%`);
|
|
109
|
+
}
|
|
110
|
+
console.log("\n各加载器效率:");
|
|
111
|
+
efficiency.forEach((loader) => {
|
|
112
|
+
console.log(` ${loader.loader}:`);
|
|
113
|
+
console.log(` 缓存命中率: ${loader.hitRate}`);
|
|
114
|
+
console.log(` 批量效率: ${loader.batchEfficiency}`);
|
|
115
|
+
console.log(` 平均批量大小: ${loader.averageBatchSize}`);
|
|
116
|
+
console.log(` 总请求数: ${loader.totalRequests}`);
|
|
117
|
+
});
|
|
118
|
+
console.log("========================\n");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.DataLoaderMonitor = DataLoaderMonitor;
|
|
122
|
+
DataLoaderMonitor.requestStats = new Map();
|
|
123
|
+
/**
|
|
124
|
+
* 创建 DataLoader 性能中间件
|
|
125
|
+
*/
|
|
126
|
+
function createDataLoaderPerformanceMiddleware() {
|
|
127
|
+
return (req, res, next) => {
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
// 在响应结束时记录性能数据
|
|
130
|
+
res.on("finish", () => {
|
|
131
|
+
const duration = Date.now() - startTime;
|
|
132
|
+
if (req.body?.query) {
|
|
133
|
+
const isQuery = req.body.query.trim().toLowerCase().startsWith("query");
|
|
134
|
+
const isMutation = req.body.query.trim().toLowerCase().startsWith("mutation");
|
|
135
|
+
if (isQuery || isMutation) {
|
|
136
|
+
console.log(`🚀 GraphQL ${isQuery ? "Query" : "Mutation"} 执行时间: ${duration}ms`);
|
|
137
|
+
// 每10个请求打印一次性能报告
|
|
138
|
+
if (Math.random() < 0.1) {
|
|
139
|
+
DataLoaderMonitor.printPerformanceReport();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
next();
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 获取 DataLoader 健康状态
|
|
149
|
+
*/
|
|
150
|
+
function getDataLoaderHealth(context) {
|
|
151
|
+
const stats = DataLoaderMonitor.getAllStats();
|
|
152
|
+
const efficiency = DataLoaderMonitor.getCacheEfficiencyReport();
|
|
153
|
+
// 计算健康分数
|
|
154
|
+
let healthScore = 100;
|
|
155
|
+
efficiency.forEach((loader) => {
|
|
156
|
+
const hitRate = parseFloat(loader.hitRate);
|
|
157
|
+
const batchEfficiency = parseFloat(loader.batchEfficiency);
|
|
158
|
+
// 缓存命中率低于50%扣分
|
|
159
|
+
if (hitRate < 50) {
|
|
160
|
+
healthScore -= 10;
|
|
161
|
+
}
|
|
162
|
+
// 批量效率低于30%扣分
|
|
163
|
+
if (batchEfficiency < 30) {
|
|
164
|
+
healthScore -= 15;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
const status = healthScore >= 80 ? "healthy" : healthScore >= 60 ? "warning" : "critical";
|
|
168
|
+
return {
|
|
169
|
+
status,
|
|
170
|
+
score: Math.max(0, healthScore),
|
|
171
|
+
summary: stats.summary,
|
|
172
|
+
loaders: efficiency,
|
|
173
|
+
recommendations: generateRecommendations(efficiency),
|
|
174
|
+
contextStats: context ? (0, dataloaders_1.getDataLoaderStats)(context) : null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 生成性能优化建议
|
|
179
|
+
*/
|
|
180
|
+
function generateRecommendations(efficiency) {
|
|
181
|
+
const recommendations = [];
|
|
182
|
+
efficiency.forEach((loader) => {
|
|
183
|
+
const hitRate = parseFloat(loader.hitRate);
|
|
184
|
+
const batchEfficiency = parseFloat(loader.batchEfficiency);
|
|
185
|
+
if (hitRate < 50) {
|
|
186
|
+
recommendations.push(`${loader.loader}: 考虑增加缓存时间或优化查询模式以提高缓存命中率`);
|
|
187
|
+
}
|
|
188
|
+
if (batchEfficiency < 30) {
|
|
189
|
+
recommendations.push(`${loader.loader}: 考虑调整 batchScheduleFn 延迟时间以提高批量效率`);
|
|
190
|
+
}
|
|
191
|
+
if (parseFloat(loader.averageBatchSize) < 2) {
|
|
192
|
+
recommendations.push(`${loader.loader}: 批量大小较小,可能需要优化查询时机`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
if (recommendations.length === 0) {
|
|
196
|
+
recommendations.push("DataLoader 性能表现良好,无需优化");
|
|
197
|
+
}
|
|
198
|
+
return recommendations;
|
|
199
|
+
}
|