nsgm-cli 2.1.39 → 2.1.41
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/client/utils/i18n.ts +19 -0
- package/lib/generate_create.js +2 -2
- package/lib/generators/dataloader-generator.d.ts +1 -1
- package/lib/generators/dataloader-generator.js +19 -27
- package/lib/generators/page-generator.js +2 -2
- package/lib/server/dataloaders/index.d.ts +2 -21
- package/lib/server/dataloaders/index.js +84 -3
- package/lib/server/debug/dataloader-debug.d.ts +1 -17
- package/lib/server/utils/dataloader-monitor.d.ts +1 -17
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/server/utils/date-formatter.js +100 -0
package/client/utils/i18n.ts
CHANGED
|
@@ -62,6 +62,25 @@ export const formatDate = (date: Date | string, locale = "zh-CN") => {
|
|
|
62
62
|
}).format(dateObj);
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
// Format date and time as YYYY-MM-DD HH:mm:ss
|
|
66
|
+
export const formatDateTime = (date: Date | string) => {
|
|
67
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
68
|
+
|
|
69
|
+
// Check if date is valid
|
|
70
|
+
if (isNaN(dateObj.getTime())) {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const year = dateObj.getFullYear();
|
|
75
|
+
const month = String(dateObj.getMonth() + 1).padStart(2, "0");
|
|
76
|
+
const day = String(dateObj.getDate()).padStart(2, "0");
|
|
77
|
+
const hours = String(dateObj.getHours()).padStart(2, "0");
|
|
78
|
+
const minutes = String(dateObj.getMinutes()).padStart(2, "0");
|
|
79
|
+
const seconds = String(dateObj.getSeconds()).padStart(2, "0");
|
|
80
|
+
|
|
81
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
82
|
+
};
|
|
83
|
+
|
|
65
84
|
// Format number based on locale
|
|
66
85
|
export const formatNumber = (number: number, locale = "zh-CN") => {
|
|
67
86
|
return new Intl.NumberFormat(locale).format(number);
|
package/lib/generate_create.js
CHANGED
|
@@ -218,8 +218,8 @@ const generateDynamicFiles = (controller, action, paths, fields, dictionary) =>
|
|
|
218
218
|
fs_1.default.writeFileSync(paths.destServerModulesResolver, resolverGenerator.generate());
|
|
219
219
|
fs_1.default.writeFileSync(paths.destClientAction, serviceGenerator.generate());
|
|
220
220
|
fs_1.default.writeFileSync(paths.destPagesAction, pageGenerator.generate());
|
|
221
|
-
// 生成 DataLoader 文件
|
|
222
|
-
const dataLoaderPath = (0, path_1.resolve)(`${projectPath}/server/dataloaders/${controller}-dataloader.
|
|
221
|
+
// 生成 DataLoader 文件 (JavaScript)
|
|
222
|
+
const dataLoaderPath = (0, path_1.resolve)(`${projectPath}/server/dataloaders/${controller}-dataloader.js`);
|
|
223
223
|
(0, utils_1.mkdirSync)(path_1.default.dirname(dataLoaderPath));
|
|
224
224
|
fs_1.default.writeFileSync(dataLoaderPath, dataLoaderGenerator.generate());
|
|
225
225
|
console.log(`🚀 已生成 DataLoader 文件: ${dataLoaderPath}`);
|
|
@@ -4,34 +4,24 @@ exports.DataLoaderGenerator = void 0;
|
|
|
4
4
|
const base_generator_1 = require("./base-generator");
|
|
5
5
|
/**
|
|
6
6
|
* DataLoader生成器
|
|
7
|
-
* 自动生成对应的 DataLoader 文件
|
|
7
|
+
* 自动生成对应的 DataLoader JavaScript 文件
|
|
8
8
|
*/
|
|
9
9
|
class DataLoaderGenerator extends base_generator_1.BaseGenerator {
|
|
10
10
|
generate() {
|
|
11
11
|
const capitalizedController = this.getCapitalizedController();
|
|
12
12
|
const selectFields = this.fields.map((f) => f.name).join(", ");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import { executeQuery } from '../utils/common';
|
|
13
|
+
return `const DataLoader = require('dataloader');
|
|
14
|
+
const { executeQuery } = require('../utils/common');
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* ${capitalizedController} DataLoader
|
|
19
18
|
* 针对 ${this.controller} 表的批量数据加载器,解决 N+1 查询问题
|
|
20
19
|
*/
|
|
21
|
-
|
|
22
|
-
// 按 ID 批量加载 ${this.controller}
|
|
23
|
-
public readonly byId: DataLoader<number, any>;
|
|
24
|
-
|
|
25
|
-
// 按名称批量加载 ${this.controller}
|
|
26
|
-
public readonly byName: DataLoader<string, any>;
|
|
27
|
-
|
|
28
|
-
// 按名称模糊搜索 ${this.controller}
|
|
29
|
-
public readonly searchByName: DataLoader<string, any[]>;
|
|
30
|
-
|
|
20
|
+
class ${capitalizedController}DataLoader {
|
|
31
21
|
constructor() {
|
|
32
22
|
// 按 ID 批量加载
|
|
33
23
|
this.byId = new DataLoader(
|
|
34
|
-
async (ids
|
|
24
|
+
async (ids) => {
|
|
35
25
|
try {
|
|
36
26
|
console.log(\`🔍 DataLoader: 批量加载 \${ids.length} 个 ${this.controller} by ID\`);
|
|
37
27
|
|
|
@@ -42,7 +32,7 @@ export class ${capitalizedController}DataLoader {
|
|
|
42
32
|
|
|
43
33
|
// 确保返回顺序与输入 keys 一致,未找到的返回 null
|
|
44
34
|
return ids.map(id =>
|
|
45
|
-
results.find((row
|
|
35
|
+
results.find((row) => row.id === id) || null
|
|
46
36
|
);
|
|
47
37
|
} catch (error) {
|
|
48
38
|
console.error('DataLoader byId 批量加载失败:', error);
|
|
@@ -58,7 +48,7 @@ export class ${capitalizedController}DataLoader {
|
|
|
58
48
|
|
|
59
49
|
// 按名称批量加载
|
|
60
50
|
this.byName = new DataLoader(
|
|
61
|
-
async (names
|
|
51
|
+
async (names) => {
|
|
62
52
|
try {
|
|
63
53
|
console.log(\`🔍 DataLoader: 批量加载 \${names.length} 个 ${this.controller} by name\`);
|
|
64
54
|
|
|
@@ -69,7 +59,7 @@ export class ${capitalizedController}DataLoader {
|
|
|
69
59
|
|
|
70
60
|
// 确保返回顺序与输入 keys 一致
|
|
71
61
|
return names.map(name =>
|
|
72
|
-
results.find((row
|
|
62
|
+
results.find((row) => row.name === name) || null
|
|
73
63
|
);
|
|
74
64
|
} catch (error) {
|
|
75
65
|
console.error('DataLoader byName 批量加载失败:', error);
|
|
@@ -85,7 +75,7 @@ export class ${capitalizedController}DataLoader {
|
|
|
85
75
|
|
|
86
76
|
// 按名称模糊搜索(返回数组)
|
|
87
77
|
this.searchByName = new DataLoader(
|
|
88
|
-
async (searchTerms
|
|
78
|
+
async (searchTerms) => {
|
|
89
79
|
try {
|
|
90
80
|
console.log(\`🔍 DataLoader: 批量搜索 \${searchTerms.length} 个关键词\`);
|
|
91
81
|
|
|
@@ -116,7 +106,7 @@ export class ${capitalizedController}DataLoader {
|
|
|
116
106
|
/**
|
|
117
107
|
* 清除所有缓存
|
|
118
108
|
*/
|
|
119
|
-
clearAll()
|
|
109
|
+
clearAll() {
|
|
120
110
|
this.byId.clearAll();
|
|
121
111
|
this.byName.clearAll();
|
|
122
112
|
this.searchByName.clearAll();
|
|
@@ -126,21 +116,21 @@ export class ${capitalizedController}DataLoader {
|
|
|
126
116
|
/**
|
|
127
117
|
* 清除特定 ID 的缓存
|
|
128
118
|
*/
|
|
129
|
-
clearById(id
|
|
119
|
+
clearById(id) {
|
|
130
120
|
this.byId.clear(id);
|
|
131
121
|
}
|
|
132
122
|
|
|
133
123
|
/**
|
|
134
124
|
* 清除特定名称的缓存
|
|
135
125
|
*/
|
|
136
|
-
clearByName(name
|
|
126
|
+
clearByName(name) {
|
|
137
127
|
this.byName.clear(name);
|
|
138
128
|
}
|
|
139
129
|
|
|
140
130
|
/**
|
|
141
131
|
* 预加载数据到缓存
|
|
142
132
|
*/
|
|
143
|
-
prime(id
|
|
133
|
+
prime(id, data) {
|
|
144
134
|
this.byId.prime(id, data);
|
|
145
135
|
if (data && data.name) {
|
|
146
136
|
this.byName.prime(data.name, data);
|
|
@@ -171,9 +161,11 @@ export class ${capitalizedController}DataLoader {
|
|
|
171
161
|
/**
|
|
172
162
|
* 创建 ${capitalizedController} DataLoader 实例
|
|
173
163
|
*/
|
|
174
|
-
|
|
164
|
+
function create${capitalizedController}DataLoader() {
|
|
175
165
|
return new ${capitalizedController}DataLoader();
|
|
176
|
-
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = { ${capitalizedController}DataLoader, create${capitalizedController}DataLoader };`;
|
|
177
169
|
}
|
|
178
170
|
/**
|
|
179
171
|
* 生成外键 DataLoader
|
|
@@ -190,7 +182,7 @@ export function create${capitalizedController}DataLoader(): ${capitalizedControl
|
|
|
190
182
|
return `
|
|
191
183
|
// 按 ${fk.name} 批量加载相关的 ${this.controller}
|
|
192
184
|
this.by${capitalizedRelated}Id = new DataLoader(
|
|
193
|
-
async (${fk.name}s
|
|
185
|
+
async (${fk.name}s) => {
|
|
194
186
|
try {
|
|
195
187
|
console.log(\`🔍 DataLoader: 批量加载 \${${fk.name}s.length} 个 ${this.controller} by ${fk.name}\`);
|
|
196
188
|
|
|
@@ -201,7 +193,7 @@ export function create${capitalizedController}DataLoader(): ${capitalizedControl
|
|
|
201
193
|
|
|
202
194
|
// 按外键分组
|
|
203
195
|
return ${fk.name}s.map(${fk.name} =>
|
|
204
|
-
results.filter((row
|
|
196
|
+
results.filter((row) => row.${fk.name} === ${fk.name})
|
|
205
197
|
);
|
|
206
198
|
} catch (error) {
|
|
207
199
|
console.error('DataLoader by${capitalizedRelated}Id 批量加载失败:', error);
|
|
@@ -48,7 +48,7 @@ import { get${capitalizedController}Service } from '@/service/${this.controller}
|
|
|
48
48
|
import { RootState, AppDispatch } from '@/redux/store'
|
|
49
49
|
import _ from 'lodash'
|
|
50
50
|
import { useTranslation } from 'next-i18next'
|
|
51
|
-
import { getAntdLocale } from '@/utils/i18n'
|
|
51
|
+
import { getAntdLocale, formatDateTime } from '@/utils/i18n'
|
|
52
52
|
import { useRouter } from 'next/router'
|
|
53
53
|
import { handleXSS, checkModalObj } from '@/utils/common'
|
|
54
54
|
import { UploadOutlined } from '@ant-design/icons'
|
|
@@ -501,7 +501,7 @@ export default Page`;
|
|
|
501
501
|
}
|
|
502
502
|
// 根据字段类型设置特定属性
|
|
503
503
|
if (field.type === "timestamp" || field.type === "date" || field.type === "datetime") {
|
|
504
|
-
column += `,\n render: (text: string) => text ?
|
|
504
|
+
column += `,\n render: (text: string) => text ? formatDateTime(text) : '-'`;
|
|
505
505
|
}
|
|
506
506
|
else if (field.type === "integer" || field.type === "decimal") {
|
|
507
507
|
column += `,\n align: 'center' as const`;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { TemplateDataLoader } from "./template-dataloader";
|
|
2
1
|
/**
|
|
3
2
|
* DataLoader 上下文接口
|
|
4
3
|
*/
|
|
5
4
|
export interface DataLoaderContext extends Record<string, unknown> {
|
|
6
|
-
dataloaders:
|
|
7
|
-
template: TemplateDataLoader;
|
|
8
|
-
};
|
|
5
|
+
dataloaders: Record<string, any>;
|
|
9
6
|
}
|
|
10
7
|
/**
|
|
11
8
|
* 创建 DataLoader 上下文
|
|
@@ -15,23 +12,7 @@ export declare function createDataLoaderContext(): DataLoaderContext;
|
|
|
15
12
|
/**
|
|
16
13
|
* DataLoader 统计信息
|
|
17
14
|
*/
|
|
18
|
-
export declare function getDataLoaderStats(context: DataLoaderContext):
|
|
19
|
-
template: {
|
|
20
|
-
byId: {
|
|
21
|
-
cacheMap: any;
|
|
22
|
-
name: string;
|
|
23
|
-
};
|
|
24
|
-
byName: {
|
|
25
|
-
cacheMap: any;
|
|
26
|
-
name: string;
|
|
27
|
-
};
|
|
28
|
-
searchByName: {
|
|
29
|
-
cacheMap: any;
|
|
30
|
-
name: string;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
timestamp: string;
|
|
34
|
-
};
|
|
15
|
+
export declare function getDataLoaderStats(context: DataLoaderContext): Record<string, any>;
|
|
35
16
|
/**
|
|
36
17
|
* 清除所有 DataLoader 缓存
|
|
37
18
|
*/
|
|
@@ -1,16 +1,79 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.createDataLoaderContext = createDataLoaderContext;
|
|
4
7
|
exports.getDataLoaderStats = getDataLoaderStats;
|
|
5
8
|
exports.clearAllDataLoaderCache = clearAllDataLoaderCache;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = require("path");
|
|
6
11
|
const template_dataloader_1 = require("./template-dataloader");
|
|
12
|
+
/**
|
|
13
|
+
* 加载项目中的自定义 DataLoader
|
|
14
|
+
* 只加载编译后的 JavaScript 文件 (*.js)
|
|
15
|
+
*/
|
|
16
|
+
function loadProjectDataLoaders() {
|
|
17
|
+
const projectDataLoadersPath = (0, path_1.join)(process.cwd(), "server", "dataloaders");
|
|
18
|
+
const dataLoaders = {};
|
|
19
|
+
if (!fs_1.default.existsSync(projectDataLoadersPath)) {
|
|
20
|
+
return dataLoaders;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const files = fs_1.default.readdirSync(projectDataLoadersPath);
|
|
24
|
+
let loadedCount = 0;
|
|
25
|
+
files.forEach((file) => {
|
|
26
|
+
// 只加载 JavaScript dataloader 文件:*-dataloader.js
|
|
27
|
+
const match = file.match(/^([a-z_-]+)-dataloader\.js$/i);
|
|
28
|
+
if (match) {
|
|
29
|
+
const moduleName = match[1]; // 提取模块名,如 'product'
|
|
30
|
+
const filePath = (0, path_1.join)(projectDataLoadersPath, file);
|
|
31
|
+
try {
|
|
32
|
+
// 清除 require 缓存,确保每次都能重新加载
|
|
33
|
+
delete require.cache[require.resolve(filePath)];
|
|
34
|
+
// 加载 JavaScript DataLoader 文件
|
|
35
|
+
const loaderModule = require(filePath);
|
|
36
|
+
// 获取 DataLoader 类的构造函数或工厂函数
|
|
37
|
+
const DataLoaderClass = loaderModule.default || loaderModule;
|
|
38
|
+
if (typeof DataLoaderClass === "function") {
|
|
39
|
+
const instance = new DataLoaderClass();
|
|
40
|
+
dataLoaders[moduleName] = instance;
|
|
41
|
+
loadedCount++;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// 尝试工厂函数
|
|
45
|
+
const factoryName = `create${moduleName.charAt(0).toUpperCase() + moduleName.slice(1)}DataLoader`;
|
|
46
|
+
const createFunc = loaderModule[factoryName] || loaderModule.createProductDataLoader;
|
|
47
|
+
if (typeof createFunc === "function") {
|
|
48
|
+
dataLoaders[moduleName] = createFunc();
|
|
49
|
+
loadedCount++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.warn(`⚠️ 加载 DataLoader 失败: ${file}`, error.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (loadedCount > 0) {
|
|
59
|
+
console.log(`📦 共加载 ${loadedCount} 个自定义 DataLoader: ${Object.keys(dataLoaders).join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
return dataLoaders;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.warn("⚠️ 加载项目 DataLoader 时出错:", error);
|
|
65
|
+
return dataLoaders;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
7
68
|
/**
|
|
8
69
|
* 创建 DataLoader 上下文
|
|
9
70
|
* 每个 GraphQL 请求都会创建新的 DataLoader 实例,确保请求隔离
|
|
10
71
|
*/
|
|
11
72
|
function createDataLoaderContext() {
|
|
73
|
+
const projectDataLoaders = loadProjectDataLoaders();
|
|
12
74
|
return {
|
|
13
75
|
dataloaders: {
|
|
76
|
+
...projectDataLoaders,
|
|
14
77
|
template: (0, template_dataloader_1.createTemplateDataLoader)(),
|
|
15
78
|
},
|
|
16
79
|
};
|
|
@@ -19,15 +82,33 @@ function createDataLoaderContext() {
|
|
|
19
82
|
* DataLoader 统计信息
|
|
20
83
|
*/
|
|
21
84
|
function getDataLoaderStats(context) {
|
|
22
|
-
|
|
23
|
-
template: context.dataloaders.template.getStats(),
|
|
85
|
+
const stats = {
|
|
24
86
|
timestamp: new Date().toISOString(),
|
|
25
87
|
};
|
|
88
|
+
if (context.dataloaders) {
|
|
89
|
+
Object.keys(context.dataloaders).forEach((key) => {
|
|
90
|
+
const loader = context.dataloaders[key];
|
|
91
|
+
if (loader && typeof loader.getStats === "function") {
|
|
92
|
+
stats[key] = loader.getStats();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
stats[key] = { name: key, status: "no stats" };
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return stats;
|
|
26
100
|
}
|
|
27
101
|
/**
|
|
28
102
|
* 清除所有 DataLoader 缓存
|
|
29
103
|
*/
|
|
30
104
|
function clearAllDataLoaderCache(context) {
|
|
31
|
-
context.dataloaders
|
|
105
|
+
if (context.dataloaders) {
|
|
106
|
+
Object.keys(context.dataloaders).forEach((key) => {
|
|
107
|
+
const loader = context.dataloaders[key];
|
|
108
|
+
if (loader && typeof loader.clearAll === "function") {
|
|
109
|
+
loader.clearAll();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
32
113
|
console.log("🧹 所有 DataLoader 缓存已清空");
|
|
33
114
|
}
|
|
@@ -19,23 +19,7 @@ export declare const dataLoaderStatsResolver: {
|
|
|
19
19
|
};
|
|
20
20
|
loaders: any[];
|
|
21
21
|
recommendations: string[];
|
|
22
|
-
contextStats:
|
|
23
|
-
template: {
|
|
24
|
-
byId: {
|
|
25
|
-
cacheMap: any;
|
|
26
|
-
name: string;
|
|
27
|
-
};
|
|
28
|
-
byName: {
|
|
29
|
-
cacheMap: any;
|
|
30
|
-
name: string;
|
|
31
|
-
};
|
|
32
|
-
searchByName: {
|
|
33
|
-
cacheMap: any;
|
|
34
|
-
name: string;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
timestamp: string;
|
|
38
|
-
} | null;
|
|
22
|
+
contextStats: Record<string, any> | null;
|
|
39
23
|
timestamp: string;
|
|
40
24
|
}>;
|
|
41
25
|
resetDataLoaderStats: () => Promise<{
|
|
@@ -67,21 +67,5 @@ export declare function getDataLoaderHealth(context?: DataLoaderContext): {
|
|
|
67
67
|
};
|
|
68
68
|
loaders: any[];
|
|
69
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;
|
|
70
|
+
contextStats: Record<string, any> | null;
|
|
87
71
|
};
|