@shys/crud 1.0.1-beta.4 → 1.0.1-beta.6
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 +150 -60
- package/dist/cli.js +2 -0
- package/dist/config.js +4 -0
- package/dist/db.js +3 -2
- package/dist/generator.d.ts +2 -0
- package/dist/generator.js +132 -45
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/templates/dto-create.njk +5 -17
- package/templates/dto-query.njk +6 -60
- package/templates/dto-replace.njk +1 -1
- package/templates/dto-update.njk +2 -15
- package/templates/vo-single.njk +2 -23
package/README.md
CHANGED
|
@@ -143,6 +143,8 @@ export default {
|
|
|
143
143
|
onlyEntity: false,
|
|
144
144
|
noDb: false,
|
|
145
145
|
templatesPath: './my-templates',
|
|
146
|
+
baseFieldNames: ['createTime', 'updateTime', 'deleteTime'],
|
|
147
|
+
excludeWritableFields: ['ctime', 'mtime', 'created_at', 'updated_at', 'create_time', 'update_time'],
|
|
146
148
|
db: {
|
|
147
149
|
host: '127.0.0.1',
|
|
148
150
|
port: 3306,
|
|
@@ -165,6 +167,8 @@ export default {
|
|
|
165
167
|
| `dryRun` | `boolean` | `false` | 模拟运行模式。只打印将要生成的文件列表,不实际写入磁盘。适合预览效果。 |
|
|
166
168
|
| `compile` | `boolean` | `false` | 生成完成后是否自动执行 `mwtsc --cleanOutDir` 进行 TypeScript 编译。 |
|
|
167
169
|
| `onlyEntity` | `boolean` | `false` | 仅生成 entity 实体文件,不生成 DTO、VO、Service、Controller 等其他文件。适合只需要实体类的场景。 |
|
|
170
|
+
| `baseFieldNames` | `string[]` | `["createTime","updateTime","deleteTime"]` | BaseEntity 基础字段列表(驼峰命名)。这些字段会从 Entity 模板中排除(不重复声明)。如果你的基类使用了不同的字段名,可以在此配置。 |
|
|
171
|
+
| `excludeWritableFields` | `string[]` | `["ctime","mtime","created_at","updated_at","create_time","update_time"]` | 自动写入的时间戳字段列表(数据库原始字段名)。这些字段不会出现在 Create/Update/Replace DTO 中,因为它们通常由数据库或框架自动维护。 |
|
|
168
172
|
| `noDb` | `boolean` | `false` | 不连接数据库读取表结构。启用后使用内置模板字段生成(字段为示例占位,需手动修改)。 |
|
|
169
173
|
| `templatesPath` | `string` | 无 | 自定义模板目录路径。在此目录下放置与默认模板同名的 `.njk` 文件即可覆盖对应模板。未覆盖的模板继续使用默认模板。 |
|
|
170
174
|
| `db.host` | `string` | `127.0.0.1` | 数据库主机地址。 |
|
|
@@ -207,76 +211,162 @@ export default {
|
|
|
207
211
|
|
|
208
212
|
### 模板变量参考
|
|
209
213
|
|
|
214
|
+
所有模板共享以下通用命名变量:
|
|
215
|
+
|
|
216
|
+
| 变量 | 类型 | 说明 | 示例 |
|
|
217
|
+
|------|------|------|------|
|
|
218
|
+
| `tableName` | `string` | 数据库表名 | `"sys_user"` |
|
|
219
|
+
| `Camel` | `string` | 表名的 camelCase 形式 | `"sysUser"` |
|
|
220
|
+
| `Pascal` | `string` | 表名的 PascalCase 形式 | `"SysUser"` |
|
|
221
|
+
| `CreateName` | `string` | Create DTO 类名 | `"CreateSysUserDto"` |
|
|
222
|
+
| `UpdateName` | `string` | Update DTO 类名 | `"UpdateSysUserDto"` |
|
|
223
|
+
| `ReplaceName` | `string` | Replace DTO 类名 | `"ReplaceSysUserDto"` |
|
|
224
|
+
| `QueryName` | `string` | Query DTO 类名 | `"QuerySysUserDto"` |
|
|
225
|
+
| `VOName` | `string` | 单个 VO 类名 | `"SysUserVO"` |
|
|
226
|
+
| `ListVOName` | `string` | 列表 VO 类名 | `"SysUserListVO"` |
|
|
227
|
+
| `ServiceName` | `string` | Service 类名 | `"SysUserService"` |
|
|
228
|
+
| `CtrlName` | `string` | Controller 类名 | `"SysUserController"` |
|
|
229
|
+
| `pluralKebab` | `string` | 复数 kebab-case 路由名 | `"sys-users"` |
|
|
230
|
+
| `tagName` | `string` | Swagger tag 名 | `"SysUser管理"` |
|
|
231
|
+
| `desc` | `string` | 接口描述 | `"sys_user 表增删改查接口"` |
|
|
232
|
+
|
|
233
|
+
#### 字段对象(Field)属性
|
|
234
|
+
|
|
235
|
+
所有字段列表(如 `fields`、`createFields`、`writable`、`filterable`)中的每个字段对象都包含以下属性:
|
|
236
|
+
|
|
237
|
+
| 属性 | 类型 | 说明 | 示例 |
|
|
238
|
+
|------|------|------|------|
|
|
239
|
+
| `name` | `string` | 数据库字段名 | `"user_name"` |
|
|
240
|
+
| `camel` | `string` | 驼峰命名 | `"userName"` |
|
|
241
|
+
| `type` | `string` | 数据库原始类型 | `"varchar"` |
|
|
242
|
+
| `ormType` | `string` | ORM 类型(标准化后) | `"varchar"` |
|
|
243
|
+
| `jsType` | `string` | JavaScript 类型(含 null) | `"string \| null"` |
|
|
244
|
+
| `createDtoType` | `string` | Create DTO 中的类型 | `"string \| null"` |
|
|
245
|
+
| `voType` | `string` | VO 中的类型(日期转 string) | `"string"` |
|
|
246
|
+
| `swagType` | `string` | Swagger 类型字符串 | `"'string'"` |
|
|
247
|
+
| `len` | `number \| null` | 字段长度 | `64` |
|
|
248
|
+
| `nullable` | `boolean` | 是否可空 | `true` |
|
|
249
|
+
| `comment` | `string` | 字段注释 | `"用户名"` |
|
|
250
|
+
| `defaultVal` | `any` | 默认值 | `""` |
|
|
251
|
+
| `pk` | `boolean` | 是否主键 | `false` |
|
|
252
|
+
| `auto` | `boolean` | 是否自增 | `false` |
|
|
253
|
+
| `joiRule` | `string` | Joi 验证规则(非必填) | `"Joi.string().max(64).label('用户名').allow(null)"` |
|
|
254
|
+
| `joiRuleRequired` | `string` | Joi 验证规则(必填) | `"Joi.string().max(64).label('用户名').required()"` |
|
|
255
|
+
| `supportsLength` | `boolean` | 该类型是否支持长度 | `true` |
|
|
256
|
+
| `isDate` | `boolean` | 是否日期类型 | `false` |
|
|
257
|
+
| `isSensitive` | `boolean` | 是否敏感字段(密码等) | `false` |
|
|
258
|
+
| `idx` | `boolean` | 是否索引字段(仅 entity 模板) | `false` |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
210
262
|
#### entity.njk
|
|
211
263
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
Pascal
|
|
231
|
-
Camel
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
hasDateField: boolean // 是否有日期字段
|
|
235
|
-
idxCount: number // 索引字段数量
|
|
236
|
-
```
|
|
264
|
+
| 变量 | 类型 | 说明 |
|
|
265
|
+
|------|------|------|
|
|
266
|
+
| `orm` | `string` | ORM 类型:`typeorm` / `sequelize` / `mikro` / `mongoose` |
|
|
267
|
+
| `ormSuffix` | `string` | 实体文件后缀:`entity` / `model` |
|
|
268
|
+
| `fields` | `Field[]` | 字段列表(排除主键和 BaseEntity 基础字段) |
|
|
269
|
+
| `tableName` | `string` | 表名 |
|
|
270
|
+
| `Pascal` | `string` | PascalCase 命名 |
|
|
271
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
272
|
+
| `pkInfo` | `Field \| null` | 主键字段信息(已增强) |
|
|
273
|
+
| `hasPk` | `boolean` | 是否有主键 |
|
|
274
|
+
| `hasDateField` | `boolean` | 是否包含日期字段 |
|
|
275
|
+
| `idxCount` | `number` | 索引字段数量 |
|
|
276
|
+
|
|
277
|
+
#### dto-create.njk
|
|
278
|
+
|
|
279
|
+
| 变量 | 类型 | 说明 |
|
|
280
|
+
|------|------|------|
|
|
281
|
+
| `CreateName` | `string` | Create DTO 类名 |
|
|
282
|
+
| `Pascal` | `string` | PascalCase 命名 |
|
|
283
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
284
|
+
| `createFields` | `Field[]` | 可创建字段列表(排除自增主键) |
|
|
285
|
+
| `picks` | `string` | Pick 字段列表字符串,如 `"name",\n "email"` |
|
|
237
286
|
|
|
238
|
-
#### dto-
|
|
287
|
+
#### dto-update.njk
|
|
239
288
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
UpdateName
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
289
|
+
| 变量 | 类型 | 说明 |
|
|
290
|
+
|------|------|------|
|
|
291
|
+
| `UpdateName` | `string` | Update DTO 类名 |
|
|
292
|
+
| `CreateName` | `string` | Create DTO 类名(用于继承) |
|
|
293
|
+
| `writable` | `Field[]` | 可写字段列表(排除时间戳字段) |
|
|
294
|
+
| `picks` | `string` | Pick 字段列表字符串 |
|
|
295
|
+
| `typeArgs` | `string` | 类型联合字符串,如 `'name' \| 'email'` |
|
|
296
|
+
|
|
297
|
+
#### dto-replace.njk
|
|
298
|
+
|
|
299
|
+
| 变量 | 类型 | 说明 |
|
|
300
|
+
|------|------|------|
|
|
301
|
+
| `ReplaceName` | `string` | Replace DTO 类名 |
|
|
302
|
+
| `CreateName` | `string` | Create DTO 类名(用于继承) |
|
|
303
|
+
| `writable` | `Field[]` | 可写字段列表 |
|
|
304
|
+
| `picks` | `string` | Pick 字段列表字符串 |
|
|
305
|
+
|
|
306
|
+
#### dto-query.njk
|
|
307
|
+
|
|
308
|
+
| 变量 | 类型 | 说明 |
|
|
309
|
+
|------|------|------|
|
|
310
|
+
| `QueryName` | `string` | Query DTO 类名 |
|
|
311
|
+
| `filterable` | `Field[]` | 可筛选字段列表(含主键) |
|
|
312
|
+
| `picks` | `string` | Pick 字段列表字符串 |
|
|
313
|
+
| `typeArgs` | `string` | 类型联合字符串 |
|
|
314
|
+
| `sortDescription` | `string` | sort 字段的描述文本 |
|
|
315
|
+
| `filterExample` | `string` | filter 字段的示例值 |
|
|
316
|
+
| `searchDescription` | `string` | search 字段的描述文本 |
|
|
317
|
+
|
|
318
|
+
#### vo-single.njk
|
|
319
|
+
|
|
320
|
+
| 变量 | 类型 | 说明 |
|
|
321
|
+
|------|------|------|
|
|
322
|
+
| `VOName` | `string` | 单个 VO 类名 |
|
|
323
|
+
| `Pascal` | `string` | PascalCase 命名(Entity 类名,用于继承) |
|
|
324
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
325
|
+
| `fields` | `Field[]` | 所有字段(含主键,排除 BaseEntity 基础字段) |
|
|
326
|
+
| `hasTransform` | `boolean` | 是否有需要转换的字段(日期/敏感) |
|
|
327
|
+
|
|
328
|
+
#### vo-list.njk
|
|
329
|
+
|
|
330
|
+
| 变量 | 类型 | 说明 |
|
|
331
|
+
|------|------|------|
|
|
332
|
+
| `ListVOName` | `string` | 列表 VO 类名 |
|
|
333
|
+
| `VOName` | `string` | 单个 VO 类名 |
|
|
334
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
335
|
+
|
|
336
|
+
#### vo-index.njk
|
|
337
|
+
|
|
338
|
+
| 变量 | 类型 | 说明 |
|
|
339
|
+
|------|------|------|
|
|
340
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
250
341
|
|
|
251
342
|
#### service.njk
|
|
252
343
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
ormSuffix: string
|
|
260
|
-
serviceParent: string // 父类名
|
|
261
|
-
importCrudOrm: string // 导入语句
|
|
262
|
-
injectEntity: string // 装饰器名
|
|
263
|
-
}
|
|
264
|
-
```
|
|
344
|
+
| 变量 | 类型 | 说明 |
|
|
345
|
+
|------|------|------|
|
|
346
|
+
| `ServiceName` | `string` | Service 类名 |
|
|
347
|
+
| `Pascal` | `string` | PascalCase 命名 |
|
|
348
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
349
|
+
| `ormInfo` | `object` | ORM 信息对象 |
|
|
265
350
|
|
|
266
351
|
#### controller.njk
|
|
267
352
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
Pascal
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
353
|
+
| 变量 | 类型 | 说明 |
|
|
354
|
+
|------|------|------|
|
|
355
|
+
| `CtrlName` | `string` | Controller 类名 |
|
|
356
|
+
| `Pascal` | `string` | PascalCase 命名 |
|
|
357
|
+
| `ServiceName` | `string` | Service 类名 |
|
|
358
|
+
| `CreateName` / `UpdateName` / `ReplaceName` / `QueryName` | `string` | 各 DTO 类名 |
|
|
359
|
+
| `VOName` / `ListVOName` | `string` | 各 VO 类名 |
|
|
360
|
+
| `Camel` | `string` | camelCase 命名 |
|
|
361
|
+
| `pluralKebab` | `string` | 复数 kebab-case 路由名 |
|
|
362
|
+
| `tagName` | `string` | Swagger tag 名 |
|
|
363
|
+
| `desc` | `string` | 接口描述 |
|
|
364
|
+
| `sortable` | `string` | 可排序字段 JSON 字符串 |
|
|
365
|
+
| `filterable` | `string` | 可过滤字段 JSON 字符串 |
|
|
366
|
+
| `searchable` | `string` | 可搜索字段 JSON 字符串 |
|
|
367
|
+
| `pkCamel` | `string` | 主键驼峰名 |
|
|
368
|
+
| `idField` | `string` | ID 字段名 |
|
|
369
|
+
| `defaultSort` | `string` | 默认排序配置 JSON 字符串 |
|
|
280
370
|
|
|
281
371
|
### 自定义模板示例
|
|
282
372
|
|
package/dist/cli.js
CHANGED
|
@@ -277,6 +277,8 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
277
277
|
outputPath,
|
|
278
278
|
templatesPath,
|
|
279
279
|
onlyEntity: cli.onlyEntity,
|
|
280
|
+
baseFieldNames: cli.baseFieldNames,
|
|
281
|
+
excludeWritableFields: cli.excludeWritableFields,
|
|
280
282
|
});
|
|
281
283
|
console.log(`\n${chalk.cyan(`生成清单(${built.files.length})`)}`);
|
|
282
284
|
const stats = writeAll(built.files, { force: !!cli.force, dryRun: !!cli.dryRun });
|
package/dist/config.js
CHANGED
|
@@ -107,6 +107,10 @@ export function mergeConfigs(...configs) {
|
|
|
107
107
|
result.templatesPath = config.templatesPath;
|
|
108
108
|
if (config.onlyEntity !== undefined)
|
|
109
109
|
result.onlyEntity = config.onlyEntity;
|
|
110
|
+
if (config.baseFieldNames !== undefined)
|
|
111
|
+
result.baseFieldNames = config.baseFieldNames;
|
|
112
|
+
if (config.excludeWritableFields !== undefined)
|
|
113
|
+
result.excludeWritableFields = config.excludeWritableFields;
|
|
110
114
|
if (config.db) {
|
|
111
115
|
result.db = { ...(result.db || {}), ...config.db };
|
|
112
116
|
}
|
package/dist/db.js
CHANGED
|
@@ -52,7 +52,7 @@ export async function getTableFieldsFromDb(dbConfig, tableName) {
|
|
|
52
52
|
database: database || '',
|
|
53
53
|
});
|
|
54
54
|
try {
|
|
55
|
-
const [rows] = await connection.execute(`SELECT column_name, data_type, is_nullable, column_type, column_comment, column_default, character_maximum_length, extra
|
|
55
|
+
const [rows] = await connection.execute(`SELECT column_name, data_type, is_nullable, column_type, column_comment, column_default, character_maximum_length, extra, column_key
|
|
56
56
|
FROM information_schema.COLUMNS
|
|
57
57
|
WHERE table_schema = ? AND table_name = ?
|
|
58
58
|
ORDER BY ordinal_position`, [database, tableName]);
|
|
@@ -68,7 +68,8 @@ export async function getTableFieldsFromDb(dbConfig, tableName) {
|
|
|
68
68
|
const defaultVal = row.COLUMN_DEFAULT || row.column_default;
|
|
69
69
|
const length = row.CHARACTER_MAXIMUM_LENGTH || row.character_maximum_length;
|
|
70
70
|
const extra = row.EXTRA || row.extra || '';
|
|
71
|
-
const
|
|
71
|
+
const columnKey = row.COLUMN_KEY || row.column_key || '';
|
|
72
|
+
const isPk = columnKey === 'PRI';
|
|
72
73
|
const isAuto = extra.includes('auto_increment');
|
|
73
74
|
return {
|
|
74
75
|
name: columnName,
|
package/dist/generator.d.ts
CHANGED
|
@@ -23,5 +23,7 @@ export interface BuildFilesParams {
|
|
|
23
23
|
templatesPath?: string;
|
|
24
24
|
renderer?: TemplateRenderer;
|
|
25
25
|
onlyEntity?: boolean;
|
|
26
|
+
baseFieldNames?: string[];
|
|
27
|
+
excludeWritableFields?: string[];
|
|
26
28
|
}
|
|
27
29
|
export declare function buildFiles(params: BuildFilesParams): BuildResult;
|
package/dist/generator.js
CHANGED
|
@@ -1,19 +1,54 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { toCamelCase, toPascalCase, toPluralKebab } from './utils.js';
|
|
3
3
|
import { createRenderer } from './renderer.js';
|
|
4
|
-
const
|
|
4
|
+
const DEFAULT_BASE_FIELD_NAMES = ['createTime', 'updateTime', 'deleteTime'];
|
|
5
|
+
const DEFAULT_EXCLUDE_WRITABLE_FIELDS = [
|
|
6
|
+
'ctime',
|
|
7
|
+
'mtime',
|
|
8
|
+
'created_at',
|
|
9
|
+
'updated_at',
|
|
10
|
+
'create_time',
|
|
11
|
+
'update_time',
|
|
12
|
+
];
|
|
5
13
|
const JSTypeMap = {
|
|
6
|
-
int: 'number',
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
int: 'number',
|
|
15
|
+
integer: 'number',
|
|
16
|
+
bigint: 'number',
|
|
17
|
+
smallint: 'number',
|
|
18
|
+
tinyint: 'number',
|
|
19
|
+
mediumint: 'number',
|
|
20
|
+
decimal: 'number',
|
|
21
|
+
numeric: 'number',
|
|
22
|
+
float: 'number',
|
|
23
|
+
double: 'number',
|
|
24
|
+
real: 'number',
|
|
25
|
+
char: 'string',
|
|
26
|
+
varchar: 'string',
|
|
27
|
+
text: 'string',
|
|
28
|
+
tinytext: 'string',
|
|
29
|
+
mediumtext: 'string',
|
|
30
|
+
longtext: 'string',
|
|
31
|
+
enum: 'string',
|
|
32
|
+
set: 'string',
|
|
33
|
+
json: 'any',
|
|
34
|
+
date: 'Date',
|
|
35
|
+
datetime: 'Date',
|
|
36
|
+
timestamp: 'Date',
|
|
37
|
+
time: 'Date',
|
|
38
|
+
year: 'number',
|
|
39
|
+
binary: 'Buffer',
|
|
40
|
+
varbinary: 'Buffer',
|
|
41
|
+
blob: 'Buffer',
|
|
42
|
+
bool: 'boolean',
|
|
43
|
+
boolean: 'boolean',
|
|
44
|
+
bit: 'boolean',
|
|
13
45
|
};
|
|
14
46
|
const NO_LENGTH_ORM_TYPES = /^(text|tinytext|mediumtext|longtext|blob|tinyblob|mediumblob|longblob|json)$/i;
|
|
15
47
|
function normalizeOrmType(type) {
|
|
16
|
-
return String(type)
|
|
48
|
+
return (String(type)
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/\(\d+.*?\)/, '')
|
|
51
|
+
.trim() || 'varchar');
|
|
17
52
|
}
|
|
18
53
|
function fieldSupportsLength(f) {
|
|
19
54
|
return !NO_LENGTH_ORM_TYPES.test(normalizeOrmType(f.type));
|
|
@@ -30,9 +65,19 @@ function createDtoTypeOf(field) {
|
|
|
30
65
|
}
|
|
31
66
|
function joiSwagType(f) {
|
|
32
67
|
const t = f.type;
|
|
33
|
-
if (t === 'int' ||
|
|
68
|
+
if (t === 'int' ||
|
|
69
|
+
t === 'bigint' ||
|
|
70
|
+
t === 'smallint' ||
|
|
71
|
+
t === 'tinyint' ||
|
|
72
|
+
t === 'mediumint' ||
|
|
73
|
+
t === 'integer')
|
|
34
74
|
return `'integer'`;
|
|
35
|
-
if (t === 'decimal' ||
|
|
75
|
+
if (t === 'decimal' ||
|
|
76
|
+
t === 'numeric' ||
|
|
77
|
+
t === 'float' ||
|
|
78
|
+
t === 'double' ||
|
|
79
|
+
t === 'real' ||
|
|
80
|
+
t === 'number')
|
|
36
81
|
return `'number'`;
|
|
37
82
|
if (t === 'datetime' || t === 'timestamp' || t === 'date')
|
|
38
83
|
return `'string'`;
|
|
@@ -114,13 +159,18 @@ function enhanceField(f, extra = {}) {
|
|
|
114
159
|
supportsLength: fieldSupportsLength(f),
|
|
115
160
|
isDate: /datetime|date|timestamp/i.test(f.type),
|
|
116
161
|
isSensitive: /password|passwd|salt|token|secret|private_key/i.test(f.name + f.camel),
|
|
117
|
-
voType: /datetime|date|timestamp/i.test(f.type)
|
|
162
|
+
voType: /datetime|date|timestamp/i.test(f.type)
|
|
163
|
+
? 'string'
|
|
164
|
+
: JSTypeMap[f.type] || 'string',
|
|
118
165
|
idx: !!extra.idx,
|
|
119
166
|
};
|
|
120
167
|
}
|
|
121
168
|
export function buildFiles(params) {
|
|
122
|
-
const { moduleName, tableName, baseSrcPath, ormInfo, fields, outputPath, templatesPath, renderer: customRenderer, onlyEntity } = params;
|
|
169
|
+
const { moduleName, tableName, baseSrcPath, ormInfo, fields, outputPath, templatesPath, renderer: customRenderer, onlyEntity, baseFieldNames, excludeWritableFields, } = params;
|
|
123
170
|
const renderer = customRenderer || createRenderer({ customTemplatesPath: templatesPath });
|
|
171
|
+
const finalBaseFieldNames = baseFieldNames ?? DEFAULT_BASE_FIELD_NAMES;
|
|
172
|
+
const finalExcludeWritable = excludeWritableFields ?? DEFAULT_EXCLUDE_WRITABLE_FIELDS;
|
|
173
|
+
const excludeWritableRegex = new RegExp(finalExcludeWritable.map(s => `^${s}$`).join('|'), 'i');
|
|
124
174
|
const Camel = toCamelCase(tableName);
|
|
125
175
|
const Pascal = toPascalCase(tableName);
|
|
126
176
|
const CreateName = `Create${Pascal}Dto`;
|
|
@@ -136,18 +186,19 @@ export function buildFiles(params) {
|
|
|
136
186
|
const desc = `${tableName} 表增删改查接口`;
|
|
137
187
|
const modRoot = resolve(outputPath || baseSrcPath, 'modules', moduleName);
|
|
138
188
|
const resultFiles = [];
|
|
139
|
-
const originalPkInfo = fields.find(
|
|
189
|
+
const originalPkInfo = fields.find(f => f.pk);
|
|
140
190
|
const hasPk = !!originalPkInfo;
|
|
141
|
-
const entityFields = fields.filter(
|
|
142
|
-
if (
|
|
191
|
+
const entityFields = fields.filter(f => {
|
|
192
|
+
if (finalBaseFieldNames.includes(f.camel))
|
|
143
193
|
return false;
|
|
144
194
|
if (f.pk)
|
|
145
195
|
return false;
|
|
146
196
|
return true;
|
|
147
197
|
});
|
|
148
|
-
const idxFields = entityFields.filter(
|
|
149
|
-
|
|
150
|
-
const
|
|
198
|
+
const idxFields = entityFields.filter(f => !f.pk &&
|
|
199
|
+
(String(f.name).toUpperCase() === 'COUNTRYCODE' || /_id$/.test(f.name)));
|
|
200
|
+
const hasDateField = entityFields.some(f => /datetime|date|timestamp/i.test(f.type));
|
|
201
|
+
const enhancedEntityFields = entityFields.map(f => enhanceField(f, { idx: idxFields.some(i => i.name === f.name) }));
|
|
151
202
|
const enhancedPkInfo = originalPkInfo ? enhanceField(originalPkInfo) : null;
|
|
152
203
|
// Entity
|
|
153
204
|
resultFiles.push({
|
|
@@ -176,37 +227,46 @@ export function buildFiles(params) {
|
|
|
176
227
|
searchable: [],
|
|
177
228
|
};
|
|
178
229
|
}
|
|
179
|
-
const createFields = entityFields.filter(
|
|
180
|
-
const writable = createFields.filter(
|
|
230
|
+
const createFields = entityFields.filter(f => !(f.pk && f.auto));
|
|
231
|
+
const writable = createFields.filter(f => !excludeWritableRegex.test(f.name));
|
|
181
232
|
const isSensitive = (f) => /password|passwd|salt|token|secret|private_key/i.test(f.name + f.camel);
|
|
182
233
|
const pkCamel = originalPkInfo?.camel || '';
|
|
183
|
-
const sortableBase = entityFields
|
|
234
|
+
const sortableBase = entityFields
|
|
235
|
+
.filter(f => !isSensitive(f))
|
|
236
|
+
.map(f => f.camel);
|
|
184
237
|
const sortable = pkCamel && !sortableBase.includes(pkCamel)
|
|
185
238
|
? [pkCamel, ...sortableBase]
|
|
186
239
|
: sortableBase;
|
|
187
|
-
const filterableBase = entityFields.filter(
|
|
188
|
-
const filterableArr = pkCamel && !filterableBase.some(
|
|
240
|
+
const filterableBase = entityFields.filter(f => !isSensitive(f));
|
|
241
|
+
const filterableArr = pkCamel && !filterableBase.some(f => f.camel === pkCamel)
|
|
189
242
|
? [originalPkInfo, ...filterableBase]
|
|
190
243
|
: filterableBase;
|
|
191
|
-
const filterable = filterableArr.map(
|
|
244
|
+
const filterable = filterableArr.map(f => f.camel);
|
|
192
245
|
const searchable = filterableArr
|
|
193
|
-
.filter(
|
|
194
|
-
.map(
|
|
195
|
-
const enhancedCreateFields = createFields.map(
|
|
196
|
-
const enhancedWritable = writable.map(
|
|
197
|
-
const enhancedFilterable = filterableArr.map(
|
|
246
|
+
.filter(f => /char|varchar|text/i.test(f.type))
|
|
247
|
+
.map(f => f.camel);
|
|
248
|
+
const enhancedCreateFields = createFields.map(f => enhanceField(f));
|
|
249
|
+
const enhancedWritable = writable.map(f => enhanceField(f));
|
|
250
|
+
const enhancedFilterable = filterableArr.map(f => enhanceField(f));
|
|
198
251
|
// Create DTO
|
|
252
|
+
const createPicks = enhancedCreateFields
|
|
253
|
+
.map(f => JSON.stringify(f.camel))
|
|
254
|
+
.join(',\n ');
|
|
199
255
|
resultFiles.push({
|
|
200
256
|
path: resolve(modRoot, `dto/${Camel}/create.dto.ts`),
|
|
201
257
|
content: renderer.render('dto-create', {
|
|
202
258
|
CreateName,
|
|
203
259
|
Pascal,
|
|
260
|
+
Camel,
|
|
204
261
|
createFields: enhancedCreateFields,
|
|
262
|
+
picks: createPicks,
|
|
205
263
|
}),
|
|
206
264
|
});
|
|
207
265
|
// Update DTO
|
|
208
|
-
const updatePicks = enhancedWritable
|
|
209
|
-
|
|
266
|
+
const updatePicks = enhancedWritable
|
|
267
|
+
.map(f => JSON.stringify(f.camel))
|
|
268
|
+
.join(',\n ');
|
|
269
|
+
const updateTypeArgs = enhancedWritable.map(f => `'${f.camel}'`).join(' | ');
|
|
210
270
|
resultFiles.push({
|
|
211
271
|
path: resolve(modRoot, `dto/${Camel}/update.dto.ts`),
|
|
212
272
|
content: renderer.render('dto-update', {
|
|
@@ -218,7 +278,9 @@ export function buildFiles(params) {
|
|
|
218
278
|
}),
|
|
219
279
|
});
|
|
220
280
|
// Replace DTO
|
|
221
|
-
const replacePicks = enhancedWritable
|
|
281
|
+
const replacePicks = enhancedWritable
|
|
282
|
+
.map(f => JSON.stringify(f.camel))
|
|
283
|
+
.join(',\n ');
|
|
222
284
|
resultFiles.push({
|
|
223
285
|
path: resolve(modRoot, `dto/${Camel}/replace.dto.ts`),
|
|
224
286
|
content: renderer.render('dto-replace', {
|
|
@@ -229,12 +291,14 @@ export function buildFiles(params) {
|
|
|
229
291
|
}),
|
|
230
292
|
});
|
|
231
293
|
// Query DTO
|
|
232
|
-
const queryPicks = enhancedFilterable
|
|
233
|
-
|
|
294
|
+
const queryPicks = enhancedFilterable
|
|
295
|
+
.map(f => JSON.stringify(f.camel))
|
|
296
|
+
.join(',\n ');
|
|
297
|
+
const queryTypeArgs = enhancedFilterable.map(f => `'${f.camel}'`).join(' | ');
|
|
234
298
|
const sortEx = filterable[0] ? `${filterable[0]}:ASC,id:DESC` : 'id:DESC';
|
|
235
299
|
const filterEx = filterableArr
|
|
236
300
|
.slice(0, 2)
|
|
237
|
-
.map(
|
|
301
|
+
.map(f => {
|
|
238
302
|
const isStr = /char|varchar|text/i.test(f.type);
|
|
239
303
|
return isStr ? `${f.camel}:like:a` : `${f.camel}:gte:10`;
|
|
240
304
|
})
|
|
@@ -248,7 +312,8 @@ export function buildFiles(params) {
|
|
|
248
312
|
filterable: enhancedFilterable,
|
|
249
313
|
picks: queryPicks,
|
|
250
314
|
typeArgs: queryTypeArgs || 'never',
|
|
251
|
-
sortDescription: '排序字段。标准格式:field:ASC 或 field:DESC;多字段用英文逗号分隔,或多次传 sort。示例:id:DESC、' +
|
|
315
|
+
sortDescription: '排序字段。标准格式:field:ASC 或 field:DESC;多字段用英文逗号分隔,或多次传 sort。示例:id:DESC、' +
|
|
316
|
+
sortEx,
|
|
252
317
|
filterExample: filterEx || 'id:gte:1',
|
|
253
318
|
searchDescription: '搜索关键字,对可搜索字段(' + searchFields + ')模糊匹配',
|
|
254
319
|
}),
|
|
@@ -259,12 +324,17 @@ export function buildFiles(params) {
|
|
|
259
324
|
content: renderer.render('dto-index', {}),
|
|
260
325
|
});
|
|
261
326
|
// Single VO
|
|
262
|
-
const allVoFields = [
|
|
263
|
-
|
|
327
|
+
const allVoFields = [
|
|
328
|
+
...(originalPkInfo ? [originalPkInfo] : []),
|
|
329
|
+
...entityFields,
|
|
330
|
+
].map(f => enhanceField(f));
|
|
331
|
+
const voHasTransform = allVoFields.some(f => f.isDate || f.isSensitive);
|
|
264
332
|
resultFiles.push({
|
|
265
333
|
path: resolve(modRoot, `vo/${Camel}/${Camel}.vo.ts`),
|
|
266
334
|
content: renderer.render('vo-single', {
|
|
267
335
|
VOName,
|
|
336
|
+
Pascal,
|
|
337
|
+
Camel,
|
|
268
338
|
fields: allVoFields,
|
|
269
339
|
hasTransform: voHasTransform,
|
|
270
340
|
}),
|
|
@@ -294,7 +364,7 @@ export function buildFiles(params) {
|
|
|
294
364
|
}),
|
|
295
365
|
});
|
|
296
366
|
// Controller
|
|
297
|
-
const sArr = (arr) => arr.map(
|
|
367
|
+
const sArr = (arr) => arr.map(x => JSON.stringify(x)).join(', ');
|
|
298
368
|
const idField = pkCamel || 'id';
|
|
299
369
|
const defaultSort = searchable && searchable.length
|
|
300
370
|
? `[{ field: ${JSON.stringify(sortable[0] || idField)}, order: 'DESC' }]`
|
|
@@ -302,10 +372,19 @@ export function buildFiles(params) {
|
|
|
302
372
|
resultFiles.push({
|
|
303
373
|
path: resolve(modRoot, `controller/${Camel}.controller.ts`),
|
|
304
374
|
content: renderer.render('controller', {
|
|
305
|
-
CtrlName,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
375
|
+
CtrlName,
|
|
376
|
+
Pascal,
|
|
377
|
+
ServiceName,
|
|
378
|
+
CreateName,
|
|
379
|
+
UpdateName,
|
|
380
|
+
ReplaceName,
|
|
381
|
+
QueryName,
|
|
382
|
+
VOName,
|
|
383
|
+
ListVOName,
|
|
384
|
+
Camel,
|
|
385
|
+
pluralKebab,
|
|
386
|
+
tagName,
|
|
387
|
+
desc,
|
|
309
388
|
sortable: sArr(sortable),
|
|
310
389
|
filterable: sArr(filterable),
|
|
311
390
|
searchable: sArr(searchable),
|
|
@@ -321,5 +400,13 @@ export function buildFiles(params) {
|
|
|
321
400
|
content: renderer.render('crud-page-vo', {}),
|
|
322
401
|
createOnly: true,
|
|
323
402
|
});
|
|
324
|
-
return {
|
|
403
|
+
return {
|
|
404
|
+
files: resultFiles,
|
|
405
|
+
Pascal,
|
|
406
|
+
pluralKebab,
|
|
407
|
+
Camel,
|
|
408
|
+
sortable,
|
|
409
|
+
filterable,
|
|
410
|
+
searchable,
|
|
411
|
+
};
|
|
325
412
|
}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/templates/dto-create.njk
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import * as Joi from 'joi';
|
|
1
|
+
import { PickDto } from '@midwayjs/validation';
|
|
2
|
+
import { {{ Pascal }} } from '../../entity/{{ Camel }}.entity';
|
|
4
3
|
|
|
5
|
-
export class {{ CreateName }} {
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
type: {{ f.swagType | json }},
|
|
9
|
-
{% if f.len %} maxLength: {{ f.len }},
|
|
10
|
-
{% endif %}{% if f.nullable %} nullable: true,
|
|
11
|
-
{% endif %} description: {{ (f.comment or f.camel) | json }},
|
|
12
|
-
{% if f.defaultVal != null %} example: {{ f.defaultVal | json }},
|
|
13
|
-
{% endif %} })
|
|
14
|
-
@Rule({{ f.joiRule }}.description({{ (f.comment or f.camel) | json }}))
|
|
15
|
-
{{ f.camel }}?: {{ f.createDtoType }};
|
|
16
|
-
|
|
17
|
-
{% endfor -%}
|
|
18
|
-
}
|
|
4
|
+
export class {{ CreateName }} extends PickDto({{ Pascal }}, [
|
|
5
|
+
{{ picks }}
|
|
6
|
+
]) {}
|
package/templates/dto-query.njk
CHANGED
|
@@ -1,71 +1,17 @@
|
|
|
1
|
-
import { Rule, PickDto } from '@midwayjs/validation';
|
|
2
1
|
import { ApiPropertyOptional } from '@midwayjs/swagger';
|
|
2
|
+
import { Rule } from '@midwayjs/validation';
|
|
3
3
|
import * as Joi from 'joi';
|
|
4
|
-
import {
|
|
4
|
+
import { CrudBaseQueryDto } from '../../../../framework/dto/base.query.dto';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const _QueryFilterPicked = PickDto({{ CreateName }}, [
|
|
9
|
-
{{ picks }}
|
|
10
|
-
]) as unknown as new () => Partial<_QueryFilterFields>;
|
|
11
|
-
|
|
12
|
-
export class {{ QueryName }} extends _QueryFilterPicked {
|
|
6
|
+
export class {{ QueryName }} extends CrudBaseQueryDto {
|
|
13
7
|
{% for f in filterable -%}
|
|
14
8
|
@ApiPropertyOptional({
|
|
15
9
|
type: {{ f.swagType | json }},
|
|
16
10
|
{% if f.len %} maxLength: {{ f.len }},
|
|
17
|
-
{% endif %} description: {{ (
|
|
11
|
+
{% endif %} description: {{ (f.comment or f.camel) | json }},
|
|
18
12
|
})
|
|
19
|
-
|
|
13
|
+
@Rule({{ f.joiRule }}.optional())
|
|
14
|
+
{{ f.camel }}?: {{ f.jsType }};
|
|
20
15
|
|
|
21
16
|
{% endfor -%}
|
|
22
|
-
{% if filterable.length %}
|
|
23
|
-
|
|
24
|
-
{% endif %} @ApiPropertyOptional({
|
|
25
|
-
type: 'integer',
|
|
26
|
-
minimum: 1,
|
|
27
|
-
default: 1,
|
|
28
|
-
description: '当前页码,从1开始',
|
|
29
|
-
example: 1,
|
|
30
|
-
})
|
|
31
|
-
@Rule(Joi.number().integer().min(1).default(1).description('当前页码,从1开始,默认1'))
|
|
32
|
-
page?: number;
|
|
33
|
-
|
|
34
|
-
@ApiPropertyOptional({
|
|
35
|
-
type: 'integer',
|
|
36
|
-
minimum: 1,
|
|
37
|
-
maximum: 100,
|
|
38
|
-
default: 10,
|
|
39
|
-
description: '每页条数,范围 1-100',
|
|
40
|
-
example: 10,
|
|
41
|
-
})
|
|
42
|
-
@Rule(Joi.number().integer().min(1).max(100).default(10).description('每页条数,1-100,默认10'))
|
|
43
|
-
limit?: number;
|
|
44
|
-
|
|
45
|
-
@ApiPropertyOptional({
|
|
46
|
-
type: 'string',
|
|
47
|
-
description: {{ sortDescription | json }},
|
|
48
|
-
example: 'id:DESC',
|
|
49
|
-
})
|
|
50
|
-
@Rule(Joi.string().description('排序字段,例如 id:ASC 或 name:DESC,id:DESC'))
|
|
51
|
-
sort?: string;
|
|
52
|
-
|
|
53
|
-
@ApiPropertyOptional({
|
|
54
|
-
type: 'string',
|
|
55
|
-
description:
|
|
56
|
-
'过滤条件(兼容两种写法)。官方标准:field||op||value,多条件多次传或逗号分隔。简写:field:op:value 逗号分隔。' +
|
|
57
|
-
'操作符 eq/ne/gt/gte/lt/lte/in/like,别名 ==/!=/>/>=/</<=/contains。' +
|
|
58
|
-
'示例:age||gte||18,name||like||张 或 age:gte:18,name:like:张',
|
|
59
|
-
example: {{ filterExample | json }},
|
|
60
|
-
})
|
|
61
|
-
@Rule(Joi.string().description('过滤条件,支持 field||op||value 或 field:op:value,逗号分隔多条件'))
|
|
62
|
-
filter?: string;
|
|
63
|
-
|
|
64
|
-
@ApiPropertyOptional({
|
|
65
|
-
type: 'string',
|
|
66
|
-
description: {{ searchDescription | json }},
|
|
67
|
-
example: 'zhang',
|
|
68
|
-
})
|
|
69
|
-
@Rule(Joi.string().description('搜索关键字,用于模糊搜索可搜索字段'))
|
|
70
|
-
search?: string;
|
|
71
17
|
}
|
|
@@ -14,7 +14,7 @@ export class {{ ReplaceName }} extends PickDto({{ CreateName }}, [
|
|
|
14
14
|
{% endif %} required: true,
|
|
15
15
|
description: {{ ((f.comment or f.camel) + '(PUT:必须完整传入)') | json }},
|
|
16
16
|
})
|
|
17
|
-
@Rule({{ f.joiRuleRequired }}
|
|
17
|
+
@Rule({{ f.joiRuleRequired }})
|
|
18
18
|
override {{ f.camel }}: {{ f.createDtoType }};
|
|
19
19
|
|
|
20
20
|
{% endfor -%}
|
package/templates/dto-update.njk
CHANGED
|
@@ -2,21 +2,8 @@ import { ApiPropertyOptional } from '@midwayjs/swagger';
|
|
|
2
2
|
import { PickDto } from '@midwayjs/validation';
|
|
3
3
|
import { {{ CreateName }} } from './create.dto';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const _UpdatePicked = PickDto({{ CreateName }}, [
|
|
5
|
+
export class {{ UpdateName }} extends PickDto({{ CreateName }}, [
|
|
8
6
|
{{ picks }}
|
|
9
|
-
])
|
|
10
|
-
|
|
11
|
-
export class {{ UpdateName }} extends _UpdatePicked {
|
|
12
|
-
{% for f in writable -%}
|
|
13
|
-
@ApiPropertyOptional({
|
|
14
|
-
type: {{ f.swagType | json }},
|
|
15
|
-
{% if f.len %} maxLength: {{ f.len }},
|
|
16
|
-
{% endif %}{% if f.nullable %} nullable: true,
|
|
17
|
-
{% endif %} description: {{ ((f.comment or f.camel) + '(PATCH:不传则不修改)') | json }},
|
|
18
|
-
})
|
|
19
|
-
override {{ f.camel }}?: {{ f.createDtoType }};
|
|
7
|
+
]) {
|
|
20
8
|
|
|
21
|
-
{% endfor -%}
|
|
22
9
|
}
|
package/templates/vo-single.njk
CHANGED
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ApiProperty } from '@midwayjs/swagger';
|
|
1
|
+
import { {{ Pascal }} } from '../../entity/{{ Camel }}.entity';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
export class {{ VOName }} {
|
|
6
|
-
{% for f in fields -%}
|
|
7
|
-
@ApiProperty({
|
|
8
|
-
type: {{ f.swagType | json }},
|
|
9
|
-
{% if f.len %} maxLength: {{ f.len }},
|
|
10
|
-
{% endif %}{% if f.nullable %} nullable: true,
|
|
11
|
-
{% endif %} description: {{ (f.comment or f.camel) | json }},
|
|
12
|
-
})
|
|
13
|
-
@Expose()
|
|
14
|
-
{% if f.isDate %} @Transform(({ value }) => {
|
|
15
|
-
if (value == null) return null;
|
|
16
|
-
const d = value instanceof Date ? value : new Date(value);
|
|
17
|
-
if (isNaN(d.getTime())) return value == null ? null : String(value);
|
|
18
|
-
return d.toISOString();
|
|
19
|
-
})
|
|
20
|
-
{% endif %}{% if f.isSensitive %} @Transform(() => undefined, { toPlainOnly: true })
|
|
21
|
-
{% endif %} {{ f.camel }}: {{ f.voType }}{% if f.nullable %} | null{% endif %};
|
|
22
|
-
|
|
23
|
-
{% endfor -%}
|
|
24
|
-
}
|
|
3
|
+
export class {{ VOName }} extends {{ Pascal }} {}
|