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.
Files changed (41) hide show
  1. package/README.md +40 -0
  2. package/client/components/Button.tsx +0 -2
  3. package/client/layout/index.tsx +2 -4
  4. package/client/utils/common.ts +12 -10
  5. package/client/utils/fetch.ts +1 -1
  6. package/client/utils/menu.tsx +0 -1
  7. package/client/utils/sso.ts +13 -3
  8. package/generation/client/utils/menu.tsx +0 -1
  9. package/jest.config.js +4 -4
  10. package/lib/generate_create.js +9 -0
  11. package/lib/generators/dataloader-generator.d.ts +12 -0
  12. package/lib/generators/dataloader-generator.js +221 -0
  13. package/lib/generators/resolver-generator.d.ts +2 -1
  14. package/lib/generators/resolver-generator.js +117 -24
  15. package/lib/generators/schema-generator.js +1 -0
  16. package/lib/index.js +11 -8
  17. package/lib/server/dataloaders/index.d.ts +38 -0
  18. package/lib/server/dataloaders/index.js +33 -0
  19. package/lib/server/dataloaders/template-dataloader.d.ts +48 -0
  20. package/lib/server/dataloaders/template-dataloader.js +131 -0
  21. package/lib/server/debug/dataloader-debug.d.ts +63 -0
  22. package/lib/server/debug/dataloader-debug.js +192 -0
  23. package/lib/server/graphql.js +9 -0
  24. package/lib/server/utils/dataloader-monitor.d.ts +87 -0
  25. package/lib/server/utils/dataloader-monitor.js +199 -0
  26. package/lib/tsconfig.build.tsbuildinfo +1 -1
  27. package/lib/utils.js +1 -1
  28. package/next-env.d.ts +1 -0
  29. package/next-i18next.config.js +7 -5
  30. package/next.config.js +34 -112
  31. package/package.json +6 -3
  32. package/pages/_app.tsx +7 -7
  33. package/pages/_document.tsx +0 -1
  34. package/pages/_error.tsx +0 -1
  35. package/pages/api/sso/ticketCheck.ts +117 -0
  36. package/pages/index.tsx +10 -3
  37. package/pages/login.tsx +41 -11
  38. package/pages/template/manage.tsx +16 -2
  39. package/server/apis/sso.js +22 -4
  40. package/server/modules/template/resolver.js +101 -21
  41. package/server/modules/template/schema.js +1 -0
@@ -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.error('读取 .env 文件失败:', error.message)
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.log(`登录失败 - 用户名: ${inputUsername}, 时间: ${new Date().toISOString()}, IP: ${req.ip}`)
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.error('登录验证出错:', error)
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
- // 根据ID获取模板
50
- templateGet: async ({ id }) => {
49
+
50
+ // 根据ID获取模板 - 使用 DataLoader 优化
51
+ templateGet: async ({ id }, context) => {
51
52
  try {
52
53
  validateId(id);
53
54
 
54
- const sql = 'SELECT id, name FROM template WHERE id = ?';
55
- const values = [id];
56
-
57
- console.log('根据ID查询模板:', { sql, values });
55
+ console.log('🚀 使用 DataLoader 根据ID查询模板:', { id });
58
56
 
59
- const results = await executeQuery(sql, values);
57
+ // 使用 DataLoader 批量加载,自动去重和缓存
58
+ const result = await context.dataloaders.template.byId.load(Number(id));
60
59
 
61
- if (results.length === 0) {
60
+ if (!result) {
62
61
  throw new Error(`ID为 ${id} 的模板不存在`);
63
62
  }
64
63
 
65
- return results[0];
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
- templateSearch: async ({ page = 0, pageSize = 10, data = {} }) => {
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('搜索模板:', { sql, values, countSql, countValues });
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
- return results.insertId;
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);
@@ -2,6 +2,7 @@ module.exports = {
2
2
  query: `
3
3
  template(page: Int, pageSize: Int): Templates
4
4
  templateGet(id: Int): Template
5
+ templateBatchGet(ids: [Int]): [Template]
5
6
  templateSearch(page: Int, pageSize: Int, data: TemplateSearchInput): Templates
6
7
  `,
7
8
  mutation: `