代码生成功能

This commit is contained in:
cuijiawang
2025-09-25 14:52:14 +08:00
parent 142c24ca90
commit f91ecee6cc
12 changed files with 560 additions and 6 deletions

View File

@@ -24,6 +24,11 @@
<groupId>com.agileboot</groupId>
<artifactId>agileboot-system-base</artifactId>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-codegenerator</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>

View File

@@ -22,5 +22,11 @@
<groupId>com.agileboot</groupId>
<artifactId>wol-common-web</artifactId>
</dependency>
<!-- Freemarker模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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<String, Object> options = new HashMap<>();
if (request.getOptions() != null) {
options.putAll(request.getOptions());
}
options.put("tableSql", request.getTableSql());
// 调用服务生成代码
Map<String, String> result = generatorService.getResultByParams(options);
// 检查是否有错误
if (result.containsKey("error")) {
return R.fail(result.get("error"));
}
Map<String, Object> 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());
}
}
}

View File

@@ -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<String, Object> options;
}

View File

@@ -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<FieldInfo> fieldList;
}

View File

@@ -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;
}

View File

@@ -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<String, Object> options;
}

View File

@@ -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<String, String> getResultByParams(Map<String, Object> options) {
Map<String, String> 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;
}
}

View File

@@ -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<String, String> getResultByParams(Map<String, Object> options);
}

View File

@@ -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<String, Object> 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;
}
}
}

View File

@@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> map, String key, Boolean defaultValue) {
Boolean value = getBoolean(map, key);
return value != null ? value : defaultValue;
}
}

View File

@@ -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<FieldInfo> 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<FieldInfo> 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";
}
}
}