Browse Source

✅ 增加 codegen 模块的单测覆盖率

YunaiV 1 year ago
parent
commit
c69fbe540a
22 changed files with 906 additions and 161 deletions
  1. 4 3
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java
  2. 4 56
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java
  3. 55 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java
  4. 6 6
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java
  5. 49 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java
  6. 32 5
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java
  7. 6 30
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/codegen/CodegenConvert.java
  8. 1 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenService.java
  9. 23 21
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java
  10. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
  11. 556 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java
  12. 89 0
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java
  13. 0 4
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/package-info.java
  14. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/logger/ApiErrorLogServiceImplTest.java
  15. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_erp/vue/index
  16. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_inner/vue/index
  17. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_normal/vue/index
  18. 4 4
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_one/vue/index
  19. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_tree/vue/index
  20. 2 0
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/clean.sql
  21. 56 0
      yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql
  22. 1 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java

+ 4 - 3
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/CodegenController.java

@@ -4,6 +4,7 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.ZipUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;
@@ -66,7 +67,7 @@ public class CodegenController {
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
     public CommonResult<List<CodegenTableRespVO>> getCodegenTableList(@RequestParam(value = "dataSourceConfigId") Long dataSourceConfigId) {
         List<CodegenTableDO> list = codegenService.getCodegenTableList(dataSourceConfigId);
-        return success(CodegenConvert.INSTANCE.convertList05(list));
+        return success(BeanUtils.toBean(list, CodegenTableRespVO.class));
     }
 
     @GetMapping("/table/page")
@@ -74,7 +75,7 @@ public class CodegenController {
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
     public CommonResult<PageResult<CodegenTableRespVO>> getCodegenTablePage(@Valid CodegenTablePageReqVO pageReqVO) {
         PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(pageReqVO);
-        return success(CodegenConvert.INSTANCE.convertPage(pageResult));
+        return success(BeanUtils.toBean(pageResult, CodegenTableRespVO.class));
     }
 
     @GetMapping("/detail")
@@ -82,7 +83,7 @@ public class CodegenController {
     @Parameter(name = "tableId", description = "表编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('infra:codegen:query')")
     public CommonResult<CodegenDetailRespVO> getCodegenDetail(@RequestParam("tableId") Long tableId) {
-        CodegenTableDO table = codegenService.getCodegenTablePage(tableId);
+        CodegenTableDO table = codegenService.getCodegenTable(tableId);
         List<CodegenColumnDO> columns = codegenService.getCodegenColumnListByTableId(tableId);
         // 拼装返回
         return success(CodegenConvert.INSTANCE.convert(table, columns));

+ 4 - 56
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java

@@ -1,18 +1,11 @@
 package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo;
 
-import cn.hutool.core.util.ObjectUtil;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnBaseVO;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableBaseVO;
-import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
-import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableSaveReqVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
 
 import javax.validation.Valid;
-import javax.validation.constraints.AssertTrue;
 import javax.validation.constraints.NotNull;
 import java.util.List;
 
@@ -22,55 +15,10 @@ public class CodegenUpdateReqVO {
 
     @Valid // 校验内嵌的字段
     @NotNull(message = "表定义不能为空")
-    private Table table;
+    private CodegenTableSaveReqVO table;
 
     @Valid // 校验内嵌的字段
     @NotNull(message = "字段定义不能为空")
-    private List<Column> columns;
-
-    @Schema(description = "更新表定义")
-    @Data
-    @EqualsAndHashCode(callSuper = true)
-    @ToString(callSuper = true)
-    @Valid
-    public static class Table extends CodegenTableBaseVO {
-
-        @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-        private Long id;
-
-        @AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段")
-        @JsonIgnore
-        public boolean isParentMenuIdValid() {
-            // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的
-            return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())
-                    || getParentMenuId() != null;
-        }
-
-        @AssertTrue(message = "关联的父表信息不全")
-        @JsonIgnore
-        public boolean isSubValid() {
-            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)
-                    || (ObjectUtil.isAllNotEmpty(getMasterTableId(), getSubJoinColumnId(), getSubJoinMany()));
-        }
-
-        @AssertTrue(message = "关联的树表信息不全")
-        @JsonIgnore
-        public boolean isTreeValid() {
-            return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.TREE)
-                    || (ObjectUtil.isAllNotEmpty(getTreeParentColumnId(), getTreeNameColumnId()));
-        }
-
-    }
-
-    @Schema(description = "更新表定义")
-    @Data
-    @EqualsAndHashCode(callSuper = true)
-    @ToString(callSuper = true)
-    public static class Column extends CodegenColumnBaseVO {
-
-        @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-        private Long id;
-
-    }
+    private List<CodegenColumnSaveReqVO> columns;
 
 }

+ 55 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java

@@ -2,20 +2,70 @@ package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
 
 import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 代码生成字段定义 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CodegenColumnRespVO extends CodegenColumnBaseVO {
+public class CodegenColumnRespVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
+    @Schema(description = "表编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long tableId;
+
+    @Schema(description = "字段名", requiredMode = Schema.RequiredMode.REQUIRED, example = "user_age")
+    private String columnName;
+
+    @Schema(description = "字段类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "int(11)")
+    private String dataType;
+
+    @Schema(description = "字段描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "年龄")
+    private String columnComment;
+
+    @Schema(description = "是否允许为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean nullable;
+
+    @Schema(description = "是否主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean primaryKey;
+
+    @Schema(description = "是否自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean autoIncrement;
+
+    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer ordinalPosition;
+
+    @Schema(description = "Java 属性类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "userAge")
+    private String javaType;
+
+    @Schema(description = "Java 属性名", requiredMode = Schema.RequiredMode.REQUIRED, example = "Integer")
+    private String javaField;
+
+    @Schema(description = "字典类型", example = "sys_gender")
+    private String dictType;
+
+    @Schema(description = "数据示例", example = "1024")
+    private String example;
+
+    @Schema(description = "是否为 Create 创建操作的字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean createOperation;
+
+    @Schema(description = "是否为 Update 更新操作的字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+    private Boolean updateOperation;
+
+    @Schema(description = "是否为 List 查询操作的字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean listOperation;
+
+    @Schema(description = "List 查询操作的条件类型,参见 CodegenColumnListConditionEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "LIKE")
+    private String listOperationCondition;
+
+    @Schema(description = "是否为 List 查询操作的返回字段", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean listOperationResult;
+
+    @Schema(description = "显示类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "input")
+    private String htmlType;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 

+ 6 - 6
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnBaseVO.java → yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java

@@ -5,12 +5,12 @@ import lombok.Data;
 
 import javax.validation.constraints.NotNull;
 
-/**
-* 代码生成字段定义 Base VO,提供给添加、修改、详细的子 VO 使用
-* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
-*/
+@Schema(description = "管理后台 - 代码生成字段定义创建/修改 Request VO")
 @Data
-public class CodegenColumnBaseVO {
+public class CodegenColumnSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
 
     @Schema(description = "表编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "表编号不能为空")
@@ -38,7 +38,7 @@ public class CodegenColumnBaseVO {
 
     @Schema(description = "是否自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
     @NotNull(message = "是否自增不能为空")
-    private String autoIncrement;
+    private Boolean autoIncrement;
 
     @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     @NotNull(message = "排序不能为空")

+ 49 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java

@@ -2,20 +2,64 @@ package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
 
 import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 代码生成表定义 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CodegenTableRespVO extends CodegenTableBaseVO {
+public class CodegenTableRespVO {
 
     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     private Long id;
 
+    @Schema(description = "生成场景,参见 CodegenSceneEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer scene;
+
+    @Schema(description = "表名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
+    private String tableName;
+
+    @Schema(description = "表描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
+    private String tableComment;
+
+    @Schema(description = "备注", example = "我是备注")
+    private String remark;
+
+    @Schema(description = "模块名", requiredMode = Schema.RequiredMode.REQUIRED, example = "system")
+    private String moduleName;
+
+    @Schema(description = "业务名", requiredMode = Schema.RequiredMode.REQUIRED, example = "codegen")
+    private String businessName;
+
+    @Schema(description = "类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "CodegenTable")
+    private String className;
+
+    @Schema(description = "类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "代码生成器的表定义")
+    private String classComment;
+
+    @Schema(description = "作者", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
+    private String author;
+
+    @Schema(description = "模板类型,参见 CodegenTemplateTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer templateType;
+
+    @Schema(description = "前端类型,参见 CodegenFrontTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Integer frontType;
+
+    @Schema(description = "父菜单编号", example = "1024")
+    private Long parentMenuId;
+
+    @Schema(description = "主表的编号", example = "2048")
+    private Long masterTableId;
+    @Schema(description = "子表关联主表的字段编号", example = "4096")
+    private Long subJoinColumnId;
+    @Schema(description = "主表与子表是否一对多", example = "4096")
+    private Boolean subJoinMany;
+
+    @Schema(description = "树表的父字段编号", example = "8192")
+    private Long treeParentColumnId;
+    @Schema(description = "树表的名字字段编号", example = "16384")
+    private Long treeNameColumnId;
+
     @Schema(description = "主键编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer dataSourceConfigId;
 

+ 32 - 5
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableBaseVO.java → yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java

@@ -1,16 +1,21 @@
 package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import javax.validation.constraints.AssertTrue;
 import javax.validation.constraints.NotNull;
 
-/**
- * 代码生成 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
+@Schema(description = "管理后台 - 代码生成表定义创建/修改 Response VO")
 @Data
-public class CodegenTableBaseVO {
+public class CodegenTableSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
 
     @Schema(description = "生成场景,参见 CodegenSceneEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "导入类型不能为空")
@@ -70,4 +75,26 @@ public class CodegenTableBaseVO {
     @Schema(description = "树表的名字字段编号", example = "16384")
     private Long treeNameColumnId;
 
+    @AssertTrue(message = "上级菜单不能为空,请前往 [修改生成配置 -> 生成信息] 界面,设置“上级菜单”字段")
+    @JsonIgnore
+    public boolean isParentMenuIdValid() {
+        // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的
+        return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())
+                || getParentMenuId() != null;
+    }
+
+    @AssertTrue(message = "关联的父表信息不全")
+    @JsonIgnore
+    public boolean isSubValid() {
+        return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)
+                || (ObjectUtil.isAllNotEmpty(masterTableId, subJoinColumnId, subJoinMany));
+    }
+
+    @AssertTrue(message = "关联的树表信息不全")
+    @JsonIgnore
+    public boolean isTreeValid() {
+        return ObjectUtil.notEqual(templateType, CodegenTemplateTypeEnum.TREE)
+                || (ObjectUtil.isAllNotEmpty(treeParentColumnId, treeNameColumnId));
+    }
+
 }

+ 6 - 30
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/convert/codegen/CodegenConvert.java

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.infra.convert.codegen;
 
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenPreviewRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;
-import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
 import com.baomidou.mybatisplus.generator.config.po.TableField;
@@ -20,7 +19,6 @@ import org.mapstruct.factory.Mappers;
 
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 @Mapper
 public interface CodegenConvert {
@@ -54,40 +52,18 @@ public interface CodegenConvert {
         return jdbcType.name();
     }
 
-    // ========== CodegenTableDO 相关 ==========
-
-    List<CodegenTableRespVO> convertList05(List<CodegenTableDO> list);
-
-    CodegenTableRespVO convert(CodegenTableDO bean);
-
-    PageResult<CodegenTableRespVO> convertPage(PageResult<CodegenTableDO> page);
-
-    // ========== CodegenTableDO 相关 ==========
-
-    List<CodegenColumnRespVO> convertList02(List<CodegenColumnDO> list);
-
-    CodegenTableDO convert(CodegenUpdateReqVO.Table bean);
-
-    List<CodegenColumnDO> convertList03(List<CodegenUpdateReqVO.Column> columns);
-
-    List<DatabaseTableRespVO> convertList04(List<TableInfo> list);
-
     // ========== 其它 ==========
 
     default CodegenDetailRespVO convert(CodegenTableDO table, List<CodegenColumnDO> columns) {
         CodegenDetailRespVO respVO = new CodegenDetailRespVO();
-        respVO.setTable(convert(table));
-        respVO.setColumns(convertList02(columns));
+        respVO.setTable(BeanUtils.toBean(table, CodegenTableRespVO.class));
+        respVO.setColumns(BeanUtils.toBean(columns, CodegenColumnRespVO.class));
         return respVO;
     }
 
     default List<CodegenPreviewRespVO> convert(Map<String, String> codes) {
-        return codes.entrySet().stream().map(entry -> {
-            CodegenPreviewRespVO respVO = new CodegenPreviewRespVO();
-            respVO.setFilePath(entry.getKey());
-            respVO.setCode(entry.getValue());
-            return respVO;
-        }).collect(Collectors.toList());
+        return CollectionUtils.convertList(codes.entrySet(),
+                entry -> new CodegenPreviewRespVO().setFilePath(entry.getKey()).setCode(entry.getValue()));
     }
 
 }

+ 1 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenService.java

@@ -70,7 +70,7 @@ public interface CodegenService {
      * @param id 表编号
      * @return 表定义
      */
-    CodegenTableDO getCodegenTablePage(Long id);
+    CodegenTableDO getCodegenTable(Long id);
 
     /**
      * 获得指定表的字段定义数组
@@ -91,7 +91,6 @@ public interface CodegenService {
     /**
      * 获得数据库自带的表定义列表
      *
-     *
      * @param dataSourceConfigId 数据源的配置编号
      * @param name 表名称
      * @param comment 表描述

+ 23 - 21
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImpl.java

@@ -3,12 +3,11 @@ package cn.iocoder.yudao.module.infra.service.codegen;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
 import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
-import cn.iocoder.yudao.module.infra.convert.codegen.CodegenConvert;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
 import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
@@ -22,6 +21,7 @@ import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import com.baomidou.mybatisplus.generator.config.po.TableField;
 import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.google.common.annotations.VisibleForTesting;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -31,6 +31,8 @@ import java.util.function.BiPredicate;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
 
 /**
@@ -69,7 +71,7 @@ public class CodegenServiceImpl implements CodegenService {
         return ids;
     }
 
-    public Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) {
+    private Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) {
         // 从数据库中,获得数据库表结构
         TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, tableName);
         // 导入
@@ -103,7 +105,8 @@ public class CodegenServiceImpl implements CodegenService {
         return table.getId();
     }
 
-    private void validateTableInfo(TableInfo tableInfo) {
+    @VisibleForTesting
+    void validateTableInfo(TableInfo tableInfo) {
         if (tableInfo == null) {
             throw exception(CODEGEN_IMPORT_TABLE_NULL);
         }
@@ -139,10 +142,10 @@ public class CodegenServiceImpl implements CodegenService {
         }
 
         // 更新 table 表定义
-        CodegenTableDO updateTableObj = CodegenConvert.INSTANCE.convert(updateReqVO.getTable());
+        CodegenTableDO updateTableObj = BeanUtils.toBean(updateReqVO.getTable(), CodegenTableDO.class);
         codegenTableMapper.updateById(updateTableObj);
         // 更新 column 字段定义
-        List<CodegenColumnDO> updateColumnObjs = CodegenConvert.INSTANCE.convertList03(updateReqVO.getColumns());
+        List<CodegenColumnDO> updateColumnObjs = BeanUtils.toBean(updateReqVO.getColumns(), CodegenColumnDO.class);
         updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj));
     }
 
@@ -161,29 +164,28 @@ public class CodegenServiceImpl implements CodegenService {
     }
 
     private void syncCodegen0(Long tableId, TableInfo tableInfo) {
-        // 校验导入的表和字段非空
+        // 1. 校验导入的表和字段非空
         validateTableInfo(tableInfo);
         List<TableField> tableFields = tableInfo.getFields();
 
-        // 构建 CodegenColumnDO 数组,只同步新增的字段
+        // 2. 构建 CodegenColumnDO 数组,只同步新增的字段
         List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
-        Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
+        Set<String> codegenColumnNames = convertSet(codegenColumns, CodegenColumnDO::getColumnName);
 
-        //计算需要修改的字段,插入时重新插入,删除时将原来的删除
-        BiPredicate<TableField, CodegenColumnDO> pr =
+        // 3.1 计算需要【修改】的字段,插入时重新插入,删除时将原来的删除
+        Map<String, CodegenColumnDO> codegenColumnDOMap = convertMap(codegenColumns, CodegenColumnDO::getColumnName);
+        BiPredicate<TableField, CodegenColumnDO> primaryKeyPredicate =
                 (tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType())
                         && tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()
                         && tableField.isKeyFlag() == codegenColumn.getPrimaryKey()
                         && tableField.getComment().equals(codegenColumn.getColumnComment());
-        Map<String, CodegenColumnDO> codegenColumnDOMap = CollectionUtils.convertMap(codegenColumns, CodegenColumnDO::getColumnName);
-        //需要修改的字段
         Set<String> modifyFieldNames = tableFields.stream()
                 .filter(tableField -> codegenColumnDOMap.get(tableField.getColumnName()) != null
-                        && !pr.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))
+                        && !primaryKeyPredicate.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))
                 .map(TableField::getColumnName)
                 .collect(Collectors.toSet());
-        // 计算需要删除的字段
-        Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
+        // 3.2 计算需要删除的字段
+        Set<String> tableFieldNames = convertSet(tableFields, TableField::getName);
         Set<Long> deleteColumnIds = codegenColumns.stream()
                 .filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName()))
                 .map(CodegenColumnDO::getId).collect(Collectors.toSet());
@@ -193,10 +195,10 @@ public class CodegenServiceImpl implements CodegenService {
             throw exception(CODEGEN_SYNC_NONE_CHANGE);
         }
 
-        // 插入新增的字段
+        // 4.1 插入新增的字段
         List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
         codegenColumnMapper.insertBatch(columns);
-        // 删除不存在的字段
+        // 4.2 删除不存在的字段
         if (CollUtil.isNotEmpty(deleteColumnIds)) {
             codegenColumnMapper.deleteBatchIds(deleteColumnIds);
         }
@@ -227,7 +229,7 @@ public class CodegenServiceImpl implements CodegenService {
     }
 
     @Override
-    public CodegenTableDO getCodegenTablePage(Long id) {
+    public CodegenTableDO getCodegenTable(Long id) {
         return codegenTableMapper.selectById(id);
     }
 
@@ -277,10 +279,10 @@ public class CodegenServiceImpl implements CodegenService {
     public List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {
         List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);
         // 移除在 Codegen 中,已经存在的
-        Set<String> existsTables = CollectionUtils.convertSet(
+        Set<String> existsTables = convertSet(
                 codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId), CodegenTableDO::getTableName);
         tables.removeIf(table -> existsTables.contains(table.getName()));
-        return CodegenConvert.INSTANCE.convertList04(tables);
+        return BeanUtils.toBean(tables, DatabaseTableRespVO.class);
     }
 
 }

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm

@@ -269,7 +269,7 @@ const queryParams = reactive({
   #foreach ($column in $columns)
     #if ($column.listOperation)
       #if ($column.listOperationCondition != 'BETWEEN')
-  $column.javaField: null,
+  $column.javaField: undefined,
   #end
       #if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
   $column.javaField: [],

+ 556 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/CodegenServiceImplTest.java

@@ -0,0 +1,556 @@
+package cn.iocoder.yudao.module.infra.service.codegen;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;
+import cn.iocoder.yudao.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenColumnMapper;
+import cn.iocoder.yudao.module.infra.dal.mysql.codegen.CodegenTableMapper;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
+import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
+import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
+import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenBuilder;
+import cn.iocoder.yudao.module.infra.service.codegen.inner.CodegenEngine;
+import cn.iocoder.yudao.module.infra.service.db.DatabaseTableService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link CodegenServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(CodegenServiceImpl.class)
+public class CodegenServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private CodegenServiceImpl codegenService;
+
+    @Resource
+    private CodegenTableMapper codegenTableMapper;
+    @Resource
+    private CodegenColumnMapper codegenColumnMapper;
+
+    @MockBean
+    private DatabaseTableService databaseTableService;
+
+    @MockBean
+    private AdminUserApi userApi;
+
+    @MockBean
+    private CodegenBuilder codegenBuilder;
+    @MockBean
+    private CodegenEngine codegenEngine;
+
+    @MockBean
+    private CodegenProperties codegenProperties;
+
+    @Test
+    public void testCreateCodegenList() {
+        // 准备参数
+        Long userId = randomLongId();
+        CodegenCreateListReqVO reqVO = randomPojo(CodegenCreateListReqVO.class,
+                o -> o.setDataSourceConfigId(1L).setTableNames(Collections.singletonList("t_yunai")));
+        // mock 方法(TableInfo)
+        TableInfo tableInfo = mock(TableInfo.class);
+        when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+                .thenReturn(tableInfo);
+        when(tableInfo.getComment()).thenReturn("芋艿");
+        // mock 方法(TableInfo fields)
+        TableField field01 = mock(TableField.class);
+        when(field01.getComment()).thenReturn("主键");
+        TableField field02 = mock(TableField.class);
+        when(field02.getComment()).thenReturn("名字");
+        List<TableField> fields = Arrays.asList(field01, field02);
+        when(tableInfo.getFields()).thenReturn(fields);
+        // mock 方法(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class);
+        when(codegenBuilder.buildTable(same(tableInfo))).thenReturn(table);
+        // mock 方法(AdminUserRespDTO)
+        AdminUserRespDTO user = randomPojo(AdminUserRespDTO.class, o -> o.setNickname("芋头"));
+        when(userApi.getUser(eq(userId))).thenReturn(user);
+        // mock 方法(CodegenColumnDO)
+        List<CodegenColumnDO> columns = randomPojoList(CodegenColumnDO.class);
+        when(codegenBuilder.buildColumns(eq(table.getId()), same(fields)))
+                .thenReturn(columns);
+        // mock 方法(CodegenProperties)
+        when(codegenProperties.getFrontType()).thenReturn(CodegenFrontTypeEnum.VUE3.getType());
+
+        // 调用
+        List<Long> result = codegenService.createCodegenList(userId, reqVO);
+        // 断言
+        assertEquals(1, result.size());
+        // 断言(CodegenTableDO)
+        CodegenTableDO dbTable = codegenTableMapper.selectList().get(0);
+        assertPojoEquals(table, dbTable);
+        assertEquals(1L, dbTable.getDataSourceConfigId());
+        assertEquals(CodegenSceneEnum.ADMIN.getScene(), dbTable.getScene());
+        assertEquals(CodegenFrontTypeEnum.VUE3.getType(), dbTable.getFrontType());
+        assertEquals("芋头", dbTable.getAuthor());
+        // 断言(CodegenColumnDO)
+        List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
+        assertEquals(columns.size(), dbColumns.size());
+        assertTrue(dbColumns.get(0).getPrimaryKey());
+        for (int i = 0; i < dbColumns.size(); i++) {
+            assertPojoEquals(columns.get(i), dbColumns.get(i));
+        }
+    }
+
+    @Test
+    public void testValidateTableInfo() {
+        // 情况一
+        assertServiceException(() -> codegenService.validateTableInfo(null),
+                CODEGEN_IMPORT_TABLE_NULL);
+        // 情况二
+        TableInfo tableInfo = mock(TableInfo.class);
+        assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+                CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);
+        // 情况三
+        when(tableInfo.getComment()).thenReturn("芋艿");
+        assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+                CODEGEN_IMPORT_COLUMNS_NULL);
+        // 情况四
+        TableField field = mock(TableField.class);
+        when(field.getName()).thenReturn("name");
+        when(tableInfo.getFields()).thenReturn(Collections.singletonList(field));
+        assertServiceException(() -> codegenService.validateTableInfo(tableInfo),
+                CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());
+    }
+
+    @Test
+    public void testUpdateCodegen_notExists() {
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class);
+        // mock 方法
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+                CODEGEN_TABLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testUpdateCodegen_sub_masterNotExists() {
+        // mock 数据
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+                o -> o.getTable().setId(table.getId())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()));
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+                CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId());
+    }
+
+    @Test
+    public void testUpdateCodegen_sub_columnNotExists() {
+        // mock 数据
+        CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(subTable);
+        // mock 数据(master)
+        CodegenTableDO masterTable = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(masterTable);
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+                o -> o.getTable().setId(subTable.getId())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setMasterTableId(masterTable.getId()));
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.updateCodegen(updateReqVO),
+                CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId());
+    }
+
+    @Test
+    public void testUpdateCodegen_success() {
+        // mock 数据
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column02);
+        // 准备参数
+        CodegenUpdateReqVO updateReqVO = randomPojo(CodegenUpdateReqVO.class,
+                o -> o.getTable().setId(table.getId())
+                        .setTemplateType(CodegenTemplateTypeEnum.ONE.getType())
+                        .setScene(CodegenSceneEnum.ADMIN.getScene()));
+        CodegenColumnSaveReqVO columnVO01 = randomPojo(CodegenColumnSaveReqVO.class,
+                o -> o.setId(column01.getId()).setTableId(table.getId()));
+        CodegenColumnSaveReqVO columnVO02 = randomPojo(CodegenColumnSaveReqVO.class,
+                o -> o.setId(column02.getId()).setTableId(table.getId()));
+        updateReqVO.setColumns(Arrays.asList(columnVO01, columnVO02));
+
+        // 调用
+        codegenService.updateCodegen(updateReqVO);
+        // 断言
+        CodegenTableDO dbTable = codegenTableMapper.selectById(table.getId());
+        assertPojoEquals(updateReqVO.getTable(), dbTable);
+        List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
+        assertEquals(2, dbColumns.size());
+        assertPojoEquals(columnVO01, dbColumns.get(0));
+        assertPojoEquals(columnVO02, dbColumns.get(1));
+    }
+
+    @Test
+    public void testSyncCodegenFromDB() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class, o -> o.setTableName("t_yunai")
+                .setDataSourceConfigId(1L).setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
+                .setColumnName("id"));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId())
+                .setColumnName("name"));
+        codegenColumnMapper.insert(column02);
+        // 准备参数
+        Long tableId = table.getId();
+        // mock 方法(TableInfo)
+        TableInfo tableInfo = mock(TableInfo.class);
+        when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+                .thenReturn(tableInfo);
+        when(tableInfo.getComment()).thenReturn("芋艿");
+        // mock 方法(TableInfo fields)
+        TableField field01 = mock(TableField.class);
+        when(field01.getComment()).thenReturn("主键");
+        TableField field03 = mock(TableField.class);
+        when(field03.getComment()).thenReturn("分类");
+        List<TableField> fields = Arrays.asList(field01, field03);
+        when(tableInfo.getFields()).thenReturn(fields);
+        when(databaseTableService.getTable(eq(1L), eq("t_yunai")))
+                .thenReturn(tableInfo);
+        // mock 方法(CodegenTableDO)
+        List<CodegenColumnDO> newColumns = randomPojoList(CodegenColumnDO.class);
+        when(codegenBuilder.buildColumns(eq(table.getId()), argThat(tableFields -> {
+            assertEquals(2, tableFields.size());
+            assertSame(tableInfo.getFields(), tableFields);
+            return true;
+        }))).thenReturn(newColumns);
+
+        // 调用
+        codegenService.syncCodegenFromDB(tableId);
+        // 断言
+        List<CodegenColumnDO> dbColumns = codegenColumnMapper.selectList();
+        assertEquals(newColumns.size(), dbColumns.size());
+        assertPojoEquals(newColumns.get(0), dbColumns.get(0));
+        assertPojoEquals(newColumns.get(1), dbColumns.get(1));
+    }
+
+    @Test
+    public void testDeleteCodegen_notExists() {
+        assertServiceException(() -> codegenService.deleteCodegen(randomLongId()),
+                CODEGEN_TABLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteCodegen_success() {
+        // mock 数据
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table);
+        CodegenColumnDO column = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用
+        codegenService.deleteCodegen(tableId);
+        // 断言
+        assertNull(codegenTableMapper.selectById(tableId));
+        assertEquals(0, codegenColumnMapper.selectList().size());
+    }
+
+    @Test
+    public void testGetCodegenTableList() {
+        // mock 数据
+        CodegenTableDO table01 = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table01);
+        CodegenTableDO table02 = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(table02);
+        // 准备参数
+        Long dataSourceConfigId = table01.getDataSourceConfigId();
+
+        // 调用
+        List<CodegenTableDO> result = codegenService.getCodegenTableList(dataSourceConfigId);
+        // 断言
+        assertEquals(1, result.size());
+        assertPojoEquals(table01, result.get(0));
+    }
+
+    @Test
+    public void testGetCodegenTablePage() {
+        // mock 数据
+        CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> {
+            o.setTableName("t_yunai");
+            o.setTableComment("芋艿");
+            o.setClassName("SystemYunai");
+            o.setCreateTime(buildTime(2021, 3, 10));
+        }).setScene(CodegenSceneEnum.ADMIN.getScene());
+        codegenTableMapper.insert(tableDO);
+        // 测试 tableName 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableName(randomString())));
+        // 测试 tableComment 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setTableComment(randomString())));
+        // 测试 className 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, o -> o.setClassName(randomString())));
+        // 测试 createTime 不匹配
+        codegenTableMapper.insert(cloneIgnoreId(tableDO, logDO -> logDO.setCreateTime(buildTime(2021, 4, 10))));
+        // 准备参数
+        CodegenTablePageReqVO reqVO = new CodegenTablePageReqVO();
+        reqVO.setTableName("yunai");
+        reqVO.setTableComment("芋");
+        reqVO.setClassName("Yunai");
+        reqVO.setCreateTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
+
+        // 调用
+        PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(reqVO);
+        // 断言,只查到了一条符合条件的
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(tableDO, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetCodegenTable() {
+        // mock 数据
+        CodegenTableDO tableDO = randomPojo(CodegenTableDO.class, o -> o.setScene(CodegenSceneEnum.ADMIN.getScene()));
+        codegenTableMapper.insert(tableDO);
+        // 准备参数
+        Long id = tableDO.getId();
+
+        // 调用
+        CodegenTableDO result = codegenService.getCodegenTable(id);
+        // 断言
+        assertPojoEquals(tableDO, result);
+    }
+
+    @Test
+    public void testGetCodegenColumnListByTableId() {
+        // mock 数据
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class);
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class);
+        codegenColumnMapper.insert(column02);
+        // 准备参数
+        Long tableId = column01.getTableId();
+
+        // 调用
+        List<CodegenColumnDO> result = codegenService.getCodegenColumnListByTableId(tableId);
+        // 断言
+        assertEquals(1, result.size());
+        assertPojoEquals(column01, result.get(0));
+    }
+
+    @Test
+    public void testGenerationCodes_tableNotExists() {
+        assertServiceException(() -> codegenService.generationCodes(randomLongId()),
+                CODEGEN_TABLE_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGenerationCodes_columnNotExists() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.generationCodes(tableId),
+                CODEGEN_COLUMN_NOT_EXISTS);
+    }
+
+    @Test
+    public void testGenerationCodes_sub_tableNotExists() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.generationCodes(tableId),
+                CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);
+    }
+
+    @Test
+    public void testGenerationCodes_sub_columnNotExists() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        // mock 数据(sub CodegenTableDO)
+        CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setMasterTableId(table.getId()));
+        codegenTableMapper.insert(subTable);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用,并断言
+        assertServiceException(() -> codegenService.generationCodes(tableId),
+                CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());
+    }
+
+    @Test
+    public void testGenerationCodes_one_success() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.ONE.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column02);
+        // mock 执行生成
+        Map<String, String> codes = MapUtil.of(randomString(), randomString());
+        when(codegenEngine.execute(eq(table), argThat(columns -> {
+            assertEquals(2, columns.size());
+            assertEquals(column01, columns.get(0));
+            assertEquals(column02, columns.get(1));
+            return true;
+        }), isNull(), isNull())).thenReturn(codes);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用
+        Map<String, String> result = codegenService.generationCodes(tableId);
+        // 断言
+        assertSame(codes, result);
+    }
+
+    @Test
+    public void testGenerationCodes_master_success() {
+        // mock 数据(CodegenTableDO)
+        CodegenTableDO table = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType()));
+        codegenTableMapper.insert(table);
+        // mock 数据(CodegenColumnDO)
+        CodegenColumnDO column01 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column01);
+        CodegenColumnDO column02 = randomPojo(CodegenColumnDO.class, o -> o.setTableId(table.getId()));
+        codegenColumnMapper.insert(column02);
+        // mock 数据(sub CodegenTableDO)
+        CodegenTableDO subTable = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
+                        .setMasterTableId(table.getId())
+                        .setSubJoinColumnId(1024L));
+        codegenTableMapper.insert(subTable);
+        // mock 数据(sub CodegenColumnDO)
+        CodegenColumnDO subColumn01 = randomPojo(CodegenColumnDO.class, o -> o.setId(1024L).setTableId(subTable.getId()));
+        codegenColumnMapper.insert(subColumn01);
+        // mock 执行生成
+        Map<String, String> codes = MapUtil.of(randomString(), randomString());
+        when(codegenEngine.execute(eq(table), argThat(columns -> {
+            assertEquals(2, columns.size());
+            assertEquals(column01, columns.get(0));
+            assertEquals(column02, columns.get(1));
+            return true;
+        }), argThat(tables -> {
+            assertEquals(1, tables.size());
+            assertPojoEquals(subTable, tables.get(0));
+            return true;
+        }), argThat(columns -> {
+            assertEquals(1, columns.size());
+            assertPojoEquals(subColumn01, columns.size());
+            return true;
+        }))).thenReturn(codes);
+        // 准备参数
+        Long tableId = table.getId();
+
+        // 调用
+        Map<String, String> result = codegenService.generationCodes(tableId);
+        // 断言
+        assertSame(codes, result);
+    }
+
+    @Test
+    public void testGetDatabaseTableList() {
+        // 准备参数
+        Long dataSourceConfigId = randomLongId();
+        String name = randomString();
+        String comment = randomString();
+        // mock 方法
+        TableInfo tableInfo01 = mock(TableInfo.class);
+        when(tableInfo01.getName()).thenReturn("t_yunai");
+        when(tableInfo01.getComment()).thenReturn("芋艿");
+        TableInfo tableInfo02 = mock(TableInfo.class);
+        when(tableInfo02.getName()).thenReturn("t_yunai_02");
+        when(tableInfo02.getComment()).thenReturn("芋艿_02");
+        when(databaseTableService.getTableList(eq(dataSourceConfigId), eq(name), eq(comment)))
+                .thenReturn(ListUtil.toList(tableInfo01, tableInfo02));
+        // mock 数据
+        CodegenTableDO tableDO = randomPojo(CodegenTableDO.class,
+                o -> o.setScene(CodegenSceneEnum.ADMIN.getScene())
+                        .setTableName("t_yunai_02")
+                        .setDataSourceConfigId(dataSourceConfigId));
+        codegenTableMapper.insert(tableDO);
+
+        // 调用
+        List<DatabaseTableRespVO> result = codegenService.getDatabaseTableList(dataSourceConfigId, name, comment);
+        // 断言
+        assertEquals(1, result.size());
+        assertEquals("t_yunai", result.get(0).getName());
+        assertEquals("芋艿", result.get(0).getComment());
+    }
+
+}

+ 89 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenBuilderTest.java

@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.infra.service.codegen.inner;
+
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
+import com.baomidou.mybatisplus.generator.config.po.TableField;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
+import org.apache.ibatis.type.JdbcType;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CodegenBuilderTest extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private CodegenBuilder codegenBuilder;
+
+    @Test
+    public void testBuildTable() {
+        // 准备参数
+        TableInfo tableInfo = mock(TableInfo.class);
+        // mock 方法
+        when(tableInfo.getName()).thenReturn("system_user");
+        when(tableInfo.getComment()).thenReturn("用户");
+
+        // 调用
+        CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
+        // 断言
+        assertEquals("system_user", table.getTableName());
+        assertEquals("用户", table.getTableComment());
+        assertEquals("system", table.getModuleName());
+        assertEquals("user", table.getBusinessName());
+        assertEquals("User", table.getClassName());
+        assertEquals("用户", table.getClassComment());
+    }
+
+    @Test
+    public void testBuildColumns() {
+        // 准备参数
+        Long tableId = randomLongId();
+        TableField tableField = mock(TableField.class);
+        List<TableField> tableFields = Collections.singletonList(tableField);
+        // mock 方法
+        TableField.MetaInfo metaInfo = mock(TableField.MetaInfo.class);
+        when(tableField.getMetaInfo()).thenReturn(metaInfo);
+        when(metaInfo.getJdbcType()).thenReturn(JdbcType.BIGINT);
+        when(tableField.getComment()).thenReturn("编号");
+        when(tableField.isKeyFlag()).thenReturn(true);
+        when(tableField.isKeyIdentityFlag()).thenReturn(true);
+        IColumnType columnType = mock(IColumnType.class);
+        when(tableField.getColumnType()).thenReturn(columnType);
+        when(columnType.getType()).thenReturn("Long");
+        when(tableField.getName()).thenReturn("id2");
+        when(tableField.getPropertyName()).thenReturn("id");
+
+        // 调用
+        List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);
+        // 断言
+        assertEquals(1, columns.size());
+        CodegenColumnDO column = columns.get(0);
+        assertEquals(tableId, column.getTableId());
+        assertEquals("id2", column.getColumnName());
+        assertEquals("BIGINT", column.getDataType());
+        assertEquals("编号", column.getColumnComment());
+        assertFalse(column.getNullable());
+        assertTrue(column.getPrimaryKey());
+        assertTrue(column.getAutoIncrement());
+        assertEquals(1, column.getOrdinalPosition());
+        assertEquals("Long", column.getJavaType());
+        assertEquals("id", column.getJavaField());
+        assertNull(column.getDictType());
+        assertNotNull(column.getExample());
+        assertFalse(column.getCreateOperation());
+        assertTrue(column.getUpdateOperation());
+        assertFalse(column.getListOperation());
+        assertEquals("=", column.getListOperationCondition());
+        assertTrue(column.getListOperationResult());
+        assertEquals("input", column.getHtmlType());
+    }
+
+}

+ 0 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 占位,无其它作用
- */
-package cn.iocoder.yudao.module.infra.service.codegen;

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/logger/ApiErrorLogServiceImplTest.java

@@ -51,13 +51,13 @@ public class ApiErrorLogServiceImplTest extends BaseDbUnitTest {
         // 测试 userId 不匹配
         apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserId(3344L)));
         // 测试 userType 不匹配
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
         // 测试 applicationName 不匹配
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setApplicationName("test")));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setApplicationName("test")));
         // 测试 requestUrl 不匹配
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setRequestUrl("bar")));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setRequestUrl("bar")));
         // 测试 exceptionTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
-        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setExceptionTime(buildTime(2021, 2, 6))));
+        apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setExceptionTime(buildTime(2021, 2, 6))));
         // 测试 progressStatus 不匹配
         apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
         // 准备参数

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_erp/vue/index

@@ -197,11 +197,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_inner/vue/index

@@ -192,11 +192,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_master_normal/vue/index

@@ -177,11 +177,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 4 - 4
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_one/vue/index

@@ -177,11 +177,11 @@ const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  name: null,
-  birthday: null,
+  name: undefined,
+  birthday: undefined,
   birthday: [],
-  sex: null,
-  enabled: null,
+  sex: undefined,
+  enabled: undefined,
   createTime: []
 })
 const queryFormRef = ref() // 搜索的表单

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue3_tree/vue/index

@@ -106,7 +106,7 @@ const { t } = useI18n() // 国际化
 const loading = ref(true) // 列表的加载中
 const list = ref([]) // 列表的数据
 const queryParams = reactive({
-  name: null
+  name: undefined
 })
 const queryFormRef = ref() // 搜索的表单
 const exportLoading = ref(false) // 导出的加载中

+ 2 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/clean.sql

@@ -8,3 +8,5 @@ DELETE FROM "infra_api_error_log";
 DELETE FROM "infra_file_config";
 DELETE FROM "infra_test_demo";
 DELETE FROM "infra_data_source_config";
+DELETE FROM "infra_codegen_table";
+DELETE FROM "infra_codegen_column";

+ 56 - 0
yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql

@@ -170,3 +170,59 @@ CREATE TABLE IF NOT EXISTS "infra_data_source_config" (
     "deleted" bit NOT NULL DEFAULT FALSE,
     PRIMARY KEY ("id")
 ) COMMENT '数据源配置表';
+
+CREATE TABLE IF NOT EXISTS "infra_codegen_table" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "data_source_config_id" bigint not null,
+    "scene" tinyint not null DEFAULT 1,
+    "table_name" varchar(200) NOT NULL,
+    "table_comment" varchar(500) NOT NULL,
+    "remark" varchar(500) NOT NULL,
+    "module_name" varchar(30) NOT NULL,
+    "business_name" varchar(30) NOT NULL,
+    "class_name" varchar(100) NOT NULL,
+    "class_comment" varchar(50) NOT NULL,
+    "author" varchar(50) NOT NULL,
+    "template_type" tinyint not null DEFAULT 1,
+    "front_type" tinyint not null,
+    "parent_menu_id" bigint not null,
+    "master_table_id" bigint not null,
+    "sub_join_column_id" bigint not null,
+    "sub_join_many" bit not null,
+    "tree_parent_column_id" bigint not null,
+    "tree_name_column_id" bigint not null,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT '代码生成表定义表';
+
+CREATE TABLE IF NOT EXISTS "infra_codegen_column" (
+    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "table_id" bigint not null,
+    "column_name" varchar(200) NOT NULL,
+    "data_type" varchar(100) NOT NULL,
+    "column_comment" varchar(500) NOT NULL,
+    "nullable" tinyint not null,
+    "primary_key" tinyint not null,
+    "auto_increment" varchar(5) not null,
+    "ordinal_position" int not null,
+    "java_type" varchar(32) NOT NULL,
+    "java_field" varchar(64) NOT NULL,
+    "dict_type" varchar(200) NOT NULL,
+    "example" varchar(64) NOT NULL,
+    "create_operation" bit not null,
+    "update_operation" bit not null,
+    "list_operation" bit not null,
+    "list_operation_condition" varchar(32) not null,
+    "list_operation_result" bit not null,
+    "html_type" varchar(32) NOT NULL,
+    "creator" varchar(64) DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater" varchar(64) DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted" bit NOT NULL DEFAULT FALSE,
+    PRIMARY KEY ("id")
+) COMMENT '代码生成表字段定义表';

+ 1 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java

@@ -14,8 +14,7 @@ import java.util.Set;
 @Data
 public class UserSaveReqVO {
 
-    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
-    @NotNull(message = "用户编号不能为空")
+    @Schema(description = "用户编号", example = "1024")
     private Long id;
 
     @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")