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
package/server/apis/sso.js
CHANGED
|
@@ -8,8 +8,12 @@ const getLoginCredentials = () => {
|
|
|
8
8
|
const username = process.env.LOGIN_USERNAME || 'admin'
|
|
9
9
|
let passwordHash = process.env.LOGIN_PASSWORD_HASH
|
|
10
10
|
|
|
11
|
+
console.warn('[SSO] getLoginCredentials - username:', username)
|
|
12
|
+
console.warn('[SSO] getLoginCredentials - passwordHash from env:', passwordHash ? `${passwordHash.substring(0, 20)}...` : 'undefined')
|
|
13
|
+
|
|
11
14
|
// 如果环境变量被截断,尝试手动读取 .env 文件
|
|
12
15
|
if (!passwordHash || passwordHash.length < 60) {
|
|
16
|
+
console.warn('[SSO] passwordHash too short or missing, trying to read from .env')
|
|
13
17
|
try {
|
|
14
18
|
const fs = require('fs')
|
|
15
19
|
const path = require('path')
|
|
@@ -21,16 +25,17 @@ const getLoginCredentials = () => {
|
|
|
21
25
|
if (line.startsWith('LOGIN_PASSWORD_HASH=')) {
|
|
22
26
|
// 移除引号和前缀
|
|
23
27
|
passwordHash = line.replace('LOGIN_PASSWORD_HASH=', '').replace(/"/g, '').trim()
|
|
28
|
+
console.warn('[SSO] Found passwordHash in .env:', `${passwordHash.substring(0, 20)}...`)
|
|
24
29
|
break
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
} catch (error) {
|
|
28
|
-
console.
|
|
33
|
+
console.warn('[SSO] 读取 .env 文件失败:', error.message)
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
if (!passwordHash) {
|
|
33
|
-
console.warn('⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希')
|
|
38
|
+
console.warn('[SSO] ⚠️ 警告: LOGIN_PASSWORD_HASH 环境变量未设置,使用默认密码哈希')
|
|
34
39
|
// 默认密码 "admin123" 的哈希值(仅用于开发环境)
|
|
35
40
|
return {
|
|
36
41
|
username,
|
|
@@ -38,6 +43,7 @@ const getLoginCredentials = () => {
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
console.warn('[SSO] getLoginCredentials - final passwordHash:', `${passwordHash.substring(0, 20)}...`)
|
|
41
47
|
return { username, passwordHash }
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -75,15 +81,27 @@ router.get('/ticketCheck', async (req, res) => {
|
|
|
75
81
|
const { query } = req
|
|
76
82
|
const { name } = query
|
|
77
83
|
|
|
84
|
+
console.warn('[SSO] ticketCheck called, name:', name)
|
|
85
|
+
|
|
78
86
|
try {
|
|
79
87
|
// 使用 Buffer 解码 Base64 字符串,然后使用 decodeURIComponent 处理特殊字符
|
|
80
88
|
const decodedBase64 = Buffer.from(name, 'base64').toString('utf-8')
|
|
89
|
+
console.warn('[SSO] Decoded Base64:', decodedBase64)
|
|
90
|
+
|
|
81
91
|
const decodedName = decodeURIComponent(decodedBase64)
|
|
92
|
+
console.warn('[SSO] Decoded name:', decodedName)
|
|
93
|
+
|
|
82
94
|
const [inputUsername, inputPassword] = decodedName.split(',')
|
|
95
|
+
console.warn('[SSO] Input username:', inputUsername, 'password length:', inputPassword?.length)
|
|
96
|
+
|
|
97
|
+
const { username: expectedUsername, passwordHash } = getLoginCredentials()
|
|
98
|
+
console.warn('[SSO] Expected username:', expectedUsername, 'passwordHash length:', passwordHash?.length)
|
|
83
99
|
|
|
84
100
|
const isValid = await validateCredentials(inputUsername, inputPassword)
|
|
101
|
+
console.warn('[SSO] Credentials valid:', isValid)
|
|
85
102
|
|
|
86
103
|
if (isValid) {
|
|
104
|
+
console.warn('[SSO] Login successful for user:', inputUsername)
|
|
87
105
|
res.json({
|
|
88
106
|
name: 'ticketCheck',
|
|
89
107
|
query,
|
|
@@ -97,7 +115,7 @@ router.get('/ticketCheck', async (req, res) => {
|
|
|
97
115
|
})
|
|
98
116
|
} else {
|
|
99
117
|
// 记录失败的登录尝试(用于安全审计)
|
|
100
|
-
console.
|
|
118
|
+
console.warn(`[SSO] 登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}, IP: ${req.ip}`)
|
|
101
119
|
|
|
102
120
|
res.json({
|
|
103
121
|
name: 'ticketCheck',
|
|
@@ -107,7 +125,7 @@ router.get('/ticketCheck', async (req, res) => {
|
|
|
107
125
|
})
|
|
108
126
|
}
|
|
109
127
|
} catch (error) {
|
|
110
|
-
console.
|
|
128
|
+
console.warn('[SSO] 登录验证出错:', error)
|
|
111
129
|
res.status(500).json({
|
|
112
130
|
name: 'ticketCheck',
|
|
113
131
|
returnCode: -1,
|
|
@@ -46,35 +46,86 @@ module.exports = {
|
|
|
46
46
|
throw error;
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
// 根据ID获取模板 - 使用 DataLoader 优化
|
|
51
|
+
templateGet: async ({ id }, context) => {
|
|
51
52
|
try {
|
|
52
53
|
validateId(id);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
const values = [id];
|
|
56
|
-
|
|
57
|
-
console.log('根据ID查询模板:', { sql, values });
|
|
55
|
+
console.log('🚀 使用 DataLoader 根据ID查询模板:', { id });
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
// 使用 DataLoader 批量加载,自动去重和缓存
|
|
58
|
+
const result = await context.dataloaders.template.byId.load(Number(id));
|
|
60
59
|
|
|
61
|
-
if (
|
|
60
|
+
if (!result) {
|
|
62
61
|
throw new Error(`ID为 ${id} 的模板不存在`);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
return
|
|
64
|
+
return result;
|
|
66
65
|
} catch (error) {
|
|
67
66
|
console.error('获取模板失败:', error.message);
|
|
68
67
|
throw error;
|
|
69
68
|
}
|
|
70
69
|
},
|
|
71
70
|
|
|
72
|
-
//
|
|
73
|
-
|
|
71
|
+
// 批量获取模板 - 新增方法,展示 DataLoader 批量能力
|
|
72
|
+
templateBatchGet: async ({ ids }, context) => {
|
|
73
|
+
try {
|
|
74
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
75
|
+
throw new Error('ID列表不能为空');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 验证所有ID
|
|
79
|
+
const validIds = ids.map(id => {
|
|
80
|
+
validateId(id);
|
|
81
|
+
return Number(id);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
console.log('🚀 使用 DataLoader 批量查询模板:', { ids: validIds });
|
|
85
|
+
|
|
86
|
+
// DataLoader 自动批量处理,一次查询获取所有数据
|
|
87
|
+
const results = await context.dataloaders.template.byId.loadMany(validIds);
|
|
88
|
+
|
|
89
|
+
// 过滤掉 null 结果(未找到的记录)
|
|
90
|
+
return results.filter(result => result !== null && !(result instanceof Error));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('批量获取模板失败:', error.message);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// 搜索模板(分页)- 使用 DataLoader 优化搜索
|
|
98
|
+
templateSearch: async ({ page = 0, pageSize = 10, data = {} }, context) => {
|
|
74
99
|
try {
|
|
75
100
|
validatePagination(page, pageSize);
|
|
76
101
|
|
|
77
102
|
const { name } = data;
|
|
103
|
+
|
|
104
|
+
// 如果有名称搜索,尝试使用 DataLoader 搜索缓存
|
|
105
|
+
if (name && name.trim() !== '') {
|
|
106
|
+
console.log('🚀 使用 DataLoader 搜索模板:', { searchTerm: name.trim() });
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// 使用 DataLoader 进行搜索(这里会缓存搜索结果)
|
|
110
|
+
const searchResults = await context.dataloaders.template.searchByName.load(name.trim());
|
|
111
|
+
|
|
112
|
+
// 手动分页处理
|
|
113
|
+
const totalCounts = searchResults.length;
|
|
114
|
+
const startIndex = page * pageSize;
|
|
115
|
+
const endIndex = startIndex + pageSize;
|
|
116
|
+
const items = searchResults.slice(startIndex, endIndex);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
totalCounts,
|
|
120
|
+
items
|
|
121
|
+
};
|
|
122
|
+
} catch (dataLoaderError) {
|
|
123
|
+
console.warn('DataLoader 搜索失败,回退到直接查询:', dataLoaderError.message);
|
|
124
|
+
// 如果 DataLoader 失败,回退到原始查询方式
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 原始查询方式(作为备用)
|
|
78
129
|
const values = [];
|
|
79
130
|
const countValues = [];
|
|
80
131
|
|
|
@@ -91,7 +142,7 @@ module.exports = {
|
|
|
91
142
|
|
|
92
143
|
values.push(pageSize, page * pageSize);
|
|
93
144
|
|
|
94
|
-
console.log('
|
|
145
|
+
console.log('搜索模板(备用查询):', { sql, values, countSql, countValues });
|
|
95
146
|
|
|
96
147
|
return await executePaginatedQuery(sql, countSql, values, countValues);
|
|
97
148
|
} catch (error) {
|
|
@@ -100,8 +151,8 @@ module.exports = {
|
|
|
100
151
|
}
|
|
101
152
|
},
|
|
102
153
|
|
|
103
|
-
// 添加模板
|
|
104
|
-
templateAdd: async ({ data }) => {
|
|
154
|
+
// 添加模板 - 添加 DataLoader 缓存清理
|
|
155
|
+
templateAdd: async ({ data }, context) => {
|
|
105
156
|
try {
|
|
106
157
|
const { name } = data || {};
|
|
107
158
|
validateName(name);
|
|
@@ -112,7 +163,16 @@ module.exports = {
|
|
|
112
163
|
console.log('添加模板:', { sql, values });
|
|
113
164
|
|
|
114
165
|
const results = await executeQuery(sql, values);
|
|
115
|
-
|
|
166
|
+
const insertId = results.insertId;
|
|
167
|
+
|
|
168
|
+
// 预加载新数据到 DataLoader 缓存
|
|
169
|
+
if (insertId && context?.dataloaders?.template) {
|
|
170
|
+
const newTemplate = { id: insertId, name: name.trim() };
|
|
171
|
+
context.dataloaders.template.prime(insertId, newTemplate);
|
|
172
|
+
console.log('🚀 新模板已预加载到 DataLoader 缓存:', newTemplate);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return insertId;
|
|
116
176
|
} catch (error) {
|
|
117
177
|
console.error('添加模板失败:', error.message);
|
|
118
178
|
throw error;
|
|
@@ -146,8 +206,8 @@ module.exports = {
|
|
|
146
206
|
}
|
|
147
207
|
},
|
|
148
208
|
|
|
149
|
-
// 更新模板
|
|
150
|
-
templateUpdate: async ({ id, data }) => {
|
|
209
|
+
// 更新模板 - 添加 DataLoader 缓存清理
|
|
210
|
+
templateUpdate: async ({ id, data }, context) => {
|
|
151
211
|
try {
|
|
152
212
|
validateId(id);
|
|
153
213
|
const { name } = data || {};
|
|
@@ -169,6 +229,12 @@ module.exports = {
|
|
|
169
229
|
throw new Error(`ID为 ${id} 的模板不存在`);
|
|
170
230
|
}
|
|
171
231
|
|
|
232
|
+
// 清除 DataLoader 缓存,确保下次查询获取最新数据
|
|
233
|
+
if (context?.dataloaders?.template) {
|
|
234
|
+
context.dataloaders.template.clearById(Number(id));
|
|
235
|
+
console.log('🧹 已清除 DataLoader 缓存:', { id });
|
|
236
|
+
}
|
|
237
|
+
|
|
172
238
|
return true;
|
|
173
239
|
} catch (error) {
|
|
174
240
|
console.error('更新模板失败:', error.message);
|
|
@@ -176,8 +242,8 @@ module.exports = {
|
|
|
176
242
|
}
|
|
177
243
|
},
|
|
178
244
|
|
|
179
|
-
// 删除模板
|
|
180
|
-
templateDelete: async ({ id }) => {
|
|
245
|
+
// 删除模板 - 添加 DataLoader 缓存清理
|
|
246
|
+
templateDelete: async ({ id }, context) => {
|
|
181
247
|
try {
|
|
182
248
|
validateId(id);
|
|
183
249
|
|
|
@@ -192,6 +258,12 @@ module.exports = {
|
|
|
192
258
|
throw new Error(`ID为 ${id} 的模板不存在`);
|
|
193
259
|
}
|
|
194
260
|
|
|
261
|
+
// 清除 DataLoader 缓存
|
|
262
|
+
if (context?.dataloaders?.template) {
|
|
263
|
+
context.dataloaders.template.clearById(Number(id));
|
|
264
|
+
console.log('🧹 已清除 DataLoader 缓存:', { id });
|
|
265
|
+
}
|
|
266
|
+
|
|
195
267
|
return true;
|
|
196
268
|
} catch (error) {
|
|
197
269
|
console.error('删除模板失败:', error.message);
|
|
@@ -199,8 +271,8 @@ module.exports = {
|
|
|
199
271
|
}
|
|
200
272
|
},
|
|
201
273
|
|
|
202
|
-
// 批量删除模板
|
|
203
|
-
templateBatchDelete: async ({ ids }) => {
|
|
274
|
+
// 批量删除模板 - 添加 DataLoader 缓存清理
|
|
275
|
+
templateBatchDelete: async ({ ids }, context) => {
|
|
204
276
|
try {
|
|
205
277
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
206
278
|
throw new Error('批量删除的ID列表不能为空');
|
|
@@ -220,6 +292,14 @@ module.exports = {
|
|
|
220
292
|
throw new Error('没有找到要删除的模板');
|
|
221
293
|
}
|
|
222
294
|
|
|
295
|
+
// 批量清除 DataLoader 缓存
|
|
296
|
+
if (context?.dataloaders?.template) {
|
|
297
|
+
ids.forEach(id => {
|
|
298
|
+
context.dataloaders.template.clearById(Number(id));
|
|
299
|
+
});
|
|
300
|
+
console.log('🧹 已批量清除 DataLoader 缓存:', { ids });
|
|
301
|
+
}
|
|
302
|
+
|
|
223
303
|
return true;
|
|
224
304
|
} catch (error) {
|
|
225
305
|
console.error('批量删除模板失败:', error.message);
|