package com.iailab.module.infra.service.codegen.inner; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.mybatis.core.dataobject.BaseDO; import com.iailab.module.infra.convert.codegen.CodegenConvert; import com.iailab.module.infra.dal.dataobject.codegen.CodegenColumnDO; import com.iailab.module.infra.dal.dataobject.codegen.CodegenTableDO; import com.iailab.module.infra.enums.codegen.CodegenColumnHtmlTypeEnum; import com.iailab.module.infra.enums.codegen.CodegenColumnListConditionEnum; import com.iailab.module.infra.enums.codegen.CodegenTemplateTypeEnum; import com.baomidou.mybatisplus.generator.config.po.TableField; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.google.common.collect.Sets; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.*; import static cn.hutool.core.text.CharSequenceUtil.*; import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.hutool.core.util.RandomUtil.randomInt; /** * 代码生成器的 Builder,负责: * 1. 将数据库的表 {@link TableInfo} 定义,构建成 {@link CodegenTableDO} * 2. 将数据库的列 {@link TableField} 构定义,建成 {@link CodegenColumnDO} */ @Component public class CodegenBuilder { /** * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射 * 注意,字段的匹配以后缀的方式 */ private static final Map COLUMN_LIST_OPERATION_CONDITION_MAPPINGS = MapUtil.builder() .put("name", CodegenColumnListConditionEnum.LIKE) .put("time", CodegenColumnListConditionEnum.BETWEEN) .put("date", CodegenColumnListConditionEnum.BETWEEN) .build(); /** * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射 * 注意,字段的匹配以后缀的方式 */ private static final Map COLUMN_HTML_TYPE_MAPPINGS = MapUtil.builder() .put("status", CodegenColumnHtmlTypeEnum.RADIO) .put("sex", CodegenColumnHtmlTypeEnum.RADIO) .put("type", CodegenColumnHtmlTypeEnum.SELECT) .put("image", CodegenColumnHtmlTypeEnum.IMAGE_UPLOAD) .put("file", CodegenColumnHtmlTypeEnum.FILE_UPLOAD) .put("content", CodegenColumnHtmlTypeEnum.EDITOR) .put("description", CodegenColumnHtmlTypeEnum.EDITOR) .put("demo", CodegenColumnHtmlTypeEnum.EDITOR) .put("time", CodegenColumnHtmlTypeEnum.DATETIME) .put("date", CodegenColumnHtmlTypeEnum.DATETIME) .build(); /** * 多租户编号的字段名 */ public static final String TENANT_ID_FIELD = "tenantId"; /** * {@link com.iailab.framework.mybatis.core.dataobject.BaseDO} 的字段 */ public static final Set BASE_DO_FIELDS = new HashSet<>(); /** * 新增操作,不需要传递的字段 */ private static final Set CREATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id"); /** * 修改操作,不需要传递的字段 */ private static final Set UPDATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet(); /** * 列表操作的条件,不需要传递的字段 */ private static final Set LIST_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet("id"); /** * 列表操作的结果,不需要返回的字段 */ private static final Set LIST_OPERATION_RESULT_EXCLUDE_COLUMN = Sets.newHashSet(); static { Arrays.stream(ReflectUtil.getFields(BaseDO.class)).forEach(field -> BASE_DO_FIELDS.add(field.getName())); BASE_DO_FIELDS.add(TENANT_ID_FIELD); // 处理 OPERATION 相关的字段 CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); LIST_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); LIST_OPERATION_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是可能需要传递的 LIST_OPERATION_RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS); LIST_OPERATION_RESULT_EXCLUDE_COLUMN.remove("createTime"); // 创建时间,还是需要返回的 } public CodegenTableDO buildTable(TableInfo tableInfo) { CodegenTableDO table = CodegenConvert.INSTANCE.convert(tableInfo); initTableDefault(table); return table; } /** * 初始化 Table 表的默认字段 * * @param table 表定义 */ private void initTableDefault(CodegenTableDO table) { // 以 system_dept 举例子。moduleName 为 system、businessName 为 dept、className 为 Dept // 如果希望以 System 前缀,则可以手动在【代码生成 - 修改生成配置 - 基本信息】,将实体类名称改为 SystemDept 即可 String tableName = table.getTableName().toLowerCase(); // 第一步,_ 前缀的前面,作为 module 名字;第二步,moduleName 必须小写; table.setModuleName(subBefore(tableName, '_', false).toLowerCase()); // 第一步,第一个 _ 前缀的后面,作为 module 名字; 第二步,可能存在多个 _ 的情况,转换成驼峰; 第三步,businessName 必须小写; table.setBusinessName(toCamelCase(subAfter(tableName, '_', false)).toLowerCase()); // 驼峰 + 首字母大写;第一步,第一个 _ 前缀的后面,作为 class 名字;第二步,驼峰命名 table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false)))); // 去除结尾的表,作为类描述 table.setClassComment(StrUtil.removeSuffixIgnoreCase(table.getTableComment(), "表")); table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType()); } public List buildColumns(Long tableId, List tableFields) { List columns = CodegenConvert.INSTANCE.convertList(tableFields); int index = 1; for (CodegenColumnDO column : columns) { column.setTableId(tableId); column.setOrdinalPosition(index++); // 特殊处理:Byte => Integer if (Byte.class.getSimpleName().equals(column.getJavaType())) { column.setJavaType(Integer.class.getSimpleName()); } // 初始化 Column 列的默认字段 processColumnOperation(column); // 处理 CRUD 相关的字段的默认值 processColumnUI(column); // 处理 UI 相关的字段的默认值 processColumnExample(column); // 处理字段的 swagger example 示例 } return columns; } private void processColumnOperation(CodegenColumnDO column) { // 处理 createOperation 字段 column.setCreateOperation(!CREATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) && !column.getPrimaryKey()); // 对于主键,创建时无需传递 // 处理 updateOperation 字段 column.setUpdateOperation(!UPDATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) || column.getPrimaryKey()); // 对于主键,更新时需要传递 // 处理 listOperation 字段 column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField()) && !column.getPrimaryKey()); // 对于主键,列表过滤不需要传递 // 处理 listOperationCondition 字段 COLUMN_LIST_OPERATION_CONDITION_MAPPINGS.entrySet().stream() .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition())); if (column.getListOperationCondition() == null) { column.setListOperationCondition(CodegenColumnListConditionEnum.EQ.getCondition()); } // 处理 listOperationResult 字段 column.setListOperationResult(!LIST_OPERATION_RESULT_EXCLUDE_COLUMN.contains(column.getJavaField())); } private void processColumnUI(CodegenColumnDO column) { // 基于后缀进行匹配 COLUMN_HTML_TYPE_MAPPINGS.entrySet().stream() .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey())) .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType())); // 如果是 Boolean 类型时,设置为 radio 类型. if (Boolean.class.getSimpleName().equals(column.getJavaType())) { column.setHtmlType(CodegenColumnHtmlTypeEnum.RADIO.getType()); } // 如果是 LocalDateTime 类型,则设置为 datetime 类型 if (LocalDateTime.class.getSimpleName().equals(column.getJavaType())) { column.setHtmlType(CodegenColumnHtmlTypeEnum.DATETIME.getType()); } // 兜底,设置默认为 input 类型 if (column.getHtmlType() == null) { column.setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType()); } } /** * 处理字段的 swagger example 示例 * * @param column 字段 */ private void processColumnExample(CodegenColumnDO column) { // id、price、count 等可能是整数的后缀 if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "id", "price", "count")) { column.setExample(String.valueOf(randomInt(1, Short.MAX_VALUE))); return; } // name if (StrUtil.endWithIgnoreCase(column.getJavaField(), "name")) { column.setExample(randomEle(new String[]{"张三", "李四", "王五", "赵六", "iailab"})); return; } // status if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "status", "type")) { column.setExample(randomEle(new String[]{"1", "2"})); return; } // url if (StrUtil.endWithIgnoreCase(column.getColumnName(), "url")) { column.setExample("https://www.baidu.com"); return; } // reason if (StrUtil.endWithIgnoreCase(column.getColumnName(), "reason")) { column.setExample(randomEle(new String[]{"不喜欢", "不对", "不好", "不香"})); return; } // description、memo、remark if (StrUtil.endWithAnyIgnoreCase(column.getColumnName(), "description", "memo", "remark")) { column.setExample(randomEle(new String[]{"你猜", "随便", "你说的对"})); return; } } }