From f91ecee6cccf8d240d5b56e0900a1fe7d882b272 Mon Sep 17 00:00:00 2001 From: cuijiawang Date: Thu, 25 Sep 2025 14:52:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agileboot-system/wol-auth/pom.xml | 5 + agileboot-system/wol-codegenerator/pom.xml | 6 + .../controller/GeneratorController.java | 41 +++- .../agileboot/codegen/dto/CodegenRequest.java | 22 +++ .../agileboot/codegen/entity/ClassInfo.java | 37 ++++ .../agileboot/codegen/entity/FieldInfo.java | 35 ++++ .../agileboot/codegen/entity/ParamInfo.java | 22 +++ .../codegen/service/GeneratorServiceImpl.java | 80 +++++++- .../codegen/service/IGeneratorService.java | 9 + .../codegen/util/FreemarkerUtil.java | 59 ++++++ .../com/agileboot/codegen/util/MapUtil.java | 71 +++++++ .../codegen/util/TableParseUtil.java | 179 ++++++++++++++++++ 12 files changed, 560 insertions(+), 6 deletions(-) create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/dto/CodegenRequest.java create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ClassInfo.java create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/FieldInfo.java create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ParamInfo.java create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/FreemarkerUtil.java create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/MapUtil.java create mode 100644 agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/TableParseUtil.java diff --git a/agileboot-system/wol-auth/pom.xml b/agileboot-system/wol-auth/pom.xml index d379eca..75cd831 100644 --- a/agileboot-system/wol-auth/pom.xml +++ b/agileboot-system/wol-auth/pom.xml @@ -24,6 +24,11 @@ com.agileboot agileboot-system-base + + com.agileboot + wol-codegenerator + 1.0.0 + diff --git a/agileboot-system/wol-codegenerator/pom.xml b/agileboot-system/wol-codegenerator/pom.xml index 146ee5b..4648c07 100644 --- a/agileboot-system/wol-codegenerator/pom.xml +++ b/agileboot-system/wol-codegenerator/pom.xml @@ -22,5 +22,11 @@ com.agileboot wol-common-web + + + + org.springframework.boot + spring-boot-starter-freemarker + diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/controller/GeneratorController.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/controller/GeneratorController.java index ea51bb3..ef28207 100644 --- a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/controller/GeneratorController.java +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/controller/GeneratorController.java @@ -1,15 +1,15 @@ package com.agileboot.codegen.controller; +import com.agileboot.codegen.dto.CodegenRequest; import com.agileboot.codegen.service.IGeneratorService; import com.agileboot.common.core.core.R; import com.alibaba.fastjson2.JSONArray; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.HashMap; +import java.util.Map; /** * @Author cuiJiaWang @@ -24,9 +24,42 @@ public class GeneratorController { private final IGeneratorService generatorService; @GetMapping("/template/all") - public R getAllTemplates() { + public R getAllTemplates() { String templates = generatorService.getTemplateConfig(); JSONArray jsonArray = JSONArray.parseArray(templates); return R.ok(jsonArray); } + + @PostMapping("/code/generate") + public R generateCode(@RequestBody CodegenRequest request) { + try { + log.info("开始生成代码,表SQL: {}", request.getTableSql()); + + // 合并请求参数 + Map options = new HashMap<>(); + if (request.getOptions() != null) { + options.putAll(request.getOptions()); + } + options.put("tableSql", request.getTableSql()); + + // 调用服务生成代码 + Map result = generatorService.getResultByParams(options); + + // 检查是否有错误 + if (result.containsKey("error")) { + return R.fail(result.get("error")); + } + + Map response = new HashMap<>(); + response.put("outputJson", result); + response.put("tableName", result.get("tableName")); + + log.info("代码生成完成,表名: {}", result.get("tableName")); + return R.ok(response); + + } catch (Exception e) { + log.error("生成代码失败", e); + return R.fail("生成代码失败: " + e.getMessage()); + } + } } diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/dto/CodegenRequest.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/dto/CodegenRequest.java new file mode 100644 index 0000000..6a38f2d --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/dto/CodegenRequest.java @@ -0,0 +1,22 @@ +package com.agileboot.codegen.dto; + +import lombok.Data; + +import java.util.Map; + +/** + * 代码生成请求DTO + */ +@Data +public class CodegenRequest { + + /** + * 表SQL语句 + */ + private String tableSql; + + /** + * 生成选项 + */ + private Map options; +} diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ClassInfo.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ClassInfo.java new file mode 100644 index 0000000..5466652 --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ClassInfo.java @@ -0,0 +1,37 @@ +package com.agileboot.codegen.entity; + +import lombok.Data; + +import java.util.List; + +/** + * 类信息实体 + */ +@Data +public class ClassInfo { + + /** + * 表名 + */ + private String tableName; + + /** + * 原始表名 + */ + private String originTableName; + + /** + * 类名 + */ + private String className; + + /** + * 类注释 + */ + private String classComment; + + /** + * 字段列表 + */ + private List fieldList; +} diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/FieldInfo.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/FieldInfo.java new file mode 100644 index 0000000..99edd9f --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/FieldInfo.java @@ -0,0 +1,35 @@ +package com.agileboot.codegen.entity; + +import lombok.Data; + +/** + * 字段信息实体 + */ +@Data +public class FieldInfo { + + /** + * 数据库列名 + */ + private String columnName; + + /** + * Java字段名 + */ + private String fieldName; + + /** + * Java字段类型 + */ + private String fieldClass; + + /** + * Swagger类型 + */ + private String swaggerClass; + + /** + * 字段注释 + */ + private String fieldComment; +} diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ParamInfo.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ParamInfo.java new file mode 100644 index 0000000..3db3208 --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/entity/ParamInfo.java @@ -0,0 +1,22 @@ +package com.agileboot.codegen.entity; + +import lombok.Data; + +import java.util.Map; + +/** + * 参数信息实体 + */ +@Data +public class ParamInfo { + + /** + * 表SQL语句 + */ + private String tableSql; + + /** + * 生成选项 + */ + private Map options; +} diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/GeneratorServiceImpl.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/GeneratorServiceImpl.java index 7863f5a..f02b4ae 100644 --- a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/GeneratorServiceImpl.java +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/GeneratorServiceImpl.java @@ -1,19 +1,31 @@ package com.agileboot.codegen.service; +import com.agileboot.codegen.entity.ClassInfo; +import com.agileboot.codegen.util.FreemarkerUtil; +import com.agileboot.codegen.util.MapUtil; +import com.agileboot.codegen.util.TableParseUtil; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; import java.util.stream.Collectors; /** * @Author cuiJiaWang * @Create 2025-09-24 14:31 */ +@Slf4j @Service public class GeneratorServiceImpl implements IGeneratorService { + @Override public String getTemplateConfig() { String templateConfig = ""; @@ -24,9 +36,73 @@ public class GeneratorServiceImpl implements IGeneratorService { inputStream.close(); } } catch (IOException e) { - e.printStackTrace(); + log.error("读取模板配置失败", e); } - return templateConfig; } + + @Override + public Map getResultByParams(Map options) { + Map result = new HashMap<>(); + + try { + // 1. 解析SQL获取表结构信息 + String tableSql = MapUtil.getString(options, "tableSql"); + String ignorePrefix = MapUtil.getString(options, "ignorePrefix", ""); + + if (StringUtils.isBlank(tableSql)) { + log.error("SQL语句为空"); + result.put("error", "SQL语句不能为空"); + return result; + } + + ClassInfo classInfo = TableParseUtil.parseTableSql(tableSql, ignorePrefix); + if (classInfo == null) { + log.error("解析SQL失败"); + result.put("error", "解析SQL失败"); + return result; + } + + // 2. 设置生成参数 + options.put("classInfo", classInfo); + options.put("tableName", classInfo.getTableName()); + options.put("authorName", MapUtil.getString(options, "authorName", "AgileBoot")); + + // 3. 根据模板配置生成代码 + String templateConfig = getTemplateConfig(); + JSONArray templateGroups = JSONArray.parseArray(templateConfig); + + for (Object groupObj : templateGroups) { + JSONObject group = (JSONObject) groupObj; + String groupName = group.getString("group"); + JSONArray templates = group.getJSONArray("templates"); + + for (Object templateObj : templates) { + JSONObject template = (JSONObject) templateObj; + String templateName = template.getString("name"); + + // 构建模板文件路径,按组别分目录 + String templatePath = groupName + "/" + templateName + ".ftl"; + + // 生成代码 + if (FreemarkerUtil.templateExists(templatePath)) { + String generatedCode = FreemarkerUtil.generateByTemplate(templatePath, options); + result.put(templateName, generatedCode); + } else { + log.warn("模板文件不存在: {}", templatePath); + result.put(templateName, "// 模板文件不存在: " + templatePath); + } + } + } + + // 添加表名信息 + result.put("tableName", classInfo.getTableName()); + + } catch (Exception e) { + log.error("生成代码失败", e); + result.put("error", "生成代码失败: " + e.getMessage()); + } + + return result; + } } diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/IGeneratorService.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/IGeneratorService.java index db51814..54e7bca 100644 --- a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/IGeneratorService.java +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/service/IGeneratorService.java @@ -1,5 +1,7 @@ package com.agileboot.codegen.service; +import java.util.Map; + /** * @Author cuiJiaWang * @Create 2025-09-24 14:31 @@ -7,4 +9,11 @@ package com.agileboot.codegen.service; public interface IGeneratorService { String getTemplateConfig(); + /** + * 根据参数生成代码 + * + * @param options 生成参数 + * @return 生成的代码Map + */ + Map getResultByParams(Map options); } diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/FreemarkerUtil.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/FreemarkerUtil.java new file mode 100644 index 0000000..d0b82cb --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/FreemarkerUtil.java @@ -0,0 +1,59 @@ +package com.agileboot.codegen.util; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; + +/** + * Freemarker工具类 + */ +@Slf4j +public class FreemarkerUtil { + + private static Configuration configuration; + + static { + configuration = new Configuration(Configuration.VERSION_2_3_31); + configuration.setClassForTemplateLoading(FreemarkerUtil.class, "/templates/code-generator"); + configuration.setDefaultEncoding("UTF-8"); + } + + /** + * 根据模板生成代码 + * + * @param templateName 模板名称 + * @param dataModel 数据模型 + * @return 生成的代码 + */ + public static String generateByTemplate(String templateName, Map dataModel) { + try { + Template template = configuration.getTemplate(templateName); + StringWriter writer = new StringWriter(); + template.process(dataModel, writer); + return writer.toString(); + } catch (IOException | TemplateException e) { + log.error("生成代码失败,模板: {}, 错误: {}", templateName, e.getMessage(), e); + return "// 生成失败: " + e.getMessage(); + } + } + + /** + * 检查模板是否存在 + * + * @param templateName 模板名称 + * @return 是否存在 + */ + public static boolean templateExists(String templateName) { + try { + configuration.getTemplate(templateName); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/MapUtil.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/MapUtil.java new file mode 100644 index 0000000..7620b36 --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/MapUtil.java @@ -0,0 +1,71 @@ +package com.agileboot.codegen.util; + +import java.util.Map; + +/** + * Map工具类 + */ +public class MapUtil { + + /** + * 从Map中获取String值 + * + * @param map Map对象 + * @param key 键 + * @return String值 + */ + public static String getString(Map map, String key) { + if (map == null || key == null) { + return null; + } + Object value = map.get(key); + return value != null ? value.toString() : null; + } + + /** + * 从Map中获取String值,带默认值 + * + * @param map Map对象 + * @param key 键 + * @param defaultValue 默认值 + * @return String值 + */ + public static String getString(Map map, String key, String defaultValue) { + String value = getString(map, key); + return value != null ? value : defaultValue; + } + + /** + * 从Map中获取Boolean值 + * + * @param map Map对象 + * @param key 键 + * @return Boolean值 + */ + public static Boolean getBoolean(Map map, String key) { + if (map == null || key == null) { + return null; + } + Object value = map.get(key); + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof String) { + return Boolean.parseBoolean((String) value); + } + return null; + } + + /** + * 从Map中获取Boolean值,带默认值 + * + * @param map Map对象 + * @param key 键 + * @param defaultValue 默认值 + * @return Boolean值 + */ + public static Boolean getBoolean(Map map, String key, Boolean defaultValue) { + Boolean value = getBoolean(map, key); + return value != null ? value : defaultValue; + } +} diff --git a/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/TableParseUtil.java b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/TableParseUtil.java new file mode 100644 index 0000000..ebdc4fd --- /dev/null +++ b/agileboot-system/wol-codegenerator/src/main/java/com/agileboot/codegen/util/TableParseUtil.java @@ -0,0 +1,179 @@ +package com.agileboot.codegen.util; + +import com.agileboot.codegen.entity.ClassInfo; +import com.agileboot.codegen.entity.FieldInfo; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 表解析工具类 + */ +public class TableParseUtil { + + /** + * 解析DDL SQL语句获取ClassInfo + * + * @param sql DDL SQL语句 + * @param ignorePrefix 忽略前缀 + * @return ClassInfo对象 + */ + public static ClassInfo parseTableSql(String sql, String ignorePrefix) { + if (StringUtils.isBlank(sql)) { + return null; + } + + ClassInfo classInfo = new ClassInfo(); + List fieldList = new ArrayList<>(); + + // 解析表名和注释 + parseTableInfo(sql, classInfo, ignorePrefix); + + // 解析字段信息 + parseFieldInfo(sql, fieldList); + + classInfo.setFieldList(fieldList); + return classInfo; + } + + /** + * 解析表信息 + */ + private static void parseTableInfo(String sql, ClassInfo classInfo, String ignorePrefix) { + // 匹配表名的正则表达式 + Pattern tablePattern = Pattern.compile("CREATE\\s+TABLE\\s+[`'\"]*([\\w_]+)[`'\"]*", Pattern.CASE_INSENSITIVE); + Matcher tableMatcher = tablePattern.matcher(sql); + + if (tableMatcher.find()) { + String tableName = tableMatcher.group(1); + classInfo.setOriginTableName(tableName); + + // 去掉前缀 + String processedTableName = tableName; + if (StringUtils.isNotBlank(ignorePrefix) && tableName.startsWith(ignorePrefix)) { + processedTableName = tableName.substring(ignorePrefix.length()); + } + + classInfo.setTableName(processedTableName); + classInfo.setClassName(toCamelCase(processedTableName, true)); + } + + // 匹配表注释 + Pattern commentPattern = Pattern.compile("COMMENT\\s*=\\s*['\"]([^'\"]*)['\"]", Pattern.CASE_INSENSITIVE); + Matcher commentMatcher = commentPattern.matcher(sql); + if (commentMatcher.find()) { + classInfo.setClassComment(commentMatcher.group(1)); + } + } + + /** + * 解析字段信息 + */ + private static void parseFieldInfo(String sql, List fieldList) { + // 匹配字段定义的正则表达式 + Pattern fieldPattern = Pattern.compile( + "[`'\"]*([\\w_]+)[`'\"]*\\s+([\\w()]+).*?(?:COMMENT\\s*['\"]([^'\"]*)['\"])?", + Pattern.CASE_INSENSITIVE + ); + + String[] lines = sql.split("\n"); + for (String line : lines) { + line = line.trim(); + // 跳过非字段行 + if (line.startsWith("CREATE") || line.startsWith("PRIMARY") || + line.startsWith("KEY") || line.startsWith("INDEX") || + line.startsWith(")") || line.isEmpty()) { + continue; + } + + Matcher fieldMatcher = fieldPattern.matcher(line); + if (fieldMatcher.find()) { + FieldInfo fieldInfo = new FieldInfo(); + String columnName = fieldMatcher.group(1); + String columnType = fieldMatcher.group(2); + String comment = fieldMatcher.group(3); + + fieldInfo.setColumnName(columnName); + fieldInfo.setFieldName(toCamelCase(columnName, false)); + fieldInfo.setFieldClass(getJavaType(columnType)); + fieldInfo.setSwaggerClass(getSwaggerType(columnType)); + fieldInfo.setFieldComment(comment != null ? comment : ""); + + fieldList.add(fieldInfo); + } + } + } + + /** + * 转换为驼峰命名 + * + * @param str 原字符串 + * @param firstUpper 首字母是否大写 + * @return 驼峰命名字符串 + */ + private static String toCamelCase(String str, boolean firstUpper) { + if (StringUtils.isBlank(str)) { + return str; + } + + StringBuilder result = new StringBuilder(); + String[] parts = str.toLowerCase().split("_"); + + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (StringUtils.isNotBlank(part)) { + if (i == 0 && !firstUpper) { + result.append(part); + } else { + result.append(part.substring(0, 1).toUpperCase()) + .append(part.substring(1)); + } + } + } + + return result.toString(); + } + + /** + * 获取Java类型 + */ + private static String getJavaType(String mysqlType) { + String lowerType = mysqlType.toLowerCase(); + if (lowerType.contains("bigint")) { + return "Long"; + } else if (lowerType.contains("int") || lowerType.contains("tinyint")) { + return "Integer"; + } else if (lowerType.contains("varchar") || lowerType.contains("text") || lowerType.contains("char")) { + return "String"; + } else if (lowerType.contains("decimal") || lowerType.contains("double")) { + return "Double"; + } else if (lowerType.contains("float")) { + return "Float"; + } else if (lowerType.contains("date") || lowerType.contains("time")) { + return "Date"; + } else { + return "String"; + } + } + + /** + * 获取Swagger类型 + */ + private static String getSwaggerType(String mysqlType) { + String lowerType = mysqlType.toLowerCase(); + if (lowerType.contains("int") || lowerType.contains("tinyint")) { + return "integer"; + } else if (lowerType.contains("varchar") || lowerType.contains("text") || lowerType.contains("char")) { + return "string"; + } else if (lowerType.contains("decimal") || lowerType.contains("double") || lowerType.contains("float")) { + return "number"; + } else if (lowerType.contains("date") || lowerType.contains("time")) { + return "string"; + } else { + return "string"; + } + } +}