提交 | 用户 | 时间
e7c126 1 package com.iailab.module.infra.service.codegen.inner;
H 2
3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.map.MapUtil;
5 import cn.hutool.core.util.ObjectUtil;
6 import cn.hutool.core.util.StrUtil;
7 import cn.hutool.extra.template.TemplateConfig;
8 import cn.hutool.extra.template.TemplateEngine;
9 import cn.hutool.extra.template.engine.velocity.VelocityEngine;
10 import cn.hutool.system.SystemUtil;
11 import com.iailab.framework.apilog.core.annotation.ApiAccessLog;
12 import com.iailab.framework.apilog.core.enums.OperateTypeEnum;
13 import com.iailab.framework.common.exception.util.ServiceExceptionUtil;
14 import com.iailab.framework.common.pojo.CommonResult;
15 import com.iailab.framework.common.pojo.PageParam;
16 import com.iailab.framework.common.pojo.PageResult;
17 import com.iailab.framework.common.util.collection.CollectionUtils;
18 import com.iailab.framework.common.util.date.DateUtils;
19 import com.iailab.framework.common.util.date.LocalDateTimeUtils;
20 import com.iailab.framework.common.util.object.BeanUtils;
21 import com.iailab.framework.common.util.object.ObjectUtils;
22 import com.iailab.framework.common.util.string.StrUtils;
23 import com.iailab.framework.excel.core.annotations.DictFormat;
24 import com.iailab.framework.excel.core.convert.DictConvert;
25 import com.iailab.framework.excel.core.util.ExcelUtils;
26 import com.iailab.framework.mybatis.core.dataobject.BaseDO;
27 import com.iailab.framework.mybatis.core.mapper.BaseMapperX;
28 import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX;
29 import com.iailab.module.infra.dal.dataobject.codegen.CodegenColumnDO;
30 import com.iailab.module.infra.dal.dataobject.codegen.CodegenTableDO;
31 import com.iailab.module.infra.enums.codegen.CodegenFrontTypeEnum;
32 import com.iailab.module.infra.enums.codegen.CodegenSceneEnum;
33 import com.iailab.module.infra.enums.codegen.CodegenTemplateTypeEnum;
34 import com.iailab.module.infra.framework.codegen.config.CodegenProperties;
35 import com.google.common.annotations.VisibleForTesting;
36 import com.google.common.collect.ImmutableTable;
37 import com.google.common.collect.Maps;
38 import com.google.common.collect.Table;
39 import lombok.Setter;
40 import org.springframework.stereotype.Component;
41
42 import javax.annotation.PostConstruct;
43 import javax.annotation.Resource;
44 import java.util.*;
45
46 import static cn.hutool.core.map.MapUtil.getStr;
47 import static cn.hutool.core.text.CharSequenceUtil.*;
48
49 /**
50  * 代码生成的引擎,用于具体生成代码
51  * 目前基于 {@link org.apache.velocity.app.Velocity} 模板引擎实现
52  *
53  * 考虑到 Java 模板引擎的框架非常多,Freemarker、Velocity、Thymeleaf 等等,所以我们采用 hutool 封装的 {@link cn.hutool.extra.template.Template} 抽象
54  *
55  * @author iailab
56  */
57 @Component
58 public class CodegenEngine {
59
60     /**
61      * 后端的模板配置
62      *
63      * key:模板在 resources 的地址
64      * value:生成的路径
65      */
66     private static final Map<String, String> SERVER_TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
67             // Java module-biz Main
68             .put(javaTemplatePath("controller/vo/pageReqVO"), javaModuleImplVOFilePath("PageReqVO"))
69             .put(javaTemplatePath("controller/vo/listReqVO"), javaModuleImplVOFilePath("ListReqVO"))
70             .put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO"))
71             .put(javaTemplatePath("controller/vo/saveReqVO"), javaModuleImplVOFilePath("SaveReqVO"))
72             .put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
73             .put(javaTemplatePath("dal/do"),
74                     javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
75             .put(javaTemplatePath("dal/do_sub"), // 特殊:主子表专属逻辑
76                     javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${subTable.className}DO"))
77             .put(javaTemplatePath("dal/mapper"),
78                     javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${table.className}Mapper"))
79             .put(javaTemplatePath("dal/mapper_sub"), // 特殊:主子表专属逻辑
80                     javaModuleImplMainFilePath("dal/mysql/${table.businessName}/${subTable.className}Mapper"))
81             .put(javaTemplatePath("dal/mapper.xml"), mapperXmlFilePath())
82             .put(javaTemplatePath("service/serviceImpl"),
83                     javaModuleImplMainFilePath("service/${table.businessName}/${table.className}ServiceImpl"))
84             .put(javaTemplatePath("service/service"),
85                     javaModuleImplMainFilePath("service/${table.businessName}/${table.className}Service"))
86             // Java module-biz Test
87             .put(javaTemplatePath("test/serviceTest"),
88                     javaModuleImplTestFilePath("service/${table.businessName}/${table.className}ServiceImplTest"))
89             // Java module-api Main
90             .put(javaTemplatePath("enums/errorcode"), javaModuleApiMainFilePath("enums/ErrorCodeConstants_手动操作"))
91             // SQL
92             .put("codegen/sql/sql.vm", "sql/sql.sql")
93             .put("codegen/sql/h2.vm", "sql/h2.sql")
94             .build();
95
96     /**
97      * 后端的配置模版
98      *
99      * key1:UI 模版的类型 {@link CodegenFrontTypeEnum#getType()}
100      * key2:模板在 resources 的地址
101      * value:生成的路径
102      */
103     private static final Table<Integer, String, String> FRONT_TEMPLATES = ImmutableTable.<Integer, String, String>builder()
104             // Vue2 标准模版
105             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/index.vue"),
106                     vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
107             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"),
108                     vueFilePath("api/${table.moduleName}/${table.businessName}/index.js"))
109             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/form.vue"),
110                     vueFilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
111             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
112                     vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
113             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
114                     vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
115             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
116                     vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
117             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
118                     vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
119             .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
120                     vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
121             // Vue3 标准模版
122             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
123                     vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
124             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/form.vue"),
125                     vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
126             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
127                     vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
128             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
129                     vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
130             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
131                     vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
132             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
133                     vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
134             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/components/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
135                     vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
136             .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"),
137                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
138             // Vue3 Schema 模版
139             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/data.ts"),
140                     vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
141             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/index.vue"),
142                     vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
143             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
144                     vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
145             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("api/api.ts"),
146                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
147             // Vue3 vben 模版
148             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"),
149                     vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
150             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/index.vue"),
151                     vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
152             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/form.vue"),
153                     vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Modal.vue"))
154             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("api/api.ts"),
155                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
156             .build();
157
158     @Resource
159     private CodegenProperties codegenProperties;
160
161     /**
162      * 是否使用 jakarta 包,用于解决 Spring Boot 2.X 和 3.X 的兼容性问题
163      *
164      * true  - 使用 jakarta.validation.constraints.*
165      * false - 使用 javax.validation.constraints.*
166      */
167     @Setter // 允许设置的原因,是因为单测需要手动改变
168     private Boolean jakartaEnable;
169
170     /**
171      * 模板引擎,由 hutool 实现
172      */
173     private final TemplateEngine templateEngine;
174     /**
175      * 全局通用变量映射
176      */
177     private final Map<String, Object> globalBindingMap = new HashMap<>();
178
179     public CodegenEngine() {
180         // 初始化 TemplateEngine 属性
181         TemplateConfig config = new TemplateConfig();
182         config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH);
183         this.templateEngine = new VelocityEngine(config);
184         // 设置 javaxEnable,按照是否使用 JDK17 来判断
185         this.jakartaEnable = SystemUtil.getJavaInfo().isJavaVersionAtLeast(1700); // 17.00 * 100
186     }
187
188     @PostConstruct
189     @VisibleForTesting
190     void initGlobalBindingMap() {
191         // 全局配置
192         globalBindingMap.put("basePackage", codegenProperties.getBasePackage());
193         globalBindingMap.put("baseFrameworkPackage", codegenProperties.getBasePackage()
194                 + '.' + "framework"); // 用于后续获取测试类的 package 地址
195         globalBindingMap.put("jakartaPackage", jakartaEnable ? "jakarta" : "javax");
196         // 全局 Java Bean
197         globalBindingMap.put("CommonResultClassName", CommonResult.class.getName());
198         globalBindingMap.put("PageResultClassName", PageResult.class.getName());
199         // VO 类,独有字段
200         globalBindingMap.put("PageParamClassName", PageParam.class.getName());
201         globalBindingMap.put("DictFormatClassName", DictFormat.class.getName());
202         // DO 类,独有字段
203         globalBindingMap.put("BaseDOClassName", BaseDO.class.getName());
204         globalBindingMap.put("baseDOFields", CodegenBuilder.BASE_DO_FIELDS);
205         globalBindingMap.put("QueryWrapperClassName", LambdaQueryWrapperX.class.getName());
206         globalBindingMap.put("BaseMapperClassName", BaseMapperX.class.getName());
207         // Util 工具类
208         globalBindingMap.put("ServiceExceptionUtilClassName", ServiceExceptionUtil.class.getName());
209         globalBindingMap.put("DateUtilsClassName", DateUtils.class.getName());
210         globalBindingMap.put("ExcelUtilsClassName", ExcelUtils.class.getName());
211         globalBindingMap.put("LocalDateTimeUtilsClassName", LocalDateTimeUtils.class.getName());
212         globalBindingMap.put("ObjectUtilsClassName", ObjectUtils.class.getName());
213         globalBindingMap.put("DictConvertClassName", DictConvert.class.getName());
214         globalBindingMap.put("ApiAccessLogClassName", ApiAccessLog.class.getName());
215         globalBindingMap.put("OperateTypeEnumClassName", OperateTypeEnum.class.getName());
216         globalBindingMap.put("BeanUtils", BeanUtils.class.getName());
217     }
218
219     /**
220      * 生成代码
221      *
222      * @param table 表定义
223      * @param columns table 的字段定义数组
224      * @param subTables 子表数组,当且仅当主子表时使用
225      * @param subColumnsList subTables 的字段定义数组
226      * @return 生成的代码,key 是路径,value 是对应代码
227      */
228     public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns,
229                                        List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
230         // 1.1 初始化 bindMap 上下文
231         Map<String, Object> bindingMap = initBindingMap(table, columns, subTables, subColumnsList);
232         // 1.2 获得模版
233         Map<String, String> templates = getTemplates(table.getFrontType());
234
235         // 2. 执行生成
236         Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
237         templates.forEach((vmPath, filePath) -> {
238             // 2.1 特殊:主子表专属逻辑
239             if (isSubTemplate(vmPath)) {
240                 generateSubCode(table, subTables, result, vmPath, filePath, bindingMap);
241                 return;
242                 // 2.2 特殊:树表专属逻辑
243             } else if (isPageReqVOTemplate(vmPath)) {
244                 // 减少多余的类生成,例如说 PageVO.java 类
245                 if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
246                     return;
247                 }
248             } else if (isListReqVOTemplate(vmPath)) {
249                 // 减少多余的类生成,例如说 ListVO.java 类
250                 if (!CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
251                     return;
252                 }
253             }
254             // 2.3 默认生成
255             generateCode(result, vmPath, filePath, bindingMap);
256         });
257         return result;
258     }
259
260     private void generateCode(Map<String, String> result, String vmPath,
261                               String filePath, Map<String, Object> bindingMap) {
262         filePath = formatFilePath(filePath, bindingMap);
263         String content = templateEngine.getTemplate(vmPath).render(bindingMap);
264         // 格式化代码
265         content = prettyCode(content);
266         result.put(filePath, content);
267     }
268
269     private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables,
270                                  Map<String, String> result, String vmPath,
271                                  String filePath, Map<String, Object> bindingMap) {
272         // 没有子表,所以不生成
273         if (CollUtil.isEmpty(subTables)) {
274             return;
275         }
276         // 主子表的模式匹配。目的:过滤掉个性化的模版
277         if (vmPath.contains("_normal")
278                 && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType())) {
279             return;
280         }
281         if (vmPath.contains("_erp")
282                 && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType())) {
283             return;
284         }
285         if (vmPath.contains("_inner")
286                 && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType())) {
287             return;
288         }
289
290         // 逐个生成
291         for (int i = 0; i < subTables.size(); i++) {
292             bindingMap.put("subIndex", i);
293             generateCode(result, vmPath, filePath, bindingMap);
294         }
295         bindingMap.remove("subIndex");
296     }
297
298     /**
299      * 格式化生成后的代码
300      *
301      * 因为尽量让 vm 模版简单,所以统一的处理都在这个方法。
302      * 如果不处理,Vue 的 Pretty 格式校验可能会报错
303      *
304      * @param content 格式化前的代码
305      * @return 格式化后的代码
306      */
307     private String prettyCode(String content) {
308         // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错
309         content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
310         // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到
311         if (StrUtil.count(content, "dateFormatter") == 1) {
312             content = StrUtils.removeLineContains(content, "dateFormatter");
313         }
314         // Vue2 界面:修正 $refs
315         if (StrUtil.count(content, "this.refs") >= 1) {
316             content = content.replace("this.refs", "this.$refs");
317         }
318         // Vue 界面:去除多的 dict 相关,只有一个的情况下,说明没使用到
319         if (StrUtil.count(content, "getIntDictOptions") == 1) {
320             content = content.replace("getIntDictOptions, ", "");
321         }
322         if (StrUtil.count(content, "getStrDictOptions") == 1) {
323             content = content.replace("getStrDictOptions, ", "");
324         }
325         if (StrUtil.count(content, "getBoolDictOptions") == 1) {
326             content = content.replace("getBoolDictOptions, ", "");
327         }
328         if (StrUtil.count(content, "DICT_TYPE.") == 0) {
329             content = StrUtils.removeLineContains(content, "DICT_TYPE");
330         }
331         return content;
332     }
333
334     private Map<String, Object> initBindingMap(CodegenTableDO table, List<CodegenColumnDO> columns,
335                                                List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {
336         // 创建 bindingMap
337         Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
338         bindingMap.put("table", table);
339         bindingMap.put("columns", columns);
340         bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, CodegenColumnDO::getPrimaryKey)); // 主键字段
341         bindingMap.put("sceneEnum", CodegenSceneEnum.valueOf(table.getScene()));
342
343         // className 相关
344         // 去掉指定前缀,将 TestDictType 转换成 DictType. 因为在 create 等方法后,不需要带上 Test 前缀
345         String simpleClassName = removePrefix(table.getClassName(), upperFirst(table.getModuleName()));
346         bindingMap.put("simpleClassName", simpleClassName);
347         bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type
348         bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType,用于变量
349         // 将 DictType 转换成 dict-type
350         String simpleClassNameStrikeCase = toSymbolCase(simpleClassName, '-');
351         bindingMap.put("simpleClassName_strikeCase", simpleClassNameStrikeCase);
352         // permission 前缀
353         bindingMap.put("permissionPrefix", table.getModuleName() + ":" + simpleClassNameStrikeCase);
354
355         // 特殊:树表专属逻辑
356         if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
357             CodegenColumnDO treeParentColumn = CollUtil.findOne(columns,
358                     column -> Objects.equals(column.getId(), table.getTreeParentColumnId()));
359             bindingMap.put("treeParentColumn", treeParentColumn);
360             bindingMap.put("treeParentColumn_javaField_underlineCase", toUnderlineCase(treeParentColumn.getJavaField()));
361             CodegenColumnDO treeNameColumn = CollUtil.findOne(columns,
362                     column -> Objects.equals(column.getId(), table.getTreeNameColumnId()));
363             bindingMap.put("treeNameColumn", treeNameColumn);
364             bindingMap.put("treeNameColumn_javaField_underlineCase", toUnderlineCase(treeNameColumn.getJavaField()));
365         }
366
367         // 特殊:主子表专属逻辑
368         if (CollUtil.isNotEmpty(subTables)) {
369             // 创建 bindingMap
370             bindingMap.put("subTables", subTables);
371             bindingMap.put("subColumnsList", subColumnsList);
372             List<CodegenColumnDO> subPrimaryColumns = new ArrayList<>();
373             List<CodegenColumnDO> subJoinColumns = new ArrayList<>();
374             List<String> subJoinColumnStrikeCases = new ArrayList<>();
375             List<String> subSimpleClassNames = new ArrayList<>();
376             List<String> subClassNameVars = new ArrayList<>();
377             List<String> simpleClassNameUnderlineCases = new ArrayList<>();
378             List<String> subSimpleClassNameStrikeCases = new ArrayList<>();
379             for (int i = 0; i < subTables.size(); i++) {
380                 CodegenTableDO subTable = subTables.get(i);
381                 List<CodegenColumnDO> subColumns = subColumnsList.get(i);
382                 subPrimaryColumns.add(CollectionUtils.findFirst(subColumns, CodegenColumnDO::getPrimaryKey)); //
383                 CodegenColumnDO subColumn = CollectionUtils.findFirst(subColumns, // 关联的字段
384                         column -> Objects.equals(column.getId(), subTable.getSubJoinColumnId()));
385                 subJoinColumns.add(subColumn);
386                 subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type
387                 // className 相关
388                 String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
389                 subSimpleClassNames.add(subSimpleClassName);
390                 simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type
391                 subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
392                 subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type
393             }
394             bindingMap.put("subPrimaryColumns", subPrimaryColumns);
395             bindingMap.put("subJoinColumns", subJoinColumns);
396             bindingMap.put("subJoinColumn_strikeCases", subJoinColumnStrikeCases);
397             bindingMap.put("subSimpleClassNames", subSimpleClassNames);
398             bindingMap.put("simpleClassNameUnderlineCases", simpleClassNameUnderlineCases);
399             bindingMap.put("subClassNameVars", subClassNameVars);
400             bindingMap.put("subSimpleClassName_strikeCases", subSimpleClassNameStrikeCases);
401         }
402         return bindingMap;
403     }
404
405     private Map<String, String> getTemplates(Integer frontType) {
406         Map<String, String> templates = new LinkedHashMap<>();
407         templates.putAll(SERVER_TEMPLATES);
408         templates.putAll(FRONT_TEMPLATES.row(frontType));
409         return templates;
410     }
411
412     @SuppressWarnings("unchecked")
413     private String formatFilePath(String filePath, Map<String, Object> bindingMap) {
414         filePath = StrUtil.replace(filePath, "${basePackage}",
415                 getStr(bindingMap, "basePackage").replaceAll("\\.", "/"));
416         filePath = StrUtil.replace(filePath, "${classNameVar}",
417                 getStr(bindingMap, "classNameVar"));
418         filePath = StrUtil.replace(filePath, "${simpleClassName}",
419                 getStr(bindingMap, "simpleClassName"));
420         // sceneEnum 包含的字段
421         CodegenSceneEnum sceneEnum = (CodegenSceneEnum) bindingMap.get("sceneEnum");
422         filePath = StrUtil.replace(filePath, "${sceneEnum.prefixClass}", sceneEnum.getPrefixClass());
423         filePath = StrUtil.replace(filePath, "${sceneEnum.basePackage}", sceneEnum.getBasePackage());
424         // table 包含的字段
425         CodegenTableDO table = (CodegenTableDO) bindingMap.get("table");
426         filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName());
427         filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
428         filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
429         // 特殊:主子表专属逻辑
430         Integer subIndex = (Integer) bindingMap.get("subIndex");
431         if (subIndex != null) {
432             CodegenTableDO subTable = ((List<CodegenTableDO>) bindingMap.get("subTables")).get(subIndex);
433             filePath = StrUtil.replace(filePath, "${subTable.moduleName}", subTable.getModuleName());
434             filePath = StrUtil.replace(filePath, "${subTable.businessName}", subTable.getBusinessName());
435             filePath = StrUtil.replace(filePath, "${subTable.className}", subTable.getClassName());
436             filePath = StrUtil.replace(filePath, "${subSimpleClassName}",
437                     ((List<String>) bindingMap.get("subSimpleClassNames")).get(subIndex));
438         }
439         return filePath;
440     }
441
442     private static String javaTemplatePath(String path) {
443         return "codegen/java/" + path + ".vm";
444     }
445
446     private static String javaModuleImplVOFilePath(String path) {
447         return javaModuleFilePath("controller/${sceneEnum.basePackage}/${table.businessName}/" +
448                 "vo/${sceneEnum.prefixClass}${table.className}" + path, "biz", "main");
449     }
450
451     private static String javaModuleImplControllerFilePath() {
452         return javaModuleFilePath("controller/${sceneEnum.basePackage}/${table.businessName}/" +
453                 "${sceneEnum.prefixClass}${table.className}Controller", "biz", "main");
454     }
455
456     private static String javaModuleImplMainFilePath(String path) {
457         return javaModuleFilePath(path, "biz", "main");
458     }
459
460     private static String javaModuleApiMainFilePath(String path) {
461         return javaModuleFilePath(path, "api", "main");
462     }
463
464     private static String javaModuleImplTestFilePath(String path) {
465         return javaModuleFilePath(path, "biz", "test");
466     }
467
468     private static String javaModuleFilePath(String path, String module, String src) {
469         return "iailab-module-${table.moduleName}/" + // 顶级模块
470                 "iailab-module-${table.moduleName}-" + module + "/" + // 子模块
471                 "src/" + src + "/java/${basePackage}/module/${table.moduleName}/" + path + ".java";
472     }
473
474     private static String mapperXmlFilePath() {
475         return "iailab-module-${table.moduleName}/" + // 顶级模块
476                 "iailab-module-${table.moduleName}-biz/" + // 子模块
477                 "src/main/resources/mapper/${table.businessName}/${table.className}Mapper.xml";
478     }
479
480     private static String vueTemplatePath(String path) {
481         return "codegen/vue/" + path + ".vm";
482     }
483
484     private static String vueFilePath(String path) {
485         return "iailab-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录
486                 "src/" + path;
487     }
488
489     private static String vue3TemplatePath(String path) {
490         return "codegen/vue3/" + path + ".vm";
491     }
492
493     private static String vue3FilePath(String path) {
494         return "iailab-ui-${sceneEnum.basePackage}-vue3/" + // 顶级目录
495                 "src/" + path;
496     }
497
498     private static String vue3SchemaTemplatePath(String path) {
499         return "codegen/vue3_schema/" + path + ".vm";
500     }
501
502     private static String vue3VbenTemplatePath(String path) {
503         return "codegen/vue3_vben/" + path + ".vm";
504     }
505
506     private static boolean isSubTemplate(String path) {
507         return path.contains("_sub");
508     }
509
510     private static boolean isPageReqVOTemplate(String path) {
511         return path.contains("pageReqVO");
512     }
513
514     private static boolean isListReqVOTemplate(String path) {
515         return path.contains("listReqVO");
516     }
517
518 }