代码生成:主子表的部分实现

This commit is contained in:
YunaiV 2023-11-08 09:42:16 +08:00
parent 53afc9d50a
commit 2afc2caf1d
14 changed files with 245 additions and 29 deletions

View File

@ -132,4 +132,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
Db.saveOrUpdateBatch(collection);
}
default int delete(String field, String value) {
return delete(new QueryWrapper<T>().eq(field, value));
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
@ -25,6 +26,7 @@ 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.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 com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableTable;
@ -34,11 +36,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.*;
import static cn.hutool.core.map.MapUtil.getStr;
import static cn.hutool.core.text.CharSequenceUtil.*;
@ -74,8 +72,12 @@ public class CodegenEngine {
javaModuleImplMainFilePath("convert/${table.businessName}/${table.className}Convert"))
.put(javaTemplatePath("dal/do"),
javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
.put(javaTemplatePath("dal/do_sub"), // 特殊主子表专属逻辑
javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${subTable.className}DO"))
.put(javaTemplatePath("dal/mapper"),
javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${table.className}Mapper"))
.put(javaTemplatePath("dal/mapper_sub"), // 特殊主子表专属逻辑
javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${subTable.className}Mapper"))
.put(javaTemplatePath("dal/mapper.xml"), mapperXmlFilePath())
.put(javaTemplatePath("service/serviceImpl"),
javaModuleImplMainFilePath("service/${table.businessName}/${table.className}ServiceImpl"))
@ -196,11 +198,6 @@ public class CodegenEngine {
bindingMap.put("columns", columns);
bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, CodegenColumnDO::getPrimaryKey)); // 主键字段
bindingMap.put("sceneEnum", CodegenSceneEnum.valueOf(table.getScene()));
if (subTable != null) {
bindingMap.put("subTable", subTable);
bindingMap.put("subColumns", subColumns);
bindingMap.put("subColumn", CollectionUtils.findFirst(subColumns, column -> column.getId().equals(table.getSubColumnId())));
}
// className 相关
// 去掉指定前缀 TestDictType 转换成 DictType. 因为在 create 等方法后不需要带上 Test 前缀
@ -214,8 +211,21 @@ public class CodegenEngine {
// permission 前缀
bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
// 特殊主子表专属逻辑
if (subTable != null) {
// 创建 bindingMap
bindingMap.put("subTable", subTable);
bindingMap.put("subColumns", subColumns);
bindingMap.put("subColumn", CollectionUtils.findFirst(subColumns, // 关联的字段
column -> Objects.equals(column.getId(), table.getSubColumnId())));
// className 相关
String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
bindingMap.put("subSimpleClassName", subSimpleClassName);
bindingMap.put("subClassNameVar", lowerFirst(subSimpleClassName)); // DictType 转换成 dictType用于变量
}
// 执行生成
Map<String, String> templates = getTemplates(table.getFrontType());
Map<String, String> templates = getTemplates(table.getTemplateType(), table.getFrontType());
Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
templates.forEach((vmPath, filePath) -> {
filePath = formatFilePath(filePath, bindingMap);
@ -227,10 +237,15 @@ public class CodegenEngine {
return result;
}
private Map<String, String> getTemplates(Integer frontType) {
private Map<String, String> getTemplates(Integer templateType, Integer frontType) {
Map<String, String> templates = new LinkedHashMap<>();
templates.putAll(SERVER_TEMPLATES);
templates.putAll(FRONT_TEMPLATES.row(frontType));
// 特殊主子表专属逻辑
if (ObjUtil.notEqual(templateType, CodegenTemplateTypeEnum.MASTER_SUB.getType())) {
templates.remove(javaTemplatePath("dal/do_sub"));
templates.remove(javaTemplatePath("dal/mapper_sub"));
}
return templates;
}
@ -250,6 +265,11 @@ public class CodegenEngine {
filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName());
filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
// 特殊主子表专属逻辑
CodegenTableDO subTable = (CodegenTableDO) bindingMap.get("subTable");
if (subTable != null) {
filePath = StrUtil.replace(filePath, "${subTable.className}", subTable.getClassName());
}
return filePath;
}

View File

@ -22,6 +22,10 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
#break
#end
#end
## 特殊:主子表专属逻辑
#if ( $subTable )
import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
#end
/**
* ${table.classComment} Base VO提供给添加、修改、详细的子 VO 使用
@ -35,5 +39,10 @@ public class ${sceneEnum.prefixClass}${table.className}BaseVO {
#parse("codegen/java/controller/vo/_column.vm")
#end
#end
## 特殊:主子表专属逻辑
#if ( $subTable )
private List<${subTable.className}DO> ${subClassNameVar}s;
#end
}

View File

@ -0,0 +1,47 @@
package ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName};
import lombok.*;
import java.util.*;
#foreach ($column in $subColumns)
#if (${column.javaType} == "BigDecimal")
import java.math.BigDecimal;
#end
#if (${column.javaType} == "LocalDateTime")
import java.time.LocalDateTime;
#end
#end
import com.baomidou.mybatisplus.annotation.*;
import ${BaseDOClassName};
/**
* ${subTable.classComment} DO
*
* @author ${subTable.author}
*/
@TableName("${subTable.tableName.toLowerCase()}")
@KeySequence("${subTable.tableName.toLowerCase()}_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ${subTable.className}DO extends BaseDO {
#foreach ($column in $subColumns)
#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段
/**
* ${column.columnComment}
#if ("$!column.dictType" != "")##处理枚举值
*
* 枚举 {@link TODO ${column.dictType} 对应的类}
#end
*/
#if (${column.primaryKey})##处理主键
@TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end
#end
private ${column.javaType} ${column.javaField};
#end
#end
}

View File

@ -0,0 +1,26 @@
package ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName};
import java.util.*;
import ${BaseMapperClassName};
import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
import org.apache.ibatis.annotations.Mapper;
#set ($SubColumnName = $subColumn.javaField.substring(0,1).toUpperCase() + ${subColumn.javaField.substring(1)})##首字母大写
/**
* ${subTable.classComment} Mapper
*
* @author ${subTable.author}
*/
@Mapper
public interface ${subTable.className}Mapper extends BaseMapperX<${subTable.className}DO> {
default List<${subTable.className}DO> selectListBy${SubColumnName}(${subColumn.javaType} ${subColumn.javaField}) {
return selectList(${subTable.className}DO::get${SubColumnName}, ${subColumn.javaField});
}
default List<${subTable.className}DO> selectListBy${SubColumnName}(List<${subColumn.javaType}> ${subColumn.javaField}s) {
return selectList(${subTable.className}DO::get${SubColumnName}, ${subColumn.javaField}s);
}
}

View File

@ -7,10 +7,18 @@ import org.springframework.validation.annotation.Validated;
import java.util.*;
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
## 特殊:主子表专属逻辑
#if( $subTable )
import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;
#end
import ${PageResultClassName};
import ${basePackage}.module.${table.moduleName}.convert.${table.businessName}.${table.className}Convert;
import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;
## 特殊:主子表专属逻辑
#if( $subTable )
import ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}.${subClassNameVar}Mapper;
#end
import static ${ServiceExceptionUtilClassName}.exception;
import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;
@ -29,12 +37,22 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
@Resource
private ${table.className}Mapper ${classNameVar}Mapper;
## 特殊:主子表专属逻辑
#if( $subTable )
@Resource
private ${subTable.className}Mapper ${subClassNameVar}Mapper;
#end
@Override
public ${primaryColumn.javaType} create${simpleClassName}(${sceneEnum.prefixClass}${table.className}CreateReqVO createReqVO) {
// 插入
${table.className}DO ${classNameVar} = ${table.className}Convert.INSTANCE.convert(createReqVO);
${classNameVar}Mapper.insert(${classNameVar});
## 特殊:主子表专属逻辑
#if( $subTable )
// 插入子表
${subClassNameVar}Mapper.insertBatch(createReqVO.get${subSimpleClassName}s);
#end
// 返回
return ${classNameVar}.getId();
}

View File

@ -1,9 +1,6 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ClassUtil;
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;
@ -13,16 +10,14 @@ import org.apache.ibatis.type.JdbcType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* {@link CodegenEngine} 的单元测试
@ -118,7 +113,7 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
.setCreateOperation(true).setUpdateOperation(true).setListOperation(true)
.setListOperationCondition(CodegenColumnListConditionEnum.BETWEEN.getCondition()).setListOperationResult(true)
.setHtmlType(CodegenColumnHtmlTypeEnum.DATETIME.getType());
List<CodegenColumnDO> columns = Arrays.asList(idColumn, nameColumn, avatarColumn, descriptionColumn,
List<CodegenColumnDO> columns = Arrays.asList(idColumn, nameColumn, avatarColumn, videoColumn, descriptionColumn,
sex1Column, sex2Column, sex3Column, birthdayColumn, memoColumn, createTimeColumn);
// 调用
@ -168,14 +163,92 @@ public class CodegenEngineTest extends BaseMockitoUnitTest {
result, "yudao-ui-admin-vue3/src/views/system/user/UserForm.vue");
assertPathContentEquals("vue3_crud/vue/api",
result, "yudao-ui-admin-vue3/src/api/system/user/index.ts");
// result.forEach(new BiConsumer<String, String>() {
// @Override
// public void accept(String s, String s2) {
// System.out.println(s);
// System.out.println(s2);
// System.out.println("=================");
}
@Test
public void testExecute_vue3_masterSub() {
// 准备请求参数
// 主表
CodegenTableDO table = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene())
.setTableName("system_user").setTableComment("用户表")
.setModuleName("system").setBusinessName("user").setClassName("SystemUser")
.setClassComment("用户").setAuthor("芋道源码")
.setTemplateType(CodegenTemplateTypeEnum.MASTER_SUB.getType()).setSubColumnId(100L)
.setFrontType(CodegenFrontTypeEnum.VUE3.getType())
.setParentMenuId(10L);
CodegenColumnDO idColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
.setColumnComment("编号").setNullable(false).setPrimaryKey(true).setAutoIncrement(true)
.setOrdinalPosition(1).setJavaType("Long").setJavaField("id").setExample("1024")
.setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
.setListOperationResult(true);
List<CodegenColumnDO> columns = Collections.singletonList(idColumn);
// 子表
CodegenTableDO subTable = new CodegenTableDO().setScene(CodegenSceneEnum.ADMIN.getScene())
.setTableName("system_user_contact").setTableComment("用户联系人表")
.setModuleName("system").setBusinessName("user").setClassName("SystemUserContact")
.setClassComment("用户联系人").setAuthor("芋道源码")
.setTemplateType(CodegenTemplateTypeEnum.CRUD.getType())
.setFrontType(CodegenFrontTypeEnum.VUE3.getType());
CodegenColumnDO subIdColumn = new CodegenColumnDO().setColumnName("id").setDataType(JdbcType.BIGINT.name())
.setColumnComment("编号").setNullable(false).setPrimaryKey(true).setAutoIncrement(true)
.setOrdinalPosition(1).setJavaType("Long").setJavaField("id").setExample("1024")
.setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
.setListOperationResult(true);
CodegenColumnDO userIdColumn = new CodegenColumnDO().setColumnName("user_id").setDataType(JdbcType.BIGINT.name())
.setColumnComment("用户编号").setNullable(false).setPrimaryKey(false)
.setOrdinalPosition(2).setJavaType("Long").setJavaField("userId").setExample("2048")
.setCreateOperation(false).setUpdateOperation(true).setListOperation(false)
.setListOperationResult(true)
.setId(100L);
List<CodegenColumnDO> subColumns = Arrays.asList(subIdColumn, userIdColumn);
// 调用
Map<String, String> result = codegenEngine.execute(table, columns, subTable, subColumns);
// 断言
assertEquals(23, result.size());
// // 断言 vo
// for (String vo : new String[]{"SystemUserBaseVO", "SystemUserCreateReqVO", "SystemUserUpdateReqVO", "SystemUserRespVO",
// "SystemUserPageReqVO", "SystemUserExportReqVO", "SystemUserExcelVO"}) {
// assertPathContentEquals("vue3_crud/java/" + vo,
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/controller/admin/user/vo/" + vo + ".java");
// }
// });
// // 断言 controller
// assertPathContentEquals("vue3_crud/java/SystemUserController",
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/controller/admin/user/SystemUserController.java");
// // 断言 service
// assertPathContentEquals("vue3_crud/java/SystemUserService",
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserService.java");
// assertPathContentEquals("vue3_crud/java/SystemUserServiceImpl",
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserServiceImpl.java");
// // 断言 convert
// assertPathContentEquals("vue3_crud/java/SystemUserConvert",
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/convert/user/SystemUserConvert.java");
// // 断言 enums
// assertPathContentEquals("vue3_crud/java/ErrorCodeConstants",
// result, "yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/module/system/enums/ErrorCodeConstants_手动操作.java");
// // 断言 dal
// assertPathContentEquals("vue3_crud/java/SystemUserDO",
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/dal/dataobject/user/SystemUserDO.java");
// assertPathContentEquals("vue3_crud/java/SystemUserMapper",
// result, "yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/module/system/dal/mysql/user/SystemUserMapper.java");
// assertPathContentEquals("vue3_crud/java/SystemUserMapper_xml",
// result, "yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/user/SystemUserMapper.xml");
// // 断言 test
// assertPathContentEquals("vue3_crud/java/SystemUserServiceImplTest",
// result, "yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/module/system/service/user/SystemUserServiceImplTest.java");
// // 断言 sql 语句
// assertPathContentEquals("vue3_crud/sql/h2",
// result, "sql/h2.sql");
// assertPathContentEquals("vue3_crud/sql/sql",
// result, "sql/sql.sql");
// // 断言 vue 语句
// assertPathContentEquals("vue3_crud/vue/index",
// result, "yudao-ui-admin-vue3/src/views/system/user/index.vue");
// assertPathContentEquals("vue3_crud/vue/form",
// result, "yudao-ui-admin-vue3/src/views/system/user/UserForm.vue");
// assertPathContentEquals("vue3_crud/vue/api",
// result, "yudao-ui-admin-vue3/src/api/system/user/index.ts");
}
private void assertPathContentEquals(String path, Map<String, String> result, String key) {

View File

@ -24,6 +24,9 @@ public class SystemUserBaseVO {
@Schema(description = "头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
@Schema(description = "视频", example = "https://www.iocoder.cn/1.mp4")
private String video;
@Schema(description = "个人简介", example = "我是介绍")
private String description;

View File

@ -35,6 +35,10 @@ public class SystemUserDO extends BaseDO {
* 头像
*/
private String avatar;
/**
* 视频
*/
private String video;
/**
* 个人简介
*/

View File

@ -28,6 +28,9 @@ public class SystemUserExcelVO {
@ExcelProperty("头像")
private String avatar;
@ExcelProperty("视频")
private String video;
@ExcelProperty("个人简介")
private String description;

View File

@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS "system_user" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"avatar" varchar,
"video" varchar,
"description" varchar,
"sex1" varchar,
"sex2" int,

View File

@ -4,6 +4,7 @@ export interface UserVO {
id: number
name: string
avatar: string
video: string
description: string
sex1: string
sex2: number

View File

@ -86,6 +86,7 @@ const formData = ref({
id: undefined,
name: undefined,
avatar: undefined,
video: undefined,
description: undefined,
sex1: undefined,
sex2: [],
@ -149,6 +150,7 @@ const resetForm = () => {
id: undefined,
name: undefined,
avatar: undefined,
video: undefined,
description: undefined,
sex1: undefined,
sex2: [],

View File

@ -95,6 +95,11 @@
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="视频" align="center" prop="video">
<template #default="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.video" />
</template>
</el-table-column>
<el-table-column label="个人简介" align="center" prop="description">
<template #default="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.description" />