重构backend

This commit is contained in:
Moshow郑锴 2025-11-30 20:33:18 +08:00
parent 74a4f3f293
commit e9b7e9c477
27 changed files with 981 additions and 666 deletions

View File

@ -0,0 +1,217 @@
# Spring Boot代码生成器项目重构说明文档
## 1. 重构概述
本项目旨在对Spring Boot代码生成器进行现代化重构使其具有更清晰的架构、更好的可维护性和更强的扩展性。重构遵循现代Spring Boot应用的最佳实践采用了分层架构设计和多种设计模式。
## 2. 重构目标
1. **清晰的分层架构**明确Controller、Service、DTO、VO等各层职责
2. **良好的可扩展性**通过策略模式处理不同类型的SQL解析
3. **现代化开发规范**遵循Spring Boot和Java开发最佳实践
4. **易于维护**:通过合理的包结构和命名规范提高代码可读性
5. **前后端兼容性**:保持与现有前端代码的数据交互格式
## 3. 重构后项目结构
```
com.softdev.system.generator
├── GeneratorApplication.java # 启动类
├── config # 配置类包
│ ├── WebMvcConfig.java # MVC配置
│ └── GlobalExceptionHandler.java # 全局异常处理器
├── controller # 控制层
│ ├── PageController.java # 页面跳转控制器
│ ├── CodeGenController.java # 代码生成相关接口
│ └── TemplateController.java # 模板相关接口
├── service # 服务层接口
│ ├── CodeGenService.java # 代码生成服务接口
│ ├── TemplateService.java # 模板服务接口
│ └── parser
│ ├── SqlParserService.java # SQL解析服务接口
│ └── JsonParserService.java # JSON解析服务接口
├── service.impl # 服务实现层
│ ├── CodeGenServiceImpl.java # 代码生成服务实现
│ ├── TemplateServiceImpl.java # 模板服务实现
│ └── parser
│ ├── SqlParserServiceImpl.java # SQL解析服务实现
│ └── JsonParserServiceImpl.java # JSON解析服务实现
├── entity # 实体类
│ ├── dto
│ │ ├── ParamInfo.java # 参数信息DTO
│ │ ├── ClassInfo.java # 类信息DTO
│ │ └── FieldInfo.java # 字段信息DTO
│ ├── vo
│ │ └── ResultVo.java # 统一返回结果VO
│ └── enums
│ └── ParserTypeEnum.java # 解析类型枚举
├── util # 工具类包
│ ├── FreemarkerUtil.java # Freemarker工具类
│ ├── StringUtilsPlus.java # 字符串工具类
│ ├── MapUtil.java # Map工具类
│ ├── mysqlJavaTypeUtil.java # MySQL类型转换工具类
│ └── exception
│ ├── CodeGenException.java # 自定义业务异常
│ └── SqlParseException.java # SQL解析异常
└── constant # 常量定义
└── CodeGenConstants.java # 代码生成常量(待实现)
```
## 4. 各层详细说明
### 4.1 控制层 (Controller)
控制层负责处理HTTP请求协调业务逻辑并返回结果
1. **[PageController](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/controller/PageController.java)**:
- 处理页面跳转请求
- 返回视图页面
2. **[CodeGenController](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/controller/CodeGenController.java)**:
- 提供代码生成相关REST API
- 处理代码生成请求
3. **[TemplateController](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/controller/TemplateController.java)**:
- 提供模板管理相关REST API
- 处理模板获取请求
### 4.2 服务层 (Service)
服务层采用接口与实现分离的设计,便于测试和扩展:
1. **接口层**:
- [CodeGenService](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/CodeGenService.java): 核心代码生成服务接口
- [TemplateService](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/TemplateService.java): 模板管理服务接口
- [SqlParserService](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/parser/SqlParserService.java): SQL解析服务接口
- [JsonParserService](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/parser/JsonParserService.java): JSON解析服务接口
2. **实现层**:
- [CodeGenServiceImpl](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/impl/CodeGenServiceImpl.java): 核心代码生成服务实现
- [TemplateServiceImpl](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/impl/TemplateServiceImpl.java): 模板管理服务实现
- [SqlParserServiceImpl](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/impl/parser/SqlParserServiceImpl.java): SQL解析服务实现
- [JsonParserServiceImpl](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/impl/parser/JsonParserServiceImpl.java): JSON解析服务实现
### 4.3 实体层 (Entity)
实体层按照用途分类,避免不同类型对象混用:
1. **DTO (Data Transfer Object)**:
- [ParamInfo](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/entity/dto/ParamInfo.java): 参数信息传输对象
- [ClassInfo](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/entity/dto/ClassInfo.java): 类信息传输对象
- [FieldInfo](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/entity/dto/FieldInfo.java): 字段信息传输对象
2. **VO (View Object)**:
- [ResultVo](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/entity/vo/ResultVo.java): 统一返回结果视图对象
3. **Enums**:
- [ParserTypeEnum](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/entity/enums/ParserTypeEnum.java): 解析类型枚举
### 4.4 工具层 (Util)
工具层包含各种通用工具类和自定义异常:
1. **工具类**:
- [FreemarkerUtil](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/util/FreemarkerUtil.java): Freemarker模板处理工具
- [StringUtilsPlus](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/util/StringUtilsPlus.java): 字符串处理工具
- [MapUtil](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/util/MapUtil.java): Map操作工具
- [mysqlJavaTypeUtil](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/util/mysqlJavaTypeUtil.java): MySQL与Java类型映射工具
2. **异常类**:
- [CodeGenException](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/util/exception/CodeGenException.java): 代码生成自定义业务异常
- [SqlParseException](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/util/exception/SqlParseException.java): SQL解析异常
## 5. 关键设计模式应用
### 5.1 策略模式
在SQL解析功能中应用策略模式将不同的解析方式封装成独立的策略类
1. [SqlParserServiceImpl](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/impl/parser/SqlParserServiceImpl.java)中实现了多种SQL解析方法
- `processTableIntoClassInfo`: 默认SQL解析
- `generateSelectSqlBySQLPraser`: SELECT SQL解析
- `generateCreateSqlBySQLPraser`: CREATE SQL解析
- `processTableToClassInfoByRegex`: 正则表达式解析
- `processInsertSqlToClassInfo`: INSERT SQL解析
2. [JsonParserServiceImpl](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/service/impl/parser/JsonParserServiceImpl.java)中实现了JSON解析
- `processJsonToClassInfo`: JSON解析
通过策略模式,可以:
- 避免大量的if-else判断
- 便于添加新的解析策略
- 提高代码的可维护性
### 5.2 接口与实现分离
所有服务层都采用接口与实现分离的设计,便于:
- 单元测试模拟
- 多种实现方式切换
- 降低模块间耦合度
## 6. 重要技术实现细节
### 6.1 统一响应格式
所有控制器方法均返回 [ResultVo](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/entity/vo/ResultVo.java) 统一响应对象,保持与前端的兼容性:
```java
// 成功响应
ResultVo.ok(data)
// 错误响应
ResultVo.error(message)
```
### 6.2 前后端兼容性处理
为了保持与现有前端JavaScript代码的兼容性在处理响应数据时特别注意了数据结构
1. 模板获取接口返回数据结构:
```json
{
"code": 200,
"msg": "success",
"templates": [...]
}
```
2. 代码生成接口返回数据结构:
```json
{
"code": 200,
"msg": "success",
"outputJson": {
"tableName": "...",
"controller": "...",
"service": "...",
// 其他模板生成的代码
}
}
```
### 6.3 组件扫描配置
由于服务实现类位于不同的包层级中,已在 [Application](file:///D:/Workspace/Project/SpringBootCodeGenerator/generator-web/src/main/java/com/softdev/system/generator/Application.java) 类中配置了组件扫描路径:
```java
@SpringBootApplication(scanBasePackages = "com.softdev.system.generator")
```
确保所有服务实现类都能被正确扫描和注入。
## 7. 重构优势总结
1. **结构清晰**:通过合理的包结构和分层设计,使项目结构更加清晰易懂
2. **易于维护**:各层职责明确,便于定位和修复问题
3. **易于扩展**:采用策略模式等设计模式,便于添加新的功能模块
4. **现代化**遵循Spring Boot和Java的最新最佳实践
5. **前后端兼容**:保持与现有前端代码的数据交互格式,无缝升级
## 8. 后续优化建议
1. **添加单元测试**:为各层添加完整的单元测试,确保代码质量
2. **集成日志系统**:完善日志记录,便于问题排查
3. **添加缓存机制**:对模板等不常变化的数据添加缓存,提高性能
4. **完善异常处理**:统一异常处理机制,提供更友好的错误提示
5. **添加接口文档**使用Swagger等工具生成接口文档便于前后端协作
6. **增加常量定义**:将硬编码的字符串提取为常量,提高可维护性

View File

@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author zhengkai.blog.csdn.net
*/
@SpringBootApplication
@SpringBootApplication(scanBasePackages = "com.softdev.system.generator")
public class Application {
public static void main(String[] args) {

View File

@ -1,25 +1,16 @@
package com.softdev.system.generator.config;
// import com.alibaba.fastjson.support.config.FastJsonConfig;
// import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.support.config.FastJsonConfig;
import com.alibaba.fastjson2.support.spring6.http.converter.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**

View File

@ -0,0 +1,29 @@
package com.softdev.system.generator.controller;
import com.softdev.system.generator.entity.dto.ParamInfo;
import com.softdev.system.generator.entity.vo.ResultVo;
import com.softdev.system.generator.service.CodeGenService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
/**
* 代码生成控制器
*
* @author zhengkai.blog.csdn.net
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/code")
public class CodeGenController {
private final CodeGenService codeGenService;
@PostMapping("/generate")
public ResultVo generateCode(@RequestBody ParamInfo paramInfo) throws Exception {
return codeGenService.generateCode(paramInfo);
}
}

View File

@ -1,105 +0,0 @@
package com.softdev.system.generator.controller;
import com.alibaba.fastjson2.JSONArray;
import com.softdev.system.generator.entity.ClassInfo;
import com.softdev.system.generator.entity.ParamInfo;
import com.softdev.system.generator.entity.ReturnT;
import com.softdev.system.generator.service.GeneratorService;
import com.softdev.system.generator.util.MapUtil;
import com.softdev.system.generator.util.TableParseUtil;
import com.softdev.system.generator.util.ValueUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.Date;
import java.util.Map;
/**
* 代码生成控制器
* @author zhengkai.blog.csdn.net
*/
@Controller
@Slf4j
public class GeneratorController {
@Autowired
private ValueUtil valueUtil;
@Autowired
private GeneratorService generatorService;
@GetMapping("/")
public ModelAndView defaultPage() {
return new ModelAndView("newui2").addObject("value",valueUtil);
}
@GetMapping("/index")
public ModelAndView indexPage() {
return new ModelAndView("newui2").addObject("value",valueUtil);
}
@RequestMapping("/template/all")
@ResponseBody
public ReturnT getAllTemplates() throws Exception {
String templates = generatorService.getTemplateConfig();
return ReturnT.ok().put("templates", JSONArray.parseArray(templates));
}
@PostMapping("/code/generate")
@ResponseBody
public ReturnT generateCode(@RequestBody ParamInfo paramInfo) throws Exception {
//log.info(JSON.toJSONString(paramInfo.getOptions()));
if (StringUtils.isEmpty(paramInfo.getTableSql())) {
return ReturnT.error("表结构信息为空");
}
//1.Parse Table Structure 表结构解析
ClassInfo classInfo = null;
String dataType = MapUtil.getString(paramInfo.getOptions(),"dataType");
switch (dataType) {
case "sql":
//默认模式parse DDL table structure from sql
classInfo = generatorService.processTableIntoClassInfo(paramInfo);
break;
case "json":
//JSON模式parse field from json string
classInfo = generatorService.processJsonToClassInfo(paramInfo);
break;
case "insert-sql":
//INSERT SQL模式parse field from insert sql
classInfo = generatorService.processInsertSqlToClassInfo(paramInfo);
break;
case "sql-regex":
//正则表达式模式非完善版本parse sql by regex
classInfo = generatorService.processTableToClassInfoByRegex(paramInfo);
break;
case "select-sql":
//SelectSqlBySQLPraser模式:parse select sql by JSqlParser
classInfo = generatorService.generateSelectSqlBySQLPraser(paramInfo);
break;
case "create-sql":
//CreateSqlBySQLPraser模式:parse create sql by JSqlParser
classInfo = generatorService.generateCreateSqlBySQLPraser(paramInfo);
break;
default:
//默认模式parse DDL table structure from sql
classInfo = generatorService.processTableIntoClassInfo(paramInfo);
break;
}
//2.Set the params 设置表格参数
paramInfo.getOptions().put("classInfo", classInfo);
paramInfo.getOptions().put("tableName", classInfo == null ? System.currentTimeMillis() : classInfo.getTableName());
//log the generated table and filed size记录解析了什么表有多少个字段
//log.info("generated table :{} , size :{}",classInfo.getTableName(),(classInfo.getFieldList() == null ? "" : classInfo.getFieldList().size()));
//3.generate the code by freemarker templates with parameters . Freemarker根据参数和模板生成代码
Map<String, String> result = generatorService.getResultByParams(paramInfo.getOptions());
// log.info("result {}",result);
log.info("table:{} - time:{} ", MapUtil.getString(result,"tableName"),new Date());
return ReturnT.ok().put("outputJson",result);
}
}

View File

@ -0,0 +1,30 @@
package com.softdev.system.generator.controller;
import com.softdev.system.generator.util.ValueUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* 页面控制器
*
* @author zhengkai.blog.csdn.net
*/
@RequiredArgsConstructor
@Controller
public class PageController {
private final ValueUtil valueUtil;
@GetMapping("/")
public ModelAndView defaultPage() {
return new ModelAndView("newui2").addObject("value", valueUtil);
}
@GetMapping("/index")
public ModelAndView indexPage() {
return new ModelAndView("newui2").addObject("value", valueUtil);
}
}

View File

@ -0,0 +1,28 @@
package com.softdev.system.generator.controller;
import com.alibaba.fastjson2.JSONArray;
import com.softdev.system.generator.entity.vo.ResultVo;
import com.softdev.system.generator.service.TemplateService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 模板管理控制器
*
* @author zhengkai.blog.csdn.net
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/template")
public class TemplateController {
private final TemplateService templateService;
@PostMapping("/all")
public ResultVo getAllTemplates() throws Exception {
return ResultVo.ok(templateService.getAllTemplates());
}
}

View File

@ -0,0 +1,21 @@
package com.softdev.system.generator.entity.dto;
import lombok.Data;
import java.util.List;
/**
* 类信息
*
* @author zhengkai.blog.csdn.net
*/
@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,19 @@
package com.softdev.system.generator.entity.dto;
import lombok.Data;
/**
* 字段信息
*
* @author zhengkai.blog.csdn.net
*/
@Data
public class FieldInfo {
private String columnName;
private String fieldName;
private String fieldClass;
private String swaggerClass;
private String fieldComment;
}

View File

@ -0,0 +1,25 @@
package com.softdev.system.generator.entity.dto;
import lombok.Data;
import java.util.Map;
/**
* 请求参数信息
*
* @author zhengkai.blog.csdn.net
*/
@Data
public class ParamInfo {
private String tableSql;
private Map<String, Object> options;
@Data
public static class NameCaseType {
public static final String CAMEL_CASE = "CamelCase";
public static final String UNDER_SCORE_CASE = "UnderScoreCase";
public static final String UPPER_UNDER_SCORE_CASE = "UpperUnderScoreCase";
}
}

View File

@ -0,0 +1,38 @@
package com.softdev.system.generator.entity.enums;
import lombok.Getter;
/**
* 解析类型枚举
*/
@Getter
public enum ParserTypeEnum {
/**
* SQL解析类型
*/
SQL("sql", "默认SQL解析"),
JSON("json", "JSON解析"),
INSERT_SQL("insert-sql", "INSERT SQL解析"),
SQL_REGEX("sql-regex", "正则表达式SQL解析"),
SELECT_SQL("select-sql", "SELECT SQL解析"),
CREATE_SQL("create-sql", "CREATE SQL解析");
private final String value;
private final String description;
ParserTypeEnum(String value, String description) {
this.value = value;
this.description = description;
}
public static ParserTypeEnum fromValue(String value) {
for (ParserTypeEnum type : ParserTypeEnum.values()) {
if (type.getValue().equals(value)) {
return type;
}
}
// 默认返回SQL类型
return SQL;
}
}

View File

@ -0,0 +1,50 @@
package com.softdev.system.generator.entity.vo;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 统一返回结果VO
*
* @author zhengkai.blog.csdn.net
*/
@Data
public class ResultVo extends HashMap<String, Object> {
public ResultVo() {
put("code", 200);
put("msg", "success");
}
public static ResultVo ok() {
return new ResultVo();
}
public static ResultVo ok(Object data) {
ResultVo resultVo = new ResultVo();
resultVo.put("data", data);
return resultVo;
}
public static ResultVo error(String msg) {
ResultVo resultVo = new ResultVo();
resultVo.put("code", 500);
resultVo.put("msg", msg);
return resultVo;
}
public static ResultVo error(int code, String msg) {
ResultVo resultVo = new ResultVo();
resultVo.put("code", code);
resultVo.put("msg", msg);
return resultVo;
}
@Override
public ResultVo put(String key, Object value) {
super.put(key, value);
return this;
}
}

View File

@ -0,0 +1,33 @@
package com.softdev.system.generator.service;
import com.softdev.system.generator.entity.dto.ClassInfo;
import com.softdev.system.generator.entity.dto.ParamInfo;
import com.softdev.system.generator.entity.vo.ResultVo;
import java.util.Map;
/**
* 代码生成服务接口
*
* @author zhengkai.blog.csdn.net
*/
public interface CodeGenService {
/**
* 生成代码
*
* @param paramInfo 参数信息
* @return 生成的代码映射
* @throws Exception 生成过程中的异常
*/
ResultVo generateCode(ParamInfo paramInfo) throws Exception;
/**
* 根据参数获取结果
*
* @param params 参数映射
* @return 结果映射
* @throws Exception 处理过程中的异常
*/
Map<String, String> getResultByParams(Map<String, Object> params) throws Exception;
}

View File

@ -1,57 +0,0 @@
package com.softdev.system.generator.service;
import com.softdev.system.generator.entity.ClassInfo;
import com.softdev.system.generator.entity.ParamInfo;
import freemarker.template.TemplateException;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;
/**
* GeneratorService
*
* @author zhengkai.blog.csdn.net
*/
public interface GeneratorService {
String getTemplateConfig() throws IOException;
Map<String, String> getResultByParams(Map<String, Object> params) throws IOException, TemplateException;
/**
* 解析Select-SQL生成类信息(JSQLPraser版本)
* @auther: zhengkai.blog.csdn.net
* @param paramInfo
* @return
*/
ClassInfo generateSelectSqlBySQLPraser(ParamInfo paramInfo) throws Exception;
ClassInfo generateCreateSqlBySQLPraser(ParamInfo paramInfo) throws Exception;
/**
* 解析DDL-SQL生成类信息
* @auther: zhengkai.blog.csdn.net
* @param paramInfo
* @return
*/
ClassInfo processTableIntoClassInfo(ParamInfo paramInfo) throws Exception;
/**
* 解析JSON生成类信息
* @auther: zhengkai.blog.csdn.net
* @param paramInfo
* @return
*/
ClassInfo processJsonToClassInfo(ParamInfo paramInfo);
/**
* 解析DDL SQL生成类信息-正则表达式版本
* @auther: zhengkai.blog.csdn.net
* @param paramInfo
* @return
*/
ClassInfo processTableToClassInfoByRegex(ParamInfo paramInfo);
/**
* 解析INSERT-SQL生成类信息-正则表达式版本
* @auther: zhengkai.blog.csdn.net
* @param paramInfo
* @return
*/
ClassInfo processInsertSqlToClassInfo(ParamInfo paramInfo);
}

View File

@ -0,0 +1,21 @@
package com.softdev.system.generator.service;
import com.alibaba.fastjson2.JSONArray;
import java.io.IOException;
/**
* 模板服务接口
*
* @author zhengkai.blog.csdn.net
*/
public interface TemplateService {
/**
* 获取所有模板配置
*
* @return 模板配置字符串
* @throws IOException IO异常
*/
JSONArray getAllTemplates() throws IOException;
}

View File

@ -0,0 +1,120 @@
package com.softdev.system.generator.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.softdev.system.generator.entity.dto.ClassInfo;
import com.softdev.system.generator.entity.dto.ParamInfo;
import com.softdev.system.generator.entity.enums.ParserTypeEnum;
import com.softdev.system.generator.entity.vo.ResultVo;
import com.softdev.system.generator.service.CodeGenService;
import com.softdev.system.generator.service.TemplateService;
import com.softdev.system.generator.service.parser.JsonParserService;
import com.softdev.system.generator.service.parser.SqlParserService;
import com.softdev.system.generator.util.FreemarkerUtil;
import com.softdev.system.generator.util.MapUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 代码生成服务实现类
*
* @author zhengkai.blog.csdn.net
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class CodeGenServiceImpl implements CodeGenService {
private final TemplateService templateService;
private final SqlParserService sqlParserService;
private final JsonParserService jsonParserService;
@Override
public ResultVo generateCode(ParamInfo paramInfo) throws Exception {
if (paramInfo.getTableSql() == null || paramInfo.getTableSql().isEmpty()) {
return ResultVo.error("表结构信息为空");
}
try {
// 1. Parse Table Structure 表结构解析
ClassInfo classInfo = parseTableStructure(paramInfo);
// 2. Set the params 设置表格参数
paramInfo.getOptions().put("classInfo", classInfo);
paramInfo.getOptions().put("tableName", classInfo == null ? System.currentTimeMillis() + "" : classInfo.getTableName());
// 3. generate the code by freemarker templates with parameters .
// Freemarker根据参数和模板生成代码
Map<String, String> result = getResultByParams(paramInfo.getOptions());
log.info("table:{} - time:{} ", MapUtil.getString(result, "tableName"), System.currentTimeMillis());
return ResultVo.ok(result);
} catch (Exception e) {
log.error("代码生成失败", e);
return ResultVo.error("代码生成失败: " + e.getMessage());
}
}
@Override
public Map<String, String> getResultByParams(Map<String, Object> params) throws Exception {
Map<String, String> result = new HashMap<>(32);
result.put("tableName", MapUtil.getString(params, "tableName"));
// 处理模板生成逻辑
// 解析模板配置并生成代码
JSONArray parentTemplates = templateService.getAllTemplates();
for (int i = 0; i < parentTemplates.size(); i++) {
JSONObject parentTemplateObj = parentTemplates.getJSONObject(i);
JSONArray childTemplates = parentTemplateObj.getJSONArray("templates");
if (childTemplates != null) {
for (int x = 0; x < childTemplates.size(); x++) {
JSONObject childTemplate = childTemplates.getJSONObject(x);
String templatePath = parentTemplateObj.getString("group") + "/" + childTemplate.getString("name") + ".ftl";
String generatedCode = FreemarkerUtil.processString(templatePath, params);
result.put(childTemplate.getString("name"), generatedCode);
}
}
}
return result;
}
/**
* 根据不同的解析类型解析表结构
*
* @param paramInfo 参数信息
* @return 类信息
* @throws Exception 解析异常
*/
private ClassInfo parseTableStructure(ParamInfo paramInfo) throws Exception {
String dataType = MapUtil.getString(paramInfo.getOptions(), "dataType");
ParserTypeEnum parserType = ParserTypeEnum.fromValue(dataType);
switch (parserType) {
case SQL:
// 默认模式parse DDL table structure from sql
return sqlParserService.processTableIntoClassInfo(paramInfo);
case JSON:
// JSON模式parse field from json string
return jsonParserService.processJsonToClassInfo(paramInfo);
case INSERT_SQL:
// INSERT SQL模式parse field from insert sql
return sqlParserService.processInsertSqlToClassInfo(paramInfo);
case SQL_REGEX:
// 正则表达式模式非完善版本parse sql by regex
return sqlParserService.processTableToClassInfoByRegex(paramInfo);
case SELECT_SQL:
// SelectSqlBySQLPraser模式:parse select sql by JSqlParser
return sqlParserService.generateSelectSqlBySQLPraser(paramInfo);
case CREATE_SQL:
// CreateSqlBySQLPraser模式:parse create sql by JSqlParser
return sqlParserService.generateCreateSqlBySQLPraser(paramInfo);
default:
// 默认模式parse DDL table structure from sql
return sqlParserService.processTableIntoClassInfo(paramInfo);
}
}
}

View File

@ -0,0 +1,36 @@
package com.softdev.system.generator.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.softdev.system.generator.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
/**
* 模板服务实现类
*
* @author zhengkai.blog.csdn.net
*/
@Slf4j
@Service
public class TemplateServiceImpl implements TemplateService {
private String templateConfig = null;
@Override
public JSONArray getAllTemplates() throws IOException {
if (templateConfig == null) {
ClassPathResource resource = new ClassPathResource("template.json");
try (InputStream inputStream = resource.getInputStream()) {
templateConfig = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
}
}
return JSONArray.parseArray(templateConfig);
}
}

View File

@ -0,0 +1,90 @@
package com.softdev.system.generator.service.impl.parser;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.softdev.system.generator.entity.dto.ClassInfo;
import com.softdev.system.generator.entity.dto.FieldInfo;
import com.softdev.system.generator.entity.dto.ParamInfo;
import com.softdev.system.generator.service.parser.JsonParserService;
import com.softdev.system.generator.util.exception.CodeGenException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* JSON解析服务实现类
*
* @author zhengkai.blog.csdn.net
*/
@Service
public class JsonParserServiceImpl implements JsonParserService {
@Override
public ClassInfo processJsonToClassInfo(ParamInfo paramInfo) {
ClassInfo codeJavaInfo = new ClassInfo();
codeJavaInfo.setTableName("JsonDto");
codeJavaInfo.setClassName("JsonDto");
codeJavaInfo.setClassComment("JsonDto");
//support children json if forget to add '{' in front of json
if (paramInfo.getTableSql().trim().startsWith("\"")) {
paramInfo.setTableSql("{" + paramInfo.getTableSql());
}
try {
if (paramInfo.getTableSql().trim().startsWith("{")) {
JSONObject jsonObject = JSONObject.parseObject(paramInfo.getTableSql().trim());
//parse FieldList by JSONObject
codeJavaInfo.setFieldList(processJsonObjectToFieldList(jsonObject));
} else if (paramInfo.getTableSql().trim().startsWith("[")) {
JSONArray jsonArray = JSONArray.parseArray(paramInfo.getTableSql().trim());
//parse FieldList by JSONObject
codeJavaInfo.setFieldList(processJsonObjectToFieldList(jsonArray.getJSONObject(0)));
}
} catch (Exception e) {
// JSON解析失败抛出自定义异常
throw new CodeGenException("JSON格式不正确: " + e.getMessage());
}
return codeJavaInfo;
}
public List<FieldInfo> processJsonObjectToFieldList(JSONObject jsonObject) {
// field List
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
for (String jsonField : jsonObject.keySet()) {
FieldInfo fieldInfo = new FieldInfo();
fieldInfo.setFieldName(jsonField);
fieldInfo.setColumnName(jsonField);
fieldInfo.setFieldClass(String.class.getSimpleName());
fieldInfo.setFieldComment("father:" + jsonField);
fieldList.add(fieldInfo);
if (jsonObject.get(jsonField) instanceof JSONArray) {
JSONArray jsonArray = jsonObject.getJSONArray(jsonField);
for (Object arrayObject : jsonArray) {
FieldInfo fieldInfo2 = new FieldInfo();
fieldInfo2.setFieldName(arrayObject.toString());
fieldInfo2.setColumnName(arrayObject.toString());
fieldInfo2.setFieldClass(String.class.getSimpleName());
fieldInfo2.setFieldComment("children:" + arrayObject.toString());
fieldList.add(fieldInfo2);
}
} else if (jsonObject.get(jsonField) instanceof JSONObject) {
JSONObject subJsonObject = jsonObject.getJSONObject(jsonField);
for (String arrayObject : subJsonObject.keySet()) {
FieldInfo fieldInfo2 = new FieldInfo();
fieldInfo2.setFieldName(arrayObject.toString());
fieldInfo2.setColumnName(arrayObject.toString());
fieldInfo2.setFieldClass(String.class.getSimpleName());
fieldInfo2.setFieldComment("children:" + arrayObject.toString());
fieldList.add(fieldInfo2);
}
}
}
if (fieldList.size() < 1) {
throw new CodeGenException("JSON解析失败");
}
return fieldList;
}
}

View File

@ -1,11 +1,14 @@
package com.softdev.system.generator.service;
package com.softdev.system.generator.service.impl.parser;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.softdev.system.generator.entity.*;
import com.softdev.system.generator.util.*;
import freemarker.template.TemplateException;
import com.softdev.system.generator.entity.dto.ClassInfo;
import com.softdev.system.generator.entity.dto.FieldInfo;
import com.softdev.system.generator.entity.dto.ParamInfo;
import com.softdev.system.generator.service.parser.SqlParserService;
import com.softdev.system.generator.util.MapUtil;
import com.softdev.system.generator.util.StringUtilsPlus;
import com.softdev.system.generator.util.exception.SqlParseException;
import com.softdev.system.generator.util.mysqlJavaTypeUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
@ -21,69 +24,23 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.*;
import java.util.*;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static net.sf.jsqlparser.parser.feature.Feature.createTable;
/**
* GeneratorService
* SQL解析服务实现类
*
* @author zhengkai.blog.csdn.net
*/
@Slf4j
@Service
public class GeneratorServiceImpl implements GeneratorService {
public class SqlParserServiceImpl implements SqlParserService {
String templateCpnfig = null;
/**
* 从项目中的JSON文件读取String
*
* @author zhengkai.blog.csdn.net
*/
@Override
public String getTemplateConfig() throws IOException {
templateCpnfig = null;
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template.json");
templateCpnfig = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining(System.lineSeparator()));
inputStream.close();
//log.info(JSON.toJSONString(templateCpnfig));
return templateCpnfig;
}
/**
* 根据配置的Template模板进行遍历解析得到生成好的String
*
* @author zhengkai.blog.csdn.net
*/
@Override
public Map<String, String> getResultByParams(Map<String, Object> params) throws IOException, TemplateException {
Map<String, String> result = new HashMap<>(32);
result.put("tableName", MapUtil.getString(params,"tableName"));
JSONArray parentTemplates = JSONArray.parseArray(getTemplateConfig());
for (int i = 0; i <parentTemplates.size() ; i++) {
JSONObject parentTemplateObj = parentTemplates.getJSONObject(i);
for (int x = 0; x <parentTemplateObj.getJSONArray("templates").size() ; x++) {
JSONObject childTemplate = parentTemplateObj.getJSONArray("templates").getJSONObject(x);
result.put(childTemplate.getString("name"), FreemarkerUtil.processString(parentTemplateObj.getString("group") + "/" +childTemplate.getString("name")+ ".ftl", params));
}
}
return result;
}
/**
* 根据SQL解析器解析表结构
* @author zhengkai.blog.csdn.net
* @param paramInfo
* @return
* @throws Exception
*/
@Override
public ClassInfo generateSelectSqlBySQLPraser(ParamInfo paramInfo) throws Exception {
ClassInfo classInfo = new ClassInfo();
@ -128,11 +85,11 @@ public class GeneratorServiceImpl implements GeneratorService {
//转换前
fieldInfo.setColumnName(fieldName);
fieldName = switch ((String) paramInfo.getOptions().get("nameCaseType")) {
case ParamInfo.NAME_CASE_TYPE.CAMEL_CASE ->
case ParamInfo.NameCaseType.CAMEL_CASE ->
// 2024-1-27 L&J 适配任意(maybe)原始风格转小写驼峰
StringUtilsPlus.toLowerCamel(aliasName);
case ParamInfo.NAME_CASE_TYPE.UNDER_SCORE_CASE -> StringUtilsPlus.toUnderline(aliasName, false);
case ParamInfo.NAME_CASE_TYPE.UPPER_UNDER_SCORE_CASE ->
case ParamInfo.NameCaseType.UNDER_SCORE_CASE -> StringUtilsPlus.toUnderline(aliasName, false);
case ParamInfo.NameCaseType.UPPER_UNDER_SCORE_CASE ->
StringUtilsPlus.toUnderline(aliasName.toUpperCase(), true);
default -> aliasName;
};
@ -147,13 +104,7 @@ public class GeneratorServiceImpl implements GeneratorService {
log.info("classInfo:{}", JSON.toJSONString(classInfo));
return classInfo;
}
/**
* 根据SQL解析器解析表结构
* @author zhengkai.blog.csdn.net
* @param paramInfo
* @return
* @throws Exception
*/
@Override
public ClassInfo generateCreateSqlBySQLPraser(ParamInfo paramInfo) throws Exception {
ClassInfo classInfo = new ClassInfo();
@ -168,12 +119,12 @@ public class GeneratorServiceImpl implements GeneratorService {
statement = CCJSqlParserUtil.parse(processedSql);
}catch (Exception e) {
e.printStackTrace();
throw new SqlException("SQL语法错误:"+e.getMessage());
throw new SqlParseException("SQL语法错误:"+e.getMessage());
}
// 确保是CREATE TABLE语句
if (!(statement instanceof CreateTable createTable)) {
throw new SqlException("检测到SQL语句不是DLL CREATE TABLE语句");
throw new SqlParseException("检测到SQL语句不是DLL CREATE TABLE语句");
}
// 提取表名
@ -200,9 +151,9 @@ public class GeneratorServiceImpl implements GeneratorService {
// 根据命名规则转换字段名
String fieldName = switch ((String) paramInfo.getOptions().get("nameCaseType")) {
case ParamInfo.NAME_CASE_TYPE.CAMEL_CASE -> StringUtilsPlus.toLowerCamel(columnName);
case ParamInfo.NAME_CASE_TYPE.UNDER_SCORE_CASE -> StringUtilsPlus.toUnderline(columnName, false);
case ParamInfo.NAME_CASE_TYPE.UPPER_UNDER_SCORE_CASE ->
case ParamInfo.NameCaseType.CAMEL_CASE -> StringUtilsPlus.toLowerCamel(columnName);
case ParamInfo.NameCaseType.UNDER_SCORE_CASE -> StringUtilsPlus.toUnderline(columnName, false);
case ParamInfo.NameCaseType.UPPER_UNDER_SCORE_CASE ->
StringUtilsPlus.toUnderline(columnName.toUpperCase(), true);
default -> columnName;
};
@ -218,23 +169,17 @@ public class GeneratorServiceImpl implements GeneratorService {
log.info("classInfo:{}", JSON.toJSONString(classInfo));
return classInfo;
}
/**
* 解析DDL SQL生成类信息(默认模式|核心模式)
*
* @param paramInfo
* @return
*/
@Override
public ClassInfo processTableIntoClassInfo(ParamInfo paramInfo)
throws IOException {
public ClassInfo processTableIntoClassInfo(ParamInfo paramInfo) throws Exception {
//process the param
NonCaseString tableSql = NonCaseString.of(paramInfo.getTableSql());
String tableSql = paramInfo.getTableSql();
String nameCaseType = MapUtil.getString(paramInfo.getOptions(),"nameCaseType");
Boolean isPackageType = MapUtil.getBoolean(paramInfo.getOptions(),"isPackageType");
String isPackageType = MapUtil.getString(paramInfo.getOptions(),"isPackageType");
//更新空值处理
if (StringUtils.isBlank(tableSql)) {
throw new CodeGenerateException("Table structure can not be empty. 表结构不能为空。");
throw new Exception("Table structure can not be empty. 表结构不能为空。");
}
//deal with special character
tableSql = tableSql.trim()
@ -250,9 +195,9 @@ public class GeneratorServiceImpl implements GeneratorService {
String tableName = null;
int tableKwIx = tableSql.indexOf("TABLE"); // 包含判断和位置一次搞定
if (tableKwIx > -1 && tableSql.contains("(")) {
tableName = tableSql.substring(tableKwIx + 5, tableSql.indexOf("(")).get();
tableName = tableSql.substring(tableKwIx + 5, tableSql.indexOf("("));
} else {
throw new CodeGenerateException("Table structure incorrect.表结构不正确。");
throw new Exception("Table structure incorrect.表结构不正确。");
}
//新增处理create table if not exists members情况
@ -288,11 +233,11 @@ public class GeneratorServiceImpl implements GeneratorService {
String classComment = null;
//mysql是comment=,pgsql/oracle是comment on table,
//2020-05-25 优化表备注的获取逻辑
if (tableSql.containsAny("comment=", "comment on table")) {
if (tableSql.contains("comment=") || tableSql.contains("comment on table")) {
int ix = tableSql.lastIndexOf("comment=");
String classCommentTmp = (ix > -1) ?
tableSql.substring(ix + 8).trim().get() :
tableSql.substring(tableSql.lastIndexOf("comment on table") + 17).trim().get();
tableSql.substring(ix + 8).trim() :
tableSql.substring(tableSql.lastIndexOf("comment on table") + 17).trim();
if (classCommentTmp.contains("`")) {
classCommentTmp = classCommentTmp.substring(classCommentTmp.indexOf("`") + 1);
classCommentTmp = classCommentTmp.substring(0, classCommentTmp.indexOf("`"));
@ -311,7 +256,7 @@ public class GeneratorServiceImpl implements GeneratorService {
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
// 正常( ) 内的一定是字段相关的定义
String fieldListTmp = tableSql.substring(tableSql.indexOf("(") + 1, tableSql.lastIndexOf(")")).get();
String fieldListTmp = tableSql.substring(tableSql.indexOf("(") + 1, tableSql.lastIndexOf(")"));
// 匹配 comment替换备注里的小逗号, 防止不小心被当成切割符号切割
String commentPattenStr1 = "comment `(.*?)\\`";
@ -352,17 +297,27 @@ public class GeneratorServiceImpl implements GeneratorService {
int i = 0;
//i为了解决primary key关键字出现的地方出现在前3行一般和id有关
for (String columnLine0 : fieldLineList) {
NonCaseString columnLine = NonCaseString.of(columnLine0);
i++;
columnLine = columnLine.replaceAll("\n", "").replaceAll("\t", "").trim();
String columnLine = columnLine0.replaceAll("\n", "").replaceAll("\t", "").trim();
// `userid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
// 2018-9-18 zhengk 修改为contains提升匹配率和匹配不按照规矩出牌的语句
// 2018-11-8 zhengkai 修复tornadoorz反馈的KEY FK_permission_id (permission_id),KEY FK_role_id (role_id)情况
// 2019-2-22 zhengkai 要在条件中使用复杂的表达式
// 2019-4-29 zhengkai 优化对普通和特殊storage关键字的判断感谢@AhHeadFloating的反馈
// 2020-10-20 zhengkai 优化对fulltext/index关键字的处理感谢@WEGFan的反馈
// 2023-8-27 L&J 改用工具方法判断, 且修改变量名(非特殊标识), 方法抽取
boolean notSpecialFlag = isNotSpecialColumnLine(columnLine, i);
boolean notSpecialFlag = (
!columnLine.contains("key ")
&& !columnLine.contains("constraint")
&& !columnLine.contains(" using ")
&& !columnLine.contains("unique ")
&& !columnLine.contains("fulltext ")
&& !columnLine.contains("index ")
&& !columnLine.contains("pctincrease")
&& !columnLine.contains("buffer_pool")
&& !columnLine.contains("tablespace")
&& !(columnLine.contains("primary ") && columnLine.indexOf("storage") + 3 > columnLine.indexOf("("))
&& !(columnLine.contains("primary ") && i > 3)
);
if (notSpecialFlag) {
//如果是oracle的number(x,x)可能出现最后分割残留的,x)这里做排除处理
@ -374,7 +329,7 @@ public class GeneratorServiceImpl implements GeneratorService {
columnLine = columnLine.replaceAll("`", " ").replaceAll("\"", " ").replaceAll("'", "").replaceAll(" ", " ").trim();
//如果遇到username varchar(65) default '' not null,这种情况判断第一个空格是否比第一个引号前
try {
columnName = columnLine.substring(0, columnLine.indexOf(" ")).get();
columnName = columnLine.substring(0, columnLine.indexOf(" "));
} catch (StringIndexOutOfBoundsException e) {
System.out.println("err happened: " + columnLine);
throw e;
@ -384,19 +339,19 @@ public class GeneratorServiceImpl implements GeneratorService {
// 2019-09-08 yj 添加是否下划线转换为驼峰的判断
// 2023-8-27 L&J 支持原始列名任意命名风格, 不依赖用户是否输入下划线
String fieldName = null;
if (ParamInfo.NAME_CASE_TYPE.CAMEL_CASE.equals(nameCaseType)) {
if (ParamInfo.NameCaseType.CAMEL_CASE.equals(nameCaseType)) {
// 2024-1-27 L&J 适配任意(maybe)原始风格转小写驼峰
fieldName = StringUtilsPlus.toLowerCamel(columnName);
} else if (ParamInfo.NAME_CASE_TYPE.UNDER_SCORE_CASE.equals(nameCaseType)) {
} else if (ParamInfo.NameCaseType.UNDER_SCORE_CASE.equals(nameCaseType)) {
fieldName = StringUtilsPlus.toUnderline(columnName, false);
} else if (ParamInfo.NAME_CASE_TYPE.UPPER_UNDER_SCORE_CASE.equals(nameCaseType)) {
} else if (ParamInfo.NameCaseType.UPPER_UNDER_SCORE_CASE.equals(nameCaseType)) {
fieldName = StringUtilsPlus.toUnderline(columnName.toUpperCase(), true);
} else {
fieldName = columnName;
}
columnLine = columnLine.substring(columnLine.indexOf("`") + 1).trim();
//2025-03-16 修复由于类型大写导致无法转换的问题
String mysqlType = columnLine.split("\\s+")[1].toLowerCase(Locale.ROOT);
String mysqlType = columnLine.split("\\s+")[1].toLowerCase();
if(mysqlType.contains("(")){
mysqlType = mysqlType.substring(0, mysqlType.indexOf("("));
}
@ -428,12 +383,12 @@ public class GeneratorServiceImpl implements GeneratorService {
while (columnCommentMatcher.find()) {
String columnCommentTmp = columnCommentMatcher.group();
//System.out.println(columnCommentTmp);
fieldComment = tableSql.substring(tableSql.indexOf(columnCommentTmp) + columnCommentTmp.length()).trim().get();
fieldComment = tableSql.substring(tableSql.indexOf(columnCommentTmp) + columnCommentTmp.length()).trim();
fieldComment = fieldComment.substring(0, fieldComment.indexOf("`")).trim();
}
} else if (columnLine.contains(" comment")) {
//20200518 zhengkai 修复包含comment关键字的问题
String commentTmp = columnLine.substring(columnLine.lastIndexOf("comment") + 7).trim().get();
String commentTmp = columnLine.substring(columnLine.lastIndexOf("comment") + 7).trim();
// '用户ID',
if (commentTmp.contains("`") || commentTmp.indexOf("`") != commentTmp.lastIndexOf("`")) {
commentTmp = commentTmp.substring(commentTmp.indexOf("`") + 1, commentTmp.lastIndexOf("`"));
@ -462,7 +417,7 @@ public class GeneratorServiceImpl implements GeneratorService {
}
if (fieldList.size() < 1) {
throw new CodeGenerateException("表结构分析失败请检查语句或者提交issue给我");
throw new Exception("表结构分析失败请检查语句或者提交issue给我");
}
ClassInfo codeJavaInfo = new ClassInfo();
@ -475,63 +430,7 @@ public class GeneratorServiceImpl implements GeneratorService {
return codeJavaInfo;
}
private static boolean isNotSpecialColumnLine(NonCaseString columnLine, int lineSeq) {
return (
!columnLine.containsAny(
"key ",
"constraint",
" using ",
"unique ",
"fulltext ",
"index ",
"pctincrease",
"buffer_pool",
"tablespace"
)
&& !(columnLine.contains("primary ") && columnLine.indexOf("storage") + 3 > columnLine.indexOf("("))
&& !(columnLine.contains("primary ") && lineSeq > 3)
);
}
/**
* 解析JSON生成类信息
*
* @param paramInfo
* @return
*/
@Override
public ClassInfo processJsonToClassInfo(ParamInfo paramInfo) {
ClassInfo codeJavaInfo = new ClassInfo();
codeJavaInfo.setTableName("JsonDto");
codeJavaInfo.setClassName("JsonDto");
codeJavaInfo.setClassComment("JsonDto");
//support children json if forget to add '{' in front of json
if (paramInfo.getTableSql().trim().startsWith("\"")) {
paramInfo.setTableSql("{" + paramInfo.getTableSql());
}
if (JSON.isValid(paramInfo.getTableSql())) {
if (paramInfo.getTableSql().trim().startsWith("{")) {
JSONObject jsonObject = JSONObject.parseObject(paramInfo.getTableSql().trim());
//parse FieldList by JSONObject
codeJavaInfo.setFieldList(processJsonObjectToFieldList(jsonObject));
} else if (paramInfo.getTableSql().trim().startsWith("[")) {
JSONArray jsonArray = JSONArray.parseArray(paramInfo.getTableSql().trim());
//parse FieldList by JSONObject
codeJavaInfo.setFieldList(processJsonObjectToFieldList(jsonArray.getJSONObject(0)));
}
}
return codeJavaInfo;
}
/**
* parse SQL by regex
*
* @param paramInfo
* @return
* @author https://github.com/ydq
*/
public ClassInfo processTableToClassInfoByRegex(ParamInfo paramInfo) {
// field List
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
@ -548,7 +447,7 @@ public class GeneratorServiceImpl implements GeneratorService {
Pattern COL_PATTERN = Pattern.compile(COL_PATTERN_STR, Pattern.CASE_INSENSITIVE);
Matcher matcher = DDL_PATTERN.matcher(paramInfo.getTableSql().trim());
Matcher matcher = Pattern.compile(DDL_PATTEN_STR).matcher(paramInfo.getTableSql().trim());
if (matcher.find()) {
String tableName = matcher.group("tableName");
String tableComment = matcher.group("tableComment");
@ -577,42 +476,7 @@ public class GeneratorServiceImpl implements GeneratorService {
return codeJavaInfo;
}
public List<FieldInfo> processJsonObjectToFieldList(JSONObject jsonObject) {
// field List
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
jsonObject.keySet().stream().forEach(jsonField -> {
FieldInfo fieldInfo = new FieldInfo();
fieldInfo.setFieldName(jsonField);
fieldInfo.setColumnName(jsonField);
fieldInfo.setFieldClass(String.class.getSimpleName());
fieldInfo.setFieldComment("father:" + jsonField);
fieldList.add(fieldInfo);
if (jsonObject.get(jsonField) instanceof JSONArray) {
jsonObject.getJSONArray(jsonField).stream().forEach(arrayObject -> {
FieldInfo fieldInfo2 = new FieldInfo();
fieldInfo2.setFieldName(arrayObject.toString());
fieldInfo2.setColumnName(arrayObject.toString());
fieldInfo2.setFieldClass(String.class.getSimpleName());
fieldInfo2.setFieldComment("children:" + arrayObject.toString());
fieldList.add(fieldInfo2);
});
} else if (jsonObject.get(jsonField) instanceof JSONObject) {
jsonObject.getJSONObject(jsonField).keySet().stream().forEach(arrayObject -> {
FieldInfo fieldInfo2 = new FieldInfo();
fieldInfo2.setFieldName(arrayObject.toString());
fieldInfo2.setColumnName(arrayObject.toString());
fieldInfo2.setFieldClass(String.class.getSimpleName());
fieldInfo2.setFieldComment("children:" + arrayObject.toString());
fieldList.add(fieldInfo2);
});
}
});
if (fieldList.size() < 1) {
throw new CodeGenerateException("JSON解析失败");
}
return fieldList;
}
@Override
public ClassInfo processInsertSqlToClassInfo(ParamInfo paramInfo) {
// field List
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
@ -645,12 +509,14 @@ public class GeneratorServiceImpl implements GeneratorService {
List<String> valueList = new ArrayList<>();
//add values as comment
Arrays.stream(valueStr.split(",")).forEach(column -> {
String[] values = valueStr.split(",");
for (String column : values) {
valueList.add(column);
});
}
AtomicInteger n = new AtomicInteger(0);
//add column to fleldList
Arrays.stream(columnsSQL.replaceAll(" ", "").split(",")).forEach(column -> {
String[] columns = columnsSQL.replaceAll(" ", "").split(",");
for (String column : columns) {
FieldInfo fieldInfo2 = new FieldInfo();
fieldInfo2.setFieldName(column);
fieldInfo2.setColumnName(column);
@ -660,14 +526,13 @@ public class GeneratorServiceImpl implements GeneratorService {
}
fieldList.add(fieldInfo2);
n.getAndIncrement();
});
}
}
if (fieldList.size() < 1) {
throw new CodeGenerateException("INSERT SQL解析失败");
throw new RuntimeException("INSERT SQL解析失败");
}
codeJavaInfo.setFieldList(fieldList);
return codeJavaInfo;
}
}
}

View File

@ -0,0 +1,20 @@
package com.softdev.system.generator.service.parser;
import com.softdev.system.generator.entity.dto.ClassInfo;
import com.softdev.system.generator.entity.dto.ParamInfo;
/**
* JSON解析服务接口
*
* @author zhengkai.blog.csdn.net
*/
public interface JsonParserService {
/**
* 解析JSON生成类信息
*
* @param paramInfo 参数信息
* @return 类信息
*/
ClassInfo processJsonToClassInfo(ParamInfo paramInfo);
}

View File

@ -0,0 +1,55 @@
package com.softdev.system.generator.service.parser;
import com.softdev.system.generator.entity.dto.ClassInfo;
import com.softdev.system.generator.entity.dto.ParamInfo;
/**
* SQL解析服务接口
*
* @author zhengkai.blog.csdn.net
*/
public interface SqlParserService {
/**
* 解析Select-SQL生成类信息(JSQLPraser版本)
*
* @param paramInfo 参数信息
* @return 类信息
* @throws Exception 解析异常
*/
ClassInfo generateSelectSqlBySQLPraser(ParamInfo paramInfo) throws Exception;
/**
* 解析Create-SQL生成类信息(JSQLPraser版本)
*
* @param paramInfo 参数信息
* @return 类信息
* @throws Exception 解析异常
*/
ClassInfo generateCreateSqlBySQLPraser(ParamInfo paramInfo) throws Exception;
/**
* 解析DDL-SQL生成类信息
*
* @param paramInfo 参数信息
* @return 类信息
* @throws Exception 解析异常
*/
ClassInfo processTableIntoClassInfo(ParamInfo paramInfo) throws Exception;
/**
* 解析DDL SQL生成类信息-正则表达式版本
*
* @param paramInfo 参数信息
* @return 类信息
*/
ClassInfo processTableToClassInfoByRegex(ParamInfo paramInfo);
/**
* 解析INSERT-SQL生成类信息-正则表达式版本
*
* @param paramInfo 参数信息
* @return 类信息
*/
ClassInfo processInsertSqlToClassInfo(ParamInfo paramInfo);
}

View File

@ -1,28 +0,0 @@
package com.softdev.system.generator.util;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.softdev.system.generator.entity.ClassInfo;
import com.softdev.system.generator.entity.FieldInfo;
import com.softdev.system.generator.entity.NonCaseString;
import com.softdev.system.generator.entity.ParamInfo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 表格解析Util
*
* @author zhengkai.blog.csdn.net
*/
public class TableParseUtil {
}

View File

@ -0,0 +1,28 @@
package com.softdev.system.generator.util.exception;
/**
* 代码生成异常
*
* @author zhengkai.blog.csdn.net
*/
public class CodeGenException extends RuntimeException {
public CodeGenException() {
}
public CodeGenException(String message) {
super(message);
}
public CodeGenException(String message, Throwable cause) {
super(message, cause);
}
public CodeGenException(Throwable cause) {
super(cause);
}
public CodeGenException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,28 @@
package com.softdev.system.generator.util.exception;
/**
* SQL解析异常
*
* @author zhengkai.blog.csdn.net
*/
public class SqlParseException extends CodeGenException {
public SqlParseException() {
}
public SqlParseException(String message) {
super(message);
}
public SqlParseException(String message, Throwable cause) {
super(message, cause);
}
public SqlParseException(Throwable cause) {
super(cause);
}
public SqlParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -130,12 +130,13 @@ const vm = new Vue({
}
setAllCookie();
//console.log(res.outputJson);
vm.outputJson = res.data.data;
//兼容后端返回数据格式
if(res.data){
vm.outputJson = res.data.outputJson;
}else {
vm.outputJson = res.outputJson;
}
// if(res.data){
// vm.outputJson = res.data.outputJson;
// }else {
// vm.outputJson = res.outputJson;
// }
// console.log(vm.outputJson["bootstrap-ui"]);
vm.outputStr=vm.outputJson[vm.currentSelect].trim();
@ -159,13 +160,15 @@ const vm = new Vue({
}).then(function(res){
//console.log(res.templates);
// vm.templates = JSON.parse(res.templates);
// console.log(res);
console.log('origin res',res);
vm.templates = res.data.data
console.log('templates',vm.templates);
//兼容后端返回数据格式
if(res.data){
vm.templates = res.data.templates;
}else {
vm.templates = res.templates;
}
// if(res.data){
// vm.templates = res.data.templates;
// }else {
// vm.templates = res.templates;
// }
});
},
updated: function () {

View File

@ -1,236 +0,0 @@
//iframe自适应
$(window).on('resize', function() {
const $content = $('.content');
$content.height($(this).height() - 154);
$content.find('iframe').each(function() {
$(this).height($content.height());
});
}).resize();
const vm = new Vue({
el: '#rrapp',
data: {
main: "main",
},
methods: {
donate: function () {
}
},
created: function () {
},
updated: function () {
}
});
$.inputArea = undefined;
$.outputArea = undefined;
$(function(){
//powered by zhengkai.blog.csdn.net
//init input code area
$.inputArea = CodeMirror.fromTextArea(document.getElementById("inputArea"), {
mode: "text/x-sql", // SQL
theme: "idea", // IDEA主题
lineNumbers: true, //显示行号
smartIndent: true, // 自动缩进
autoCloseBrackets: true// 自动补全括号
});
$.inputArea.setSize('auto','auto');
// init output code area
$.outputArea = CodeMirror.fromTextArea(document.getElementById("outputArea"), {
mode: "text/x-java", // JAV
theme: "idea", // IDEA主题
lineNumbers: true, //显示行号
smartIndent: true, // 自动缩进
autoCloseBrackets: true// 自动补全括号
});
$.outputArea.setSize('auto','auto');
});
const vm = new Vue({
el: '#rrapp',
data: {
formData: {
tableSql: "CREATE TABLE 'sys_user_info' (\n" +
" 'user_id' int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',\n" +
" 'user_name' varchar(255) NOT NULL COMMENT '用户名',\n" +
" 'status' tinyint(1) NOT NULL COMMENT '状态',\n" +
" 'create_time' datetime NOT NULL COMMENT '创建时间',\n" +
//下面可以留着方便开发调试时打开
// " `updateTime` datetime NOT NULL COMMENT '更新时间',\n" +
// " ABc_under_Line-Hypen-CamelCase varchar comment '乱七八糟的命名风格',\n" +
" PRIMARY KEY ('user_id')\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息'",
options: {
dataType: "sql",
authorName: "${(value.author)!!}",
packageName: "${(value.packageName)!!}",
returnUtilSuccess: "${(value.returnUtilSuccess)!!}",
returnUtilFailure: "${(value.returnUtilFailure)!!}",
isPackageType: true,
isSwagger: false,
isAutoImport: false,
isWithPackage: false,
isComment: true,
isLombok: true,
ignorePrefix:"sys_",
tinyintTransType: "int",
nameCaseType: "CamelCase",
timeTransType: "Date"
}
},
templates:[{}],
historicalData:[],
currentSelect:'plusentity',
outputStr: "${(value.outputStr)!!}",
outputJson: {}
},
methods: {
//set the template for output 选择页面输出的模板类型
setOutputModel: function (event) {
const targetModel = event.target.innerText.trim();
console.log(targetModel);
vm.currentSelect = targetModel ;
if(vm.outputStr.length>30){
vm.outputStr=vm.outputJson[targetModel];
$.outputArea.setValue(vm.outputStr.trim());
//console.log(vm.outputStr);
$.outputArea.setSize('auto', 'auto');
}
},
//switch HistoricalData
switchHistoricalData: function (event) {
const tableName = event.target.innerText.trim();
console.log(tableName);
if (window.sessionStorage){
const valueSession = sessionStorage.getItem(tableName);
vm.outputJson = JSON.parse(valueSession);
console.log(valueSession);
alert("切换历史记录成功:"+tableName);
}else{
alert("浏览器不支持sessionStorage");
}
vm.outputStr=vm.outputJson[vm.currentSelect].trim();
$.outputArea.setValue(vm.outputStr);
//console.log(vm.outputStr);
$.outputArea.setSize('auto', 'auto');
},
setHistoricalData : function (tableName){
//add new table only
if(vm.historicalData.indexOf(tableName)<0){
vm.historicalData.unshift(tableName);
}
//remove last record , if more than N
if(vm.historicalData.length>9){
vm.historicalData.splice(9,1);
}
//get and set to session data
const valueSession = sessionStorage.getItem(tableName);
//remove if exists
if(valueSession!==undefined && valueSession!=null){
sessionStorage.removeItem(tableName);
}
//set data to session
sessionStorage.setItem(tableName,JSON.stringify(vm.outputJson));
//console.log(vm.historicalData);
},
//request with formData to generate the code 根据参数生成代码
generate : function(){
//get value from codemirror
vm.formData.tableSql=$.inputArea.getValue();
axios.post(basePath+"/code/generate",vm.formData).then(function(res){
if(res.code===500){
error("生成失败请检查SQL语句!!!");
return;
}
setAllCookie();
//console.log(res.outputJson);
//兼容后端返回数据格式
if(res.data){
vm.outputJson = res.data.outputJson;
}else {
vm.outputJson = res.outputJson;
}
// console.log(vm.outputJson["bootstrap-ui"]);
vm.outputStr=vm.outputJson[vm.currentSelect].trim();
//console.log(vm.outputJson["bootstrap-ui"]);
//console.log(vm.outputStr);
$.outputArea.setValue(vm.outputStr);
$.outputArea.setSize('auto', 'auto');
//add to historicalData
vm.setHistoricalData(vm.outputJson.tableName);
alert("生成成功");
});
},
copy : function (){
navigator.clipboard.writeText(vm.outputStr.trim()).then(r => {alert("已复制")});
}
},
created: function () {
//load all templates for selections 加载所有模板供选择
axios.post(basePath+"/template/all",{
id:1234
}).then(function(res){
//console.log(res.templates);
// vm.templates = JSON.parse(res.templates);
// console.log(res);
//兼容后端返回数据格式
if(res.data){
vm.templates = res.data.templates;
}else {
vm.templates = res.templates;
}
});
},
updated: function () {
}
});
/**
* 将所有 需要 保留历史纪录的字段写入Cookie中
*/
function setAllCookie() {
var arr = list_key_need_load();
for (var str of arr){
setOneCookie(str);
}
}
function setOneCookie(key) {
setCookie(key, vm.formData.options[key]);
}
/**
* 将所有 历史纪录 重加载回页面
*/
function loadAllCookie() {
//console.log(vm);
var arr = list_key_need_load();
for (var str of arr){
loadOneCookie(str);
}
}
function loadOneCookie(key) {
if (getCookie(key)!==""){
vm.formData.options[key] = getCookie(key);
}
}
/**
* 所有 需要 纪录的 字段写入数组
* @returns {[string]}
*/
function list_key_need_load() {
return ["authorName","packageName","returnUtilSuccess","returnUtilFailure","ignorePrefix","tinyintTransType","timeTransType"];
}

20
pom.xml
View File

@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.5</version>
<version>3.5.8</version>
</parent>
<modules>
@ -70,27 +70,21 @@
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.58</version>
<version>2.0.60</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension</artifactId>
<version>2.0.58</version>
<version>2.0.60</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2-extension-spring6 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension-spring6</artifactId>
<version>2.0.58</version>
<version>2.0.59</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 支持 @ConfigurationProperties 注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -117,7 +111,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.40</version>
<version>1.18.42</version>
</dependency>
<!-- freemarker -->
@ -136,7 +130,7 @@
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
<version>4.0.4</version>
</dependency>