代码生成功能
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user