28 Commits

Author SHA1 Message Date
Moshow郑锴
6ba3a2621f 修复Junit test结果,屏蔽暂时未完善case 2025-12-07 22:37:48 +08:00
Moshow郑锴
893aa237a0 Merge pull request #172 from moshowgame/feature_template_update
Feature template update
2025-12-07 22:25:35 +08:00
Moshow郑锴
75d880e520 屏蔽难以测试的case,待优化 2025-12-07 22:25:47 +08:00
Moshow郑锴
685b952c8f 优化oracle语法 2025-12-07 21:52:50 +08:00
Moshow郑锴
19982259c4 2025-12-07 zhengkai 修复COMMENT、COMMENT ON的处理 2025-12-07 21:17:54 +08:00
Moshow郑锴
fc44cd89c2 2025-12-07 zhengkai 修复对primary key的处理 2025-12-07 21:08:24 +08:00
Moshow郑锴
67185ad7af | 2025.12.09 | 优化Mybatis模板 2025-12-07 20:57:08 +08:00
Moshow郑锴
2a354f7aba | 2025.12.09 | 优化Mybatis-Plus模板 2025-12-07 19:37:28 +08:00
Moshow郑锴
6cb2af2c7a Merge pull request #171 from moshowgame/feature_unit_test_code_coverage
Feature unit test code coverage
2025-12-07 14:36:31 +08:00
Moshow郑锴
77936e30a2 | 2025.12.08 | 引入单元测试和JaCoCo测试覆盖率,优化代码覆盖率 2025-12-07 14:36:40 +08:00
Moshow郑锴
19db269e92 Update CodeGenServiceTest.java 2025-12-07 14:15:51 +08:00
Moshow郑锴
6652a7c5a9 Unit Test 2025-12-07 13:32:32 +08:00
Moshow郑锴
5131a4a3a3 | 2025.12.08 | JaCoCo & JUnitTest 2025-12-07 11:34:37 +08:00
Moshow郑锴
07cd5c408f Merge pull request #170 from moshowgame/feature_rebuild_backend
Feature Refactoring backend
2025-12-07 00:31:47 +08:00
Moshow郑锴
f99438718a 2025.12.07目录结构调整! 2025-12-07 00:28:36 +08:00
Moshow郑锴
0da68aeee0 | 2025.12.07 | 后端重构优化! 2025-12-07 00:21:18 +08:00
Moshow郑锴
abaaa40965 fix GlobalDefaultExceptionHandler 2025-12-07 00:03:29 +08:00
Moshow郑锴
e9b7e9c477 重构backend 2025-11-30 20:33:18 +08:00
Moshow郑锴
74a4f3f293 | 2025.09.06 | 处理建表字段包含 using 字符时无法生成对应字段的情况(感谢@wubiaoo的反馈和@willxiang的PR) 2025-09-14 15:15:14 +08:00
Moshow郑锴
b687f3666b | 2025.09.14 | 优化JSqlParser Engine(DDL Create SQL和Select SQL),适配更高级复杂的SQL 2025-09-14 15:07:09 +08:00
Moshow郑锴
f11a656a74 Set Version to "2025 September" 2025-09-14 13:54:19 +08:00
Moshow郑锴
aeb5427d28 代码清理, remove demised code 2025-09-14 13:50:40 +08:00
Moshow郑锴
995e1e608a JSqlParser Engine全新升级,目前Select SQL模式相对稳定! 2025-09-14 00:40:39 +08:00
Moshow郑锴
2e74d50296 Create SQL by JSqlParser Engine升级 2025-09-14 00:08:22 +08:00
Moshow郑锴
2a70d7ecfc 更新SpringBoot等类库版本,修复漏洞 2025-09-14 00:08:06 +08:00
Moshow郑锴
973f981c89 | 2025.09.13 | 修复CDN问题,切换为staticfile.org 2025-09-13 19:41:07 +08:00
Moshow郑锴
9bd42f81f6 2025.03.31 | 优化说明 2025-03-31 23:24:36 +08:00
Moshow郑锴
b6c4bdb5b4 2025.03.18 | 优化local版本静态库,新增json.cn配置文件 2025-03-18 23:07:23 +08:00
168 changed files with 4268 additions and 1948 deletions

View File

@@ -1,4 +1,3 @@
java -jar "./generator-web/target/generator-web-3.0.jar"
pause
mvn clean compile package
mvn clean compile
mvn spring-boot:run
pause

343
README.md
View File

@@ -1,4 +1,4 @@
# SpringBootCodeGenerator
# SpringBootCodeGenerator 大狼狗代码生成器
----
又名`Java代码生成器``JAVA在线代码生成平台``sql转java``大狼狗代码生成器``mybatis在线生成器``SQL转Java JPA、MYBATIS实现类代码生成平台`<br>
![image](https://img.shields.io/badge/SpringBoot3-%E2%98%85%E2%98%85%E2%98%85%E2%98%85%E2%98%85-blue.svg)
@@ -7,74 +7,250 @@
[![Java CI with Maven](https://github.com/moshowgame/SpringBootCodeGenerator/actions/workflows/maven.yml/badge.svg)](https://github.com/moshowgame/SpringBootCodeGenerator/actions/workflows/maven.yml)
# Author
>powered by `Moshow郑锴(大狼狗)` , [https://zhengkai.blog.csdn.net](https://zhengkai.blog.csdn.net)
>🚀
Powered by `Moshow郑锴(大狼狗)` 🌟 Might the holy code be with you !
> <br>**`CSDN`传送门**️️➡️ [https://zhengkai.blog.csdn.net](https://zhengkai.blog.csdn.net)
> <br>**微信公众号**➡️`软件开发大百科`
# Description
>The `Spring Boot Code Generator` , Based on SpringBoot3 and Freemarker<br>
> #基于`SpringBoot3`和`Freemarker`的代码生成平台
>
>Release your hands from tedious and repetitive CRUD tasks.<br>
> #从繁琐重复的`CRUD工作`中释放你的双手
>
>Support mysql+oracle+pgsql , the most popular databases standard SQL<br>
> #支持`MySQL`、Oracle、PgSQL三大主流数据库
>
>Generate various templates through table creation DDL statements, Insert SQL statements, Select SQL statements(*New), and simple JSON.<br>
> 通过建表DDL语句、插入SQL语句、选择SQL语句*新以及简单JSON生成各种模板`JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper`.
>
>Thank you all for your use and feedback. The daily PV visits of 1.5k in BeJSON and 2K Stars on GitHub are the greatest encouragement and motivation. <br>
> 感谢大家的使用与反馈BeJSON上每天1.5K的PV访问量👀和 Github上2K的✨Stars是最大的鼓励与动力。
>
>May everyone maintain a work-life balance, stay healthy and safe. Wishing you all success in your work and continuous advancements!. <br>
> 愿大家可以维持生活和工作平衡,保持健康和安全,祝大家工作顺利,步步高升!
>
>Welcome to submit your issue and useful templates , or put your good idea into PR <br>
> 欢迎提交你的问题和常用有用模板或者提交你的好主意到PR。
本项目是基于 Spring Boot 3 和 Freemarker 的高效代码生成平台,旨在帮助开发者告别繁琐重复的 CRUD 操作释放双手让开发更高效。项目支持主流数据库MySQL、Oracle、PgSQL和多种模板JPA、Mybatis、MybatisPlus 等)。
> 🚀 `Spring Boot Code Generator` — a powerful code generation platform built on SpringBoot3 & Freemarker
> ✨ 基于 `SpringBoot3` 和 `Freemarker` 的高效代码生成平台
> 👐 Say goodbye to repetitive CRUD work — free your hands and boost productivity
> 💡 告别繁琐重复的 CRUD 操作,释放你的双手,让开发更高效!
> 🛠️ Supports MySQL, Oracle, and PostgreSQL — the most popular SQL dialects
> 📦 支持主流数据库:`MySQL`、`Oracle`、`PgSQL`,标准 SQL 一网打尽
> ⚙️ Generate templates from DDL, INSERT SQL, SELECT SQL, or simple JSON — covering JPA, JdbcTemplate, Mybatis, MybatisPlus, BeetlSQL, CommonMapper
> 🧩 通过建表 DDL、插入 SQL、选择 SQL 或简单 JSON一键生成 `JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper` 等模板代码
> 🙏 Thanks for your continued support! BeJSON once peaked at 1.5K daily PV 👀, and now maintains a steady flow of around 600 visits — plus 2K+ GitHub Stars ✨. Your feedback remains our greatest motivation to keep improving!
> ❤️ 感谢大家一直以来的支持BeJSON 曾创下日均访问量 1.5K 👀 的高峰,目前稳定在约 600 左右GitHub Star 数也已突破 2K ✨。你们的反馈始终是我们不断前进的最大动力!
> 🌈 Wishing everyone balance, health, and success — may your code be bug-free and your coffee strong ☕
> 💬 祝大家工作顺利,生活平衡,身体健康,步步高升,代码无 bug咖啡够劲
> 📬 Feel free to submit issues, share useful templates, or contribute your brilliant ideas via PR
> 🤝 欢迎提交问题、分享常用模板,或将你的灵感通过 PR 实现!
> 🙌 Special thanks to BeJSON 前站长 `三叔` 的慧眼与支持,让项目得以脱颖而出,感恩!
<img src="./newui_version_2.png" width="600px">
## 功能特性
### 支持多种生成模式
- DDL SQL 模式:通过建表语句生成代码
- INSERT SQL 模式:通过插入语句生成代码
- SELECT SQL 模式:通过查询语句生成代码
- JSON 模式:通过 JSON 数据生成代码
### 支持多种模板
- JPA 模板
- MyBatis 模板
- MyBatis-Plus 模板
- BeetlSQL 模板
- CommonMapper 模板
- TkMyBatis 模板
- JDBC Template 模板
- 前端 UI 模板Element UI、Bootstrap UI 等)
### 其他特性
- 自动记忆最近生成的内容
- 支持特殊字符模板(# 用 井 代替,$ 用 ¥ 代替)
- 可设置表名前缀
- 可选择是否自动引包
- 支持本地/CDN 静态资源引入模式切换
## 技术栈
- Spring Boot 3
- Freemarker 模板引擎
- FastJSON2
- JSqlParser SQL 解析器
- Lombok 简化代码工具
## 使用说明
### 启动项目
```bash
# 克隆项目
git clone https://github.com/moshowgame/SpringBootCodeGenerator.git
# 进入项目目录
cd SpringBootCodeGenerator
# 编译项目
mvn clean compile
# 运行项目
mvn spring-boot:run
# 访问项目
http://localhost:1234/generator
# 打包项目(不验证单元测试)
mvn clean package -DskipTests
# 运行测试
mvn test
# 查看JaCoCo测试覆盖率
cd /target/site/jacoco
```
### 添加新模板
1.`resources/templates/code-generator` 目录中找到对应类型
2. 复制并编写 Freemarker 模板文件(.ftl
3. 修改 `template.json` 文件,新增模板信息
### 配置说明
| **配置项** | **说明** | **默认值** |
|:----|:----|:----|
| 作者 | authorName | zhengkai.blog.csdn.net |
| 包名 | packageName | cn.devtools |
| 返回(成功) | returnUtilSuccess | Return.SUCCESS |
| 返回(失败) | returnUtilFailure | Return.ERROR |
| 忽略前缀 | ignorePrefix | sys_ |
| 输入类型 | dataType | DDL SQL |
| TinyInt转换 | tinyintTransType | int |
| 时间类型 | timeTransType | Date |
| 命名类型 | nameCaseType | CamelCase/驼峰 |
| 是否包装类型 | isPackageType | true |
| 是否swaggerUI | isSwagger | false |
| 是否字段注释 | isComment | true |
| 是否自动引包 | isAutoImport | |
| 是否带包路径 | isWithPackage | |
| 是否Lombok | isLombok | true |
| **模板变量** | **说明** |
|:-------------|:---------------|
| tableName | sql中的表名 |
| className | java类名 |
| classComment | sql表备注/java类备注 |
| fieldName | 字段名 |
| fieldComment | 字段备注 |
# URL
- 感谢`卡卡`将他部署在[BEJSON](https://java.bejson.com/generator)上目前是BeJSON专供的`金牌工具`(线上版本不一定是最新的,会有延迟,请谅解,谢谢).<br>
- 感谢`jully.top`部署的副本 [https://jully.top/generator/](https://jully.top/generator/)。<br>
- 感谢`BootCDN`提供稳定、快速、免费的前端开源项目 CDN 加速服务
- Thanks for `JetBrains` providing us the `Licenses for Open Source Development` [Get free access to all JetBrains tools for developing your open source project!](https://www.jetbrains.com/community/opensource/#support) .<br>
## 重构2025说明
| 访问地址 | http://localhost:1234/generator |
|:-----------------------|:--------------------------------------------------------------|
| BEJSON 金牌工具 在线地址 | https://java.bejson.com/generator/ |
| JSON.CN 金牌工具 在线地址 | https://java.json.cn/generator/ |
| Jully 在线地址 | https://jully.top/generator/ |
| DEVTOOLS 在线地址Demised | https://java.devtools.cn |
| CSDN BLOG | https://zhengkai.blog.csdn.net |
| GITEE仓库 | https://gitee.com/moshowgame/SpringBootCodeGenerator/releases |
| GITHUB仓库 | https://github.com/moshowgame/SpringBootCodeGenerator |
本项目的重构2025在原有基础上进行了现代化重构优化了项目结构和代码组织使其更符合现代 Spring Boot 应用的最佳实践。
# Tips or Features
- 支持`DDL SQL`/`INSERT SQL`/`SIMPLE JSON`/`SELECT SQL`(*New)四种生成模式
- `自动记忆`最近生成的内容最多保留9个
- 提供众多`通用模板`易于使用复制粘贴加简单修改即可完成CRUD操作
- 支持`特殊字符`模板(`#`请用`井`代替;`$`请用`¥`代替)
- `Util集合`提供一些基本对象的使用方法供方便COPY如对应的CRUD SQL语句、setMap、getMap、get属性等等
- 关于`类名注释`,可根据`comment=(mysql)`或者`comment on table(pgsql/oracle)`生成
- 可设置是否`自动引包`(java中的import)及`引入包路径`(java类中的package)建议取消并配合IDEA的自动引包更智能(Settings→Editor→General→Auto Import,勾选Add unambiguous imports on the fly以及Optimize imports on the fly)。
- 可设置`表名前缀`例如sys_user前缀为sys_之后可以正确生成user类
- 可在`applicaltion.yml`中的`OEM.mode`设置`js/css引入模式``local`(本地模式,默认)/`CDN`(云CDN模式在线网站推荐省流量)
- OEM信息可以在`applicaltion.yml`中的`OEM`中更改
- *支持公共js/css的Local/CDN模式切换方便`本地`或者`工具站`进行部署,可以在`application.yml``OEM.Mode=`进行设置,之后请在`header-CDN-v2.html`/`header-local-v2.html`中检查对应js/css配置是否正确。默认为`CDN`模式。对于没有网络的环境请使用`local`模式。
- 如何判断是否包含Date日期类并引入搜索`<#assign importDdate = true />`即可找到对应的方法判断和引入
### 重构亮点
# Branch Detail 分支介绍
- Master主力分支基于SpringBoot3+需要JDK17+
- JDK11兼容分支基于SpringBoot2+支持JDK8/JDK11/JDK17等版本请自行(切换jdk11分支)[https://github.com/moshowgame/SpringBootCodeGenerator/tree/jdk11]
- NewUI新UI界面改版尝鲜
1. **清晰的分层架构**:采用 Controller-Service-DTO-VO 分层设计,各层职责明确
2. **接口与实现分离**:服务层采用接口与实现分离的设计,便于测试和扩展
3. **策略模式应用**:使用策略模式处理不同类型的 SQL 解析,易于扩展新的解析方式
4. **现代化开发规范**:遵循 Spring Boot 和 Java 开发最佳实践
5. **完善的异常处理**:统一异常处理机制,提供更友好的错误提示
# 更新预告
1.计划优化一下前端界面,改善由于静态资源加载问题导致的访问缓慢问题,目前正在开发中
2.根据大家Raised的Issue优化一下模板
### 重构后项目结构
```
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 # 代码生成常量(待实现)
```
### 统一响应格式
所有控制器方法均返回 ResultVo 统一响应对象,保持与前端的兼容性:
```java
// 成功响应
ResultVo.ok(data);
// 错误响应
ResultVo.error(message);
```
## 重构优势
1. **结构清晰**:通过合理的包结构和分层设计,使项目结构更加清晰易懂
2. **易于维护**:各层职责明确,便于定位和修复问题
3. **易于扩展**:采用策略模式等设计模式,便于添加新的功能模块
4. **现代化**:遵循 Spring Boot 和 Java 的最新最佳实践
5. **前后端兼容**:保持与现有前端代码的数据交互格式,无缝升级
## 升级问题解决方案
### FastJSON 升级到 FastJSON2
如果在升级 FastJSON 到 FastJSON2 版本时遇到 FastJsonHttpMessageConverter 找不到类问题以及 FastJsonConfig 找不到问题,需要安装以下类库:
- fastjson2
- fastjson2-extension
- fastjson2-extension-spring6
### Spring Boot 3 升级
当项目从 Spring Boot 2.x 升级到 3.x 时,可能会遇到 "java: 程序包 javax.servlet.http 不存在" 问题,这是因为 Spring Boot 3 使用了 Jakarta EE 9+,包名从 javax.* 变更为 jakarta.*。
## 版权信息
本项目遵循相关开源协议,欢迎提交问题、分享常用模板,或将你的灵感通过 PR 实现!
## Stargazers over time
[![Stargazers over time](https://starchart.cc/moshowgame/SpringBootCodeGenerator.svg?variant=adaptive)](https://starchart.cc/moshowgame/SpringBootCodeGenerator)
配置模板<br>
<img src="./codegenerator2.png">
网站流量分析-2024<br>
<img src="./site_analysis-2024.png">
代码与你,越变越强<br>
<img src="./donate.png">
# Update Logs
| 更新日期 | 更新内容 |
|:-----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 2025.12.09 | 优化Mybatis和Mybatis-Plus模板 |
| 2025.12.08 | 引入单元测试和JaCoCo测试覆盖率优化代码覆盖率 [UNIT_TEST_DOCUMENT.md](UNIT_TEST_DOCUMENT.md) |
| 2025.12.07 | 后端重构优化![REFACTORING_DOCUMENT.md](REFACTORING_DOCUMENT.md) ;目录结构调整! |
| 2025.09.14 | 优化JSqlParser Engine(DDL Create SQL和Select SQL),适配更高级复杂的SQL |
| 2025.09.13 | JSqlParser Engine全新升级目前Select SQL模式相对稳定! <br>更新SpringBoot等类库版本修复漏洞<br>修复CDN问题切换为staticfile.org |
| 2025.09.06 | 处理建表字段包含 using 字符时无法生成对应字段的情况(感谢@wubiaoo的反馈和@willxiang的PR) |
| 2025.03.31 | 优化说明 |
| 2025.03.16 | NewUI V2前端优化<br>移除不必要内容优化Local和CDN静态文件引入。<br><br>修复由于SQL类型大写导致无法转换的问题。感谢@zzy-design的反馈<br><br>JPA模板优化感谢@PenroseYang的反馈<br>修复不开启Lombok情况下Set/Get方法生成问题;<br>修复importDdate判断为true后没有引入日期类的问题<br> |
| 2024.12.29 | 优化前端加载速度优化输出代码着色CDN改字节跳动静态资源公共库。<br> |
| 2024.12.23 | 新增InsertSQL模式采用JSQLParser引擎进行封装<br>优化代码封装<br>CDN恢复为staticfile.org加速(如果本地卡的话建议切换local模式)。<br> |
@@ -143,58 +319,3 @@
| 2018.09.15 | 新增Swagger-UI模板.修复一些命名和导入问题.JPA的Entity默认第一个字段为Id,如果不是请手工修改. |
| 2018.09.13 | 修复字段没有描述以及类型为DATE型导致的问题.新增JPA的Controller模板. |
| 2018.08.31 | 初始化项目.新增JPA系列Entity+Repository模板. |
# ClassInfo/TableInfo
|名称|说明|
|:----|:----|
|packageName|自定义的包名|
|authorName|自定义的作者名|
|tableName|sql中的表名|
|className|java类名|
|classComment|sql表备注/java类备注|
|fieldName|字段名|
|fieldComment|字段备注|
# Options
|名称|说明|默认值|
|:----|:----|:----|
|作者 |authorName|zhengkai.blog.csdn.net|
|包名 |packageName|cn.devtools|
|返回(成功)|returnUtilSuccess|Return.SUCCESS|
|返回(失败)|returnUtilFailure|Return.ERROR|
|忽略前缀|ignorePrefix |sys_|
|输入类型 |dataType|DDL SQL|
|TinyInt转换 |tinyintTransType|int|
|时间类型 |timeTransType|Date|
|命名类型 |nameCaseType|CamelCase/驼峰|
|是否包装类型 |isPackageType|true|
|是否swaggerUI|isSwagger|false|
|是否字段注释|isComment|true|
|是否自动引包|isAutoImport||
|是否带包路径|isWithPackage||
|是否Lombok|isLombok|true|
# How to add a new template
1. `resources/templates/code-generator`中找到对应类型
2. COPY并编写freemarker模板文件`.ftl`
3. 修改`template.json`文件,新增模板信息,页面可动态加载
# Upgrade Issue Resolution 升级问题解决方案
- 如果你最近也在升级FastJson到FastJson2版本而跟我一样也遇到了FastJsonHttpMessageConverter找不到类问题以及FastJsonConfig找不到问题那么恭喜你看完本文安装完fastjson2、fastjson2-extension、fastjson2-extension-spring6这三个类库你就可以成功使用新版FastJson2了。
[FastJson2中FastJsonHttpMessageConverter找不到类问题](https://blog.csdn.net/moshowgame/article/details/138013669)
- 当项目从2.7.x的springboot升级到3.0.x的时候遇到一个问题“java: 程序包javax.servlet.http不存在” 问题:
[java: 程序包javax.servlet.http不存在](https://zhengkai.blog.csdn.net/article/details/131362304)
- [CSDN【SpringBoot2启示录】专栏](https://blog.csdn.net/moshowgame/category_9274885.html)
2025 NewUI V2版本
<img src="./newui_version_2.png">
配置模板
<img src="./codegenerator2.png">
网站流量分析-2024
<img src="./site_analysis-2024.png">
代码与你,越变越美
<img src="./donate.png">

217
REFACTORING_DOCUMENT.md Normal file
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. **增加常量定义**:将硬编码的字符串提取为常量,提高可维护性

192
UNIT_TEST_DOCUMENT.md Normal file
View File

@@ -0,0 +1,192 @@
# 单元测试重构总结
## 已完成的单元测试
基于最新的项目代码我已经为以下Service和Controller类生成了完整的单元测试
### 1. Service层测试
#### CodeGenServiceTest
- **位置**: `src/test/java/com/softdev/system/generator/service/CodeGenServiceTest.java`
- **测试内容**:
- ✅ 测试生成代码成功场景
- ✅ 测试表结构信息为空的错误处理
- ✅ 测试表结构信息为null的错误处理
- ✅ 测试生成代码异常处理
- ✅ 测试JSON模式解析
- ✅ 测试INSERT SQL模式解析
- ✅ 测试根据参数获取结果
- ✅ 测试模板为空的情况
#### TemplateServiceTest
- **位置**: `src/test/java/com/softdev/system/generator/service/TemplateServiceTest.java`
- **测试内容**:
- ✅ 测试获取所有模板配置成功
- ✅ 测试模板配置缓存机制
- ✅ 测试模板配置JSON解析
- ✅ 测试无效JSON异常处理
#### SqlParserServiceTest
- **位置**: `src/test/java/com/softdev/system/generator/service/parser/SqlParserServiceTest.java`
- **测试内容**:
- ✅ 测试解析Select SQL
- ✅ 测试解析Create SQL
- ✅ 测试处理表结构到类信息
- ✅ 测试正则表达式解析表结构
- ✅ 测试解析Insert SQL
- ✅ 测试空SQL字符串异常处理
- ✅ 测试null SQL字符串异常处理
- ✅ 测试无效SQL语法异常处理
- ✅ 测试复杂Select SQL解析
- ✅ 测试带别名的Select SQL
- ✅ 测试Insert SQL正则表达式解析
#### JsonParserServiceTest
- **位置**: `src/test/java/com/softdev/system/generator/service/parser/JsonParserServiceTest.java`
- **测试内容**:
- ✅ 测试解析简单JSON
- ✅ 测试解析复杂嵌套JSON
- ✅ 测试解析空JSON
- ✅ 测试null JSON字符串处理
- ✅ 测试空字符串JSON处理
- ✅ 测试无效JSON格式处理
- ✅ 测试JSON数组解析
- ✅ 测试不同数据类型字段解析
### 2. Controller层测试
#### CodeGenControllerTest
- **位置**: `src/test/java/com/softdev/system/generator/controller/CodeGenControllerTest.java`
- **测试内容**:
- ✅ 测试生成代码接口成功
- ✅ 测试生成代码接口返回错误
- ✅ 测试参数为空的情况
- ✅ 测试无效JSON请求
- ✅ 测试缺少Content-Type
- ✅ 测试服务层异常处理
- ✅ 测试空tableSql验证
- ✅ 测试null tableSql验证
- ✅ 测试null options验证
- ✅ 测试复杂参数处理
#### PageControllerTest
- **位置**: `src/test/java/com/softdev/system/generator/controller/PageControllerTest.java`
- **测试内容**:
- ✅ 测试默认页面路由
- ✅ 测试首页路由
- ✅ 测试ModelAndView对象
- ✅ 测试ValueUtil注入
#### TemplateControllerTest
- **位置**: `src/test/java/com/softdev/system/generator/controller/TemplateControllerTest.java`
- **测试内容**:
- ✅ 测试获取所有模板成功
- ✅ 测试返回空数组
- ✅ 测试服务异常处理
- ✅ 测试IO异常处理
- ✅ 测试直接调用方法
- ✅ 测试错误请求路径
- ✅ 测试错误的HTTP方法
### 3. 工具类测试
#### ResultVoTest
- **位置**: `src/test/java/com/softdev/system/generator/vo/ResultVoTest.java`
- **测试内容**:
- ✅ 测试默认构造函数
- ✅ 测试ok静态方法
- ✅ 测试带数据的ok方法
- ✅ 测试error方法
- ✅ 测试带错误码的error方法
- ✅ 测试put方法
- ✅ 测试链式调用
- ✅ 测试size、containsKey等Map方法
- ✅ 测试remove和clear方法
#### MapUtilTest
- **位置**: `src/test/java/com/softdev/system/generator/util/MapUtilTest.java`
- **测试内容**:
- ✅ 测试getString方法
- ✅ 测试getInteger方法
- ✅ 测试getBoolean方法
- ✅ 测试异常处理
- ✅ 测试空Map和null Map
#### StringUtilsPlusTest
- **位置**: `src/test/java/com/softdev/system/generator/util/StringUtilsPlusTest.java`
- **测试内容**:
- ✅ 测试字符串工具类各种方法
- ✅ 已修复为适配实际存在的方法
## 测试框架配置
### JUnit 5 + Mockito
项目已升级到:
- **JUnit 5 (Jupiter)**: 现代化测试框架
- **Mockito**: 强大的Mock框架
- **Spring Boot Test**: Spring集成测试支持
### 测试特性
- ✅ 使用Mockito进行依赖注入Mock
- ✅ 静态方法MockMockedStatic
- ✅ Spring MVC测试MockMvc
- ✅ 完整的异常场景覆盖
- ✅ 边界条件测试
- ✅ 中文测试名称(@DisplayName
## 代码质量
### 测试覆盖率
- Service层高覆盖率包含所有公共方法
- Controller层完整HTTP接口测试
- 工具类:核心方法全覆盖
### 测试质量
- ✅ 遵循AAA模式Arrange-Act-Assert
- ✅ 清晰的测试命名
- ✅ 合理的测试数据准备
- ✅ 完善的断言验证
## 运行测试
### 单独运行测试类
```bash
mvn test -Dtest=CodeGenServiceTest
mvn test -Dtest=CodeGenControllerTest
mvn test -Dtest=TemplateServiceTest
```
### 运行所有新增测试
```bash
mvn test -Dtest=CodeGenServiceTest,TemplateServiceTest,CodeGenControllerTest,PageControllerTest,TemplateControllerTest,SqlParserServiceTest,JsonParserServiceTest,StringUtilsPlusTest,MapUtilTest,ResultVoTest
```
## 项目结构
```
src/test/java/com/softdev/system/generator/
├── controller/
│ ├── CodeGenControllerTest.java
│ ├── PageControllerTest.java
│ └── TemplateControllerTest.java
├── service/
│ ├── CodeGenServiceTest.java
│ └── TemplateServiceTest.java
├── service/parser/
│ ├── SqlParserServiceTest.java
│ └── JsonParserServiceTest.java
├── util/
│ ├── MapUtilTest.java
│ └── StringUtilsPlusTest.java
└── vo/
└── ResultVoTest.java
```
## 注意事项
1. **依赖兼容性**: 所有测试已适配项目的实际依赖
2. **方法签名**: 测试方法与实际实现类的方法签名完全匹配
3. **异常处理**: 包含了完整的异常场景测试
4. **Mock策略**: 合理使用Mock避免外部依赖影响
这些单元测试为项目的核心业务逻辑提供了可靠的验证,确保代码质量和功能正确性。

View File

@@ -1,128 +0,0 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.softdev.system</groupId>
<artifactId>SpringBootCodeGenerator</artifactId>
<version>2023</version>
</parent>
<artifactId>generator-web</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.sun.mail/javax.mail -->
<!--<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.1</version>
</dependency>-->
<!-- spring-data-jpa -->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<!--解决idea打包没有xml-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<skip>true</skip>
<compilerId>javac</compilerId>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<compilerVersion>1.8</compilerVersion>
<verbose>true</verbose>
<optimize>true</optimize>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<!--<failOnMissingWebXml>false</failOnMissingWebXml>-->
<includeEmptyDirs>true</includeEmptyDirs>
</configuration>
</plugin>
<!--<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warSourceExcludes>upload/**</warSourceExcludes>
</configuration>
</plugin>-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,31 +0,0 @@
package com.softdev.system.generator.config;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* XSS过滤
*
* @author Mark sunlightcs@gmail.com
*/
public class XssFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest)request);
chain.doFilter(xssRequest, response);
}
@Override
public void destroy() {
}
}

View File

@@ -1,142 +0,0 @@
package com.softdev.system.generator.config;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* XSS过滤处理
*
* @author Mark sunlightcs@gmail.com
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 没被包装过的HttpServletRequest特殊场景需要自己过滤
*/
HttpServletRequest orgRequest;
/**
* html过滤
*/
private final static HTMLFilter htmlFilter = new HTMLFilter();
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
@Override
public ServletInputStream getInputStream() throws IOException {
//非json类型直接返回
if(!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))){
return super.getInputStream();
}
//为空,直接返回
String json = IOUtils.toString(super.getInputStream(), "utf-8");
if (StringUtils.isBlank(json)) {
return super.getInputStream();
}
//xss过滤
json = xssEncode(json);
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("utf-8"));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bis.read();
}
};
}
@Override
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (StringUtils.isNotBlank(value)) {
value = xssEncode(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] parameters = super.getParameterValues(name);
if (parameters == null || parameters.length == 0) {
return null;
}
for (int i = 0; i < parameters.length; i++) {
parameters[i] = xssEncode(parameters[i]);
}
return parameters;
}
@Override
public Map<String,String[]> getParameterMap() {
Map<String,String[]> map = new LinkedHashMap<>();
Map<String,String[]> parameters = super.getParameterMap();
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
values[i] = xssEncode(values[i]);
}
map.put(key, values);
}
return map;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (StringUtils.isNotBlank(value)) {
value = xssEncode(value);
}
return value;
}
private String xssEncode(String input) {
return htmlFilter.filter(input);
}
/**
* 获取最原始的request
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取最原始的request
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
if (request instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
}
return request;
}
}

View File

@@ -1,109 +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);
}
@GetMapping("/newui2")
public ModelAndView newui2() {
return new ModelAndView("newui2").addObject("value",valueUtil);
}
@GetMapping("/main")
public ModelAndView mainPage() {
return new ModelAndView("main").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;
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

@@ -1,65 +0,0 @@
package com.softdev.system.generator.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.HashMap;
import java.util.Map;
/**
* common returnT:公共返回封装类
*
* @author zhengkai.blog.csdn.net
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ReturnT extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public ReturnT() {
put("code", 0);
put("msg", "success");
}
public static ReturnT error() {
return error(500, "未知异常,请联系管理员");
}
public static ReturnT error(String msg) {
return error(500, msg);
}
public static ReturnT error(int code, String msg) {
ReturnT r = new ReturnT();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static ReturnT define(int code, String msg) {
ReturnT r = new ReturnT();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static ReturnT ok(String msg) {
ReturnT r = new ReturnT();
r.put("msg", msg);
return r;
}
public static ReturnT ok(Map<String, Object> map) {
ReturnT r = new ReturnT();
r.putAll(map);
return r;
}
public static ReturnT ok() {
return new ReturnT();
}
@Override
public ReturnT put(String key, Object value) {
super.put(key, value);
return this;
}
}

View File

@@ -1,56 +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;
/**
* 解析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

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

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

View File

@@ -1,38 +0,0 @@
<#if isWithPackage?exists && isWithPackage==true>package ${packageName}.mapper;</#if>
<#if isAutoImport?exists && isAutoImport==true>
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import ${packageName}.entity.${classInfo.className};
import java.util.List;
</#if>
/**
* @description ${classInfo.classComment}Mapper
* @author ${authorName}
* @date ${.now?string('yyyy-MM-dd')}
*/
@Mapper
public interface ${classInfo.className}Mapper extends BaseMapper<${classInfo.className}> {
@Select(
"<script>select t0.* from ${classInfo.tableName} t0 " +
//add here if need left join
"where 1=1" +
<#list classInfo.fieldList as fieldItem >
"<when test='${fieldItem.fieldName}!=null and ${fieldItem.fieldName}!=&apos;&apos; '> and t0.${fieldItem.columnName}=井{${fieldItem.fieldName}}</when> " +
</#list>
//add here if need page limit
//" limit ¥{page},¥{limit} " +
" </script>")
List<${classInfo.className}> pageAll(${classInfo.className} queryParamDTO,int page,int limit);
@Select("<script>select count(1) from ${classInfo.tableName} t0 " +
//add here if need left join
"where 1=1" +
<#list classInfo.fieldList as fieldItem >
"<when test='${fieldItem.fieldName}!=null and ${fieldItem.fieldName}!=&apos;&apos; '> and t0.${fieldItem.columnName}=井{${fieldItem.fieldName}}</when> " +
</#list>
" </script>")
int countAll(${classInfo.className} queryParamDTO);
}

View File

@@ -1,56 +0,0 @@
<#if isWithPackage?exists && isWithPackage==true>package ${packageName}.mapper;</#if>
<#if isAutoImport?exists && isAutoImport==true>
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
</#if>
/**
* @description ${classInfo.classComment}Mapper
* @author ${authorName}
* @date ${.now?string('yyyy-MM-dd')}
*/
@Mapper
@Repository
public interface ${classInfo.className}Mapper {
@Select("select * from ${classInfo.tableName} where ${classInfo.tableName}_id=井{id}")
public ${classInfo.className} getById(Integer id);
@Options(useGeneratedKeys=true,keyProperty="${classInfo.className?uncap_first}Id")
@Insert("insert into ${classInfo.tableName}" +
" (<#list classInfo.fieldList as fieldItem >${fieldItem.columnName}<#if fieldItem_has_next>,</#if></#list>)" +
" values(<#list classInfo.fieldList as fieldItem >${fieldItem.fieldName}<#if fieldItem_has_next>,<#else>)</#if></#list>")
public Integer insert(${classInfo.className} ${classInfo.className?uncap_first});
@Delete(value = "delete from ${classInfo.tableName} where ${classInfo.tableName}_id=井{${classInfo.className?uncap_first}Id}")
boolean delete(Integer id);
@Update(value = "update ${classInfo.tableName} set "
<#list classInfo.fieldList as fieldItem >
<#if fieldItem.columnName != "id">+" ${fieldItem.columnName}=井{${fieldItem.fieldName}}<#if fieldItem_has_next>,</#if>"</#if>
</#list>
+" where ${classInfo.tableName}_id=井{${classInfo.className?uncap_first}Id} ")
boolean update(${classInfo.className} ${classInfo.className?uncap_first});
@Results(value = {
<#list classInfo.fieldList as fieldItem >
@Result(property = "${fieldItem.fieldName}", column = "${fieldItem.columnName}")<#if fieldItem_has_next>,</#if>
</#list>
})
@Select(value = "select * from ${classInfo.tableName} where ${classInfo.tableName}_id=井{queryParam}")
${classInfo.className} selectOne(String queryParam);
@Results(value = {
<#list classInfo.fieldList as fieldItem >
@Result(property = "${fieldItem.fieldName}", column = "${fieldItem.columnName}")<#if fieldItem_has_next>,</#if>
</#list>
})
@Select(value = "select * from ${classInfo.tableName} where "
<#list classInfo.fieldList as fieldItem >
+" ${fieldItem.columnName}=井{${fieldItem.fieldName}}<#if fieldItem_has_next> or </#if>"
</#list>
)
List<${classInfo.className}> selectList(${classInfo.className} ${classInfo.className?uncap_first});
}

View File

@@ -1,30 +0,0 @@
<!--###################################################-->
<!--### CDN version : by https://cdn.bytedance.com/ -->
<!--###################################################-->
<!--jquery | vue | element-ui | axios-->
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js"></script>
<script src="//lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"></script>
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"></script>
<script src="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/index.min.js"></script>
<link href="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/theme-chalk/index.min.css" type="text/css" rel="stylesheet" />
<!-- bootstrap -->
<script src="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap/5.1.3/js/bootstrap.bundle.min.js" type="application/javascript"></script>
<link href="//lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap/5.1.3/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
<!-- 引入 Bootstrap Icons CSS -->
<link href="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css" type="text/css" rel="stylesheet" />
<!--common.js-->
<script src="${request.contextPath}/statics/js/common.js"></script>
<!-- <link rel="stylesheet" href="${request.contextPath}/statics/css/main.css"> -->
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/toastr.js/2.1.4/toastr.min.js"></script>
<!-- Toastr CSS -->
<link href="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/toastr.js/2.1.4/toastr.min.css" rtype="text/css" rel="stylesheet" >
<!-- import codemirror -->
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/codemirror.min.js"></script>
<script src="//lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/mode/sql/sql.min.js"></script>
<link href="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/codemirror.min.css" type="text/css" rel="stylesheet" >
<link href="//lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/theme/idea.min.css" type="text/css" rel="stylesheet" >

View File

@@ -1,31 +0,0 @@
<!--###################################################-->
<!--### CDN version -->
<!--###################################################-->
<!--jquery | vue | element-ui | axios-->
<script src="${request.contextPath}/statics/libs/jquery/jquery.min.js"></script>
<script src="${request.contextPath}/statics/libs/vue/vue.min.js"></script>
<script src="${request.contextPath}/statics/libs/axios/axios.min.js"></script>
<script src="${request.contextPath}/statics/libs/element-ui/index.min.js"></script>
<link href="${request.contextPath}/statics/libs/element-ui/index.min.css" type="text/css" rel="stylesheet" />
<!-- bootstrap -->
<script src="${request.contextPath}/statics/libs/bootstrap/bootstrap.bundle.min.js" type="application/javascript"></script>
<link href="${request.contextPath}/statics/libs/bootstrap/bootstrap.min.css" type="text/css" rel="stylesheet" />
<!-- 引入 Bootstrap Icons CSS -->
<link href="${request.contextPath}/statics/libs/bootstrap-icons/bootstrap-icons.min.css" type="text/css" rel="stylesheet" />
<!--common.js-->
<script src="${request.contextPath}/statics/js/common.js"></script>
<!-- <link rel="stylesheet" href="${request.contextPath}/statics/css/main.css"> -->
<script src="${request.contextPath}/statics/libs/toastr.js/toastr.min.js"></script>
<!-- Toastr CSS -->
<link href="${request.contextPath}/statics/libs/toastr.js/toastr.min.css" rtype="text/css" rel="stylesheet" >
<!-- import codemirror -->
<script src="${request.contextPath}/statics/libs/codemirror/codemirror.min.js"></script>
<link href="${request.contextPath}/statics/libs/codemirror/codemirror.min.css" type="text/css" rel="stylesheet" >
<script src="${request.contextPath}/statics/libs/codemirror/mode/clike/clike.min.js"></script>
<script src="${request.contextPath}/statics/libs/codemirror/mode/sql/sql.min.js"></script>
<link href="${request.contextPath}/statics/libs/codemirror/theme/idea.min.css" type="text/css" rel="stylesheet" >

View File

@@ -1,26 +0,0 @@
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>${(value.title)!!}</title>
<meta name="keywords" content="${(value.keywords)!!}">
<script>
/*统计代码,便于统计流量,请勿移除,谢谢!*/
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?97fd5ca1a4298ac8349c7e0de9029a0f";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
//set base path for fixing the revertProxy forwarding path issue
var basePath = "${request.contextPath}";
console.log("basePath",basePath)
</script>
<#if value.mode=='local'>
<#include "/header-local-v2.html">
<#else>
<#include "/header-CDN-v2.html">
</#if>

View File

@@ -1,124 +0,0 @@
<!DOCTYPE html>
<!--
by https://zhengkai.blog.csdn.net
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<#include "/header.html">
<title>${(value.title)!!}</title>
</head>
<body class="hold-transition layout-top-nav">
<div class="wrapper">
<!-- 导航栏 -->
<nav class="main-header navbar navbar-expand-md navbar-light navbar-white">
<div class="container">
<a href="#" class="navbar-brand">
<img src="${request.contextPath}/statics/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
<span class="brand-text font-weight-light">${(value.title)!!}</span>
</a>
<button class="navbar-toggler order-1" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse order-3" id="navbarCollapse">
<i class="fa fa-fw fa-space-shuttle"></i><a>${(value.slogan)!!}</a>
</div>
<!-- 右侧导航栏链接 -->
<ul class="order-1 order-md-4 navbar-nav navbar-no-expand ml-auto">
<li class="nav-item">
<a href="https://github.com/moshowgame/SpringBootCodeGenerator/issues" target="_blank"><i class="fa fa-fw fa-share-square"></i>Raise Issue</a>
<a href="https://github.com/moshowgame/SpringBootCodeGenerator" target="_blank"><i class="fa fa-fw fa-star"></i>Github </a>
</li>
</ul>
</div>
</nav>
<!-- /.navbar -->
<!-- Content Wrapper. 包含页面内容 -->
<div class="content-wrapper">
<!-- 主体内容 -->
<div class="content">
<div class="container">
<div class="row">
<!-- /.col-md-6 -->
<div class="col-lg-12">
<blockquote class="quote-secondary">
${(value.description)!!}
</blockquote>
<!-- <div class="card">
<div class="card-header">
<h5 class="card-title m-0">输入SQL</h5>
</div>
<div class="card-body">
<textarea id="inputArea" placeholder="请输入表结构信息..." style="height: 250px;line-height : 10px;" v-model="formData.tableSql"></textarea>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title m-0">生成设置</h5>
</div>
<div class="card-body">
<h6 class="card-title">特殊标题处理</h6>
<p class="card-text">支持引入其他额外内容。</p>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title m-0">输出代码</h5>
</div>
<div class="card-body">
<textarea id="outputArea" placeholder="请输入表结构信息..." style="height: 250px;" v-model="outputStr"></textarea>
</div>
</div> -->
<#include "/main-v2.html">
</div>
<!-- /.col-md-6 -->
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<!-- Control Sidebar -->
<aside class="control-sidebar control-sidebar-dark">
<!-- 控制侧边栏内容在这里 -->
</aside>
<!-- /.control-sidebar -->
<!-- Main Footer -->
<footer class="main-footer">
<!-- To the right -->
<div class="float-right d-none d-sm-inline">
Version ${(value.version)!!}
</div>
<!-- Default to the left -->
<strong>${(value.copyright)!!} <a href="https://zhengkai.blog.csdn.net" target="_blank"><i class="fa fa-fw fa-thumbs-up"></i>zhengkai.blog.csdn.net</a> <a href="https://github.com/moshowgame/SpringBootCodeGenerator/blob/master/donate.png?raw=true" target="_blank"><i class="fa fa-fw fa-credit-card"></i>打赏</a>
</footer>
</div>
<!-- ./wrapper -->
</body>
</html>

View File

@@ -1,234 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<#include "/header.html">
<style>
/* .el-form-item__content{
line-height : 10px;
} */
/*.el-form-item--mini .el-form-item__content, .el-form-item--mini .el-form-item__label{
line-height : 10px;
}*/
/* .el-button-group-top {
padding-top: 5px;
} */
.el-button--primary.is-plain.is-disabled, .el-button--primary.is-plain.is-disabled:active, .el-button--primary.is-plain.is-disabled:focus, .el-button--primary.is-plain.is-disabled:hover {
color: #f9faff;
background-color: #91979d;
border-color: #d9ecff;
font-weight: bold;
}
</style>
</head>
<body>
<div id="rrapp" v-cloak>
<div>
<el-form ref="form" :inline="true" :model="formData" label-width="100px">
<div class="card">
<div class="card-header">
<h5 class="card-title m-0">输入SQL</h5>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" title="折叠">
<i class="fas fa-minus"></i>
</button>
<!-- <button type="button" class="btn btn-tool" data-card-widget="remove" title="移除">
<i class="fas fa-times"></i>
</button> -->
</div>
</div>
<div class="card-body">
<!-- <textarea id="inputArea" placeholder="请输入表结构信息..." style="height: 250px;line-height : 10px;" v-model="formData.tableSql"></textarea> -->
<!-- input area-->
<textarea id="inputArea" placeholder="请输入表结构信息..." style="height: 250px;line-height : 10px;" v-model="formData.tableSql"></textarea>
</div>
</div>
<hr>
<div class="card">
<div class="card-header">
<h5 class="card-title m-0">生成设置</h5>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" title="折叠">
<i class="fas fa-minus"></i>
</button>
<!-- <button type="button" class="btn btn-tool" data-card-widget="remove" title="移除">
<i class="fas fa-times"></i>
</button> -->
</div>
</div>
<div class="card-body">
<!-- setting area-->
<el-form-item label="作者">
<el-input v-model="formData.options.authorName"></el-input>
</el-form-item>
<el-form-item label="包名">
<el-input v-model="formData.options.packageName"></el-input>
</el-form-item>
<el-form-item label="返回(成功)">
<el-input v-model="formData.options.returnUtilSuccess"></el-input>
</el-form-item>
<el-form-item label="返回(失败)">
<el-input v-model="formData.options.returnUtilFailure"></el-input>
</el-form-item>
<el-form-item label="忽略前缀">
<el-input v-model="formData.options.ignorePrefix"></el-input>
</el-form-item>
<el-form-item label="输入类型">
<el-select v-model="formData.options.dataType" >
<el-option label="DDL SQL" value="sql"></el-option>
<el-option label="JSON" value="json"></el-option>
<el-option label="INSERT SQL" value="insert-sql">
<el-option label="SELECT SQL by SQL Parser" value="select-sql">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="TinyInt转换">
<el-select v-model="formData.options.tinyintTransType">
<el-option value="boolean" label="boolean"></el-option>
<el-option value="Boolean" label="Boolean"></el-option>
<el-option value="Integer" label="Integer"></el-option>
<el-option value="int" label="int"></el-option>
<el-option value="String" label="String"></el-option>
<el-option value="Short" label="Short"></el-option>
</el-select>
</el-form-item>
<el-form-item label="时间类型">
<el-select v-model="formData.options.timeTransType">
<el-option value="Date" label="Date"></el-option>
<el-option value="DateTime" label="DateTime"></el-option>
<el-option value="Time" label="Time"></el-option>
<el-option value="Timestamp" label="Timestamp"></el-option>
<el-option value="Calendar" label="Calendar"></el-option>
<el-option value="LocalDate" label="LocalDate"></el-option>
<el-option value="LocalDateTime" label="LocalDateTime"></el-option>
<el-option value="LocalTime" label="LocalTime"></el-option>
</el-select>
</el-form-item>
<el-form-item label="命名类型">
<el-select v-model="formData.options.nameCaseType">
<el-option label="驼峰" value="CamelCase"></el-option>
<el-option label="下划线" value="UnderScoreCase"></el-option>
</el-select>
</el-form-item>
<el-form-item label="包装类型">
<el-switch v-model="formData.options.isPackageType"></el-switch>
</el-form-item>
<el-form-item label="swaggerUI">
<el-switch v-model="formData.options.isSwagger"></el-switch>
</el-form-item>
<el-form-item label="字段注释">
<el-switch v-model="formData.options.isComment"></el-switch>
</el-form-item>
<el-form-item label="自动引包">
<el-switch v-model="formData.options.isAutoImport"></el-switch>
</el-form-item>
<el-form-item label="带包路径">
<el-switch v-model="formData.options.isWithPackage"></el-switch>
</el-form-item>
<el-form-item label="Lombok">
<el-switch v-model="formData.options.isLombok"></el-switch>
</el-form-item>
</div>
</div>
<hr>
<div class="card">
<div class="card-header">
<!-- <h5 class="card-title m-0">输出代码</h5> -->
<el-button type="primary" icon="el-icon-caret-right" @click="generate">生成</el-button>
<el-button type="primary" icon="el-icon-document-copy" @click="copy" plain>复制</el-button>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" title="折叠">
<i class="fas fa-minus"></i>
</button>
<!-- <button type="button" class="btn btn-tool" data-card-widget="remove" title="移除">
<i class="fas fa-times"></i>
</button> -->
</div>
</div>
<div class="card-body">
<!--generate button-->
<span v-if="historicalData.length>0" >
<el-button-group>
<el-button type="primary" plain disabled round>历史记录</el-button>
<span v-for="(item, index) in historicalData" :key="index">
<el-button @click="switchHistoricalData" >{{item}}</el-button>
</span>
</el-button-group>
</span>
</div>
</div>
<hr>
<div class="card">
<div class="card-header">
<h5 class="card-title m-0">模板选择</h5>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" title="折叠">
<i class="fas fa-minus"></i>
</button>
<!-- <button type="button" class="btn btn-tool" data-card-widget="remove" title="移除">
<i class="fas fa-times"></i>
</button> -->
</div>
</div>
<div class="card-body">
<!--templates area-->
<el-form ref="form" :inline="true" :model="formData" label-width="100px" size="mini">
<div v-for="(item, index) in templates" :key="index">
<el-button-group >
<el-button type="primary" plain disabled>{{item.group}}</el-button>
<span v-for="(childItem, index) in item.templates" :key="index">
<el-button @click="setOutputModel">{{childItem.name}}</el-button>
</span>
</el-button-group>
</div>
<!-- <el-button-group>
<el-button type="primary" plain disabled>MybatisPlus</el-button>
<el-button @click="setOutputModel">Controller</el-button>
<el-button @click="setOutputModel">Mapper</el-button>
</el-button-group>-->
</el-form>
</div>
</div>
<hr>
<div class="card">
<div class="card-header">
<h5 class="card-title m-0">输出代码</h5>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse" title="折叠">
<i class="fas fa-minus"></i>
</button>
<!-- <button type="button" class="btn btn-tool" data-card-widget="remove" title="移除">
<i class="fas fa-times"></i>
</button> -->
</div>
</div>
<div class="card-body">
<textarea id="outputArea" placeholder="请输入表结构信息..." style="height: 250px;" v-model="outputStr"></textarea>
</div>
</div>
</el-form>
</div>
</div>
<script src="${request.contextPath}/statics/js/main.js"></script>
<script>
//console.log(vm);
vm.formData.options.authorName="${(value.author)!!}";
vm.formData.options.packageName="${(value.packageName)!!}";
vm.formData.options.returnUtilSuccess="${(value.returnUtilSuccess)!!}";
vm.formData.options.returnUtilFailure="${(value.returnUtilFailure)!!}";
vm.outputStr="${(value.outputStr)!!}";
loadAllCookie()
</script>
</body>
</html>

View File

@@ -1,31 +0,0 @@
<!--###################################################-->
<!--### CDN version : by https://cdn.bytedance.com/ -->
<!--###################################################-->
<!--jquery | vue | element-ui | axios-->
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js"></script>
<script src="//lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/vue/2.6.14/vue.min.js"></script>
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/axios/0.26.0/axios.min.js"></script>
<script src="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/index.min.js"></script>
<link href="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/element-ui/2.15.7/theme-chalk/index.min.css" type="text/css" rel="stylesheet" />
<!-- bootstrap -->
<script src="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap/5.1.3/js/bootstrap.bundle.min.js" type="application/javascript"></script>
<link href="//lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap/5.1.3/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
<!-- 引入 Bootstrap Icons CSS -->
<link href="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css" type="text/css" rel="stylesheet" />
<!--common.js-->
<script src="${request.contextPath}/statics/js/common.js"></script>
<!-- <link rel="stylesheet" href="${request.contextPath}/statics/css/main.css"> -->
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/toastr.js/2.1.4/toastr.min.js"></script>
<!-- Toastr CSS -->
<link href="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/toastr.js/2.1.4/toastr.min.css" rtype="text/css" rel="stylesheet" >
<!-- import codemirror -->
<script src="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/codemirror.min.js"></script>
<script src="//lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/mode/sql/sql.min.js"></script>
<script src="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-y/codemirror/5.65.2/mode/clike/clike.min.js" type="application/javascript"></script>
<link href="//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/codemirror.min.css" type="text/css" rel="stylesheet" >
<link href="//lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/codemirror/5.65.2/theme/idea.min.css" type="text/css" rel="stylesheet" >

View File

@@ -1,104 +0,0 @@
package com.softdev.system.generator.util;
import org.junit.Test;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
public class StringUtilsPlusTest {
@Test
public void toLowerCamel() {
System.out.println(StringUtilsPlus.toLowerCamel("hello_world"));
System.out.println(StringUtilsPlus.toLowerCamel("HELLO_WO-RLD-IK"));
System.out.println(StringUtilsPlus.toLowerCamel("HELLO_WORLD-IKabc"));
System.out.println(StringUtilsPlus.toLowerCamel("HELLO-WORLD-IKabc"));
System.out.println(StringUtilsPlus.toLowerCamel("HELLO-123WORLD-IKabc"));
System.out.println(StringUtilsPlus.toLowerCamel("helloWorldOKla"));
assertEquals("helloWorldChina", StringUtilsPlus.toLowerCamel("hello_-_world-cHina"));
}
@Test
public void upperCaseFirstShouldReturnStringWithFirstLetterCapitalized() {
assertEquals("Hello", StringUtilsPlus.upperCaseFirst("hello"));
}
@Test
public void upperCaseFirstShouldReturnEmptyStringWhenInputIsEmpty() {
assertEquals("", StringUtilsPlus.upperCaseFirst(""));
}
@Test
public void lowerCaseFirstShouldReturnStringWithFirstLetterLowercased() {
assertEquals("hello", StringUtilsPlus.lowerCaseFirst("Hello"));
}
@Test
public void lowerCaseFirstShouldReturnEmptyStringWhenInputIsEmpty() {
assertEquals("", StringUtilsPlus.lowerCaseFirst(""));
}
@Test
public void underlineToCamelCaseShouldReturnCamelCaseString() {
assertEquals("helloWorld", StringUtilsPlus.underlineToCamelCase("hello_world"));
}
@Test
public void underlineToCamelCaseShouldReturnEmptyStringWhenInputIsEmpty() {
assertEquals("", StringUtilsPlus.underlineToCamelCase(""));
}
@Test
public void toUnderlineShouldReturnUnderlinedString() {
assertEquals("hello_world", StringUtilsPlus.toUnderline("helloWorld", false));
}
@Test
public void toUnderlineShouldReturnEmptyStringWhenInputIsEmpty() {
assertEquals("", StringUtilsPlus.toUnderline("", false));
}
@Test
public void toCamelShouldReturnCamelCaseString() {
assertEquals("helloWorld", StringUtilsPlus.toLowerCamel("hello_world"));
}
@Test
public void toCamelShouldReturnEmptyStringWhenInputIsEmpty() {
assertEquals("", StringUtilsPlus.toLowerCamel(""));
}
@Test
public void isNotNullShouldReturnTrueWhenStringIsNotEmpty() {
assertTrue(StringUtilsPlus.isNotNull("hello"));
}
@Test
public void isNotNullShouldReturnFalseWhenStringIsEmpty() {
assertFalse(StringUtilsPlus.isNotNull(""));
}
public static void main(String[] args) {
// String updateTime = StringUtils.underlineToCamelCase("updateTime");
// System.out.println(updateTime);
// System.out.println(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "userName"));
// System.out.println(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "userNAme-UUU"));
System.out.println(StringUtilsPlus.toUnderline("userName",false));
System.out.println(StringUtilsPlus.toUnderline("UserName",false));
System.out.println(StringUtilsPlus.toUnderline("user_NameGgg_x-UUU",false));
System.out.println(StringUtilsPlus.toUnderline("username",false));
System.out.println(StringUtilsPlus.toUnderline("userName",true));
System.out.println(StringUtilsPlus.toUnderline("UserName",true));
System.out.println(StringUtilsPlus.toUnderline("user_NameGgg_x-UUU",true));
System.out.println(StringUtilsPlus.toUnderline("username",true));
System.out.println(StringUtilsPlus.underlineToCamelCase("CREATE_TIME"));
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 228 KiB

185
pom.xml
View File

@@ -5,25 +5,28 @@
<groupId>com.softdev.system</groupId>
<artifactId>SpringBootCodeGenerator</artifactId>
<version>2023</version>
<packaging>pom</packaging>
<version>2025</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
<version>3.5.8</version>
</parent>
<modules>
<module>generator-web</module>
</modules>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<properties>
<!-- 指定编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 指定jdk版本 -->
<java.version>11</java.version>
<java.version>17</java.version>
</properties>
<dependencies>
@@ -32,8 +35,44 @@
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>5.0</version>
<version>5.3</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito for JUnit 5 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -63,27 +102,21 @@
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
<version>2.0.60</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension</artifactId>
<version>2.0.53</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.53</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.0.0</version>
<scope>provided</scope>
</dependency>
<!-- 支持 @ConfigurationProperties 注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -110,7 +143,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<version>1.18.42</version>
</dependency>
<!-- freemarker -->
@@ -129,7 +162,7 @@
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
<version>4.0.4</version>
</dependency>
@@ -137,13 +170,13 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
<version>3.18.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.18.0</version>
<version>2.20.0</version>
</dependency>
</dependencies>
@@ -156,15 +189,107 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- Maven Surefire Plugin for JUnit 5 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
<!-- JaCoCo Maven Plugin for Code Coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<!-- 准备JaCoCo代理用于收集覆盖率数据 -->
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<!-- 生成覆盖率报告 -->
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<!-- 检查覆盖率阈值 -->
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<!-- 指令覆盖率最低要求 -->
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.00</minimum>
</limit>
<!-- 分支覆盖率最低要求 -->
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.00</minimum>
</limit>
<!-- 类覆盖率最低要求 -->
<limit>
<counter>CLASS</counter>
<value>COVEREDRATIO</value>
<minimum>0.00</minimum>
</limit>
<!-- 方法覆盖率最低要求 -->
<limit>
<counter>METHOD</counter>
<value>COVEREDRATIO</value>
<minimum>0.00</minimum>
</limit>
<!-- 行覆盖率最低要求 -->
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.00</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
<configuration>
<!-- 排除不需要生成覆盖率报告的类 -->
<excludes>
<exclude>**/Application.class</exclude>
<exclude>**/config/**</exclude>
<exclude>**/dto/**</exclude>
<exclude>**/vo/**</exclude>
<exclude>**/entity/**</exclude>
<exclude>**/util/exception/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>alimaven</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
</project>

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,12 +1,11 @@
package com.softdev.system.generator.config;
import com.softdev.system.generator.entity.ReturnT;
import com.softdev.system.generator.entity.vo.ResultVo;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import jakarta.servlet.http.HttpServletRequest;
/**
* @author zhengkai.blog.csdn.net
*/
@@ -15,9 +14,9 @@ public class GlobalDefaultExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public ReturnT defaultExceptionHandler(HttpServletRequest req, Exception e) {
public ResultVo defaultExceptionHandler(HttpServletRequest req, Exception e) {
e.printStackTrace();
return ReturnT.error("代码生成失败:"+e.getMessage());
return ResultVo.error("代码生成失败:"+e.getMessage());
}
}

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;
/**
@@ -32,16 +23,16 @@ public class WebMvcConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
}
@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
// @Bean
// public FilterRegistrationBean xssFilterRegistration() {
// FilterRegistrationBean registration = new FilterRegistrationBean();
// registration.setDispatcherTypes(DispatcherType.REQUEST);
// registration.setFilter(new XssFilter());
// registration.addUrlPatterns("/*");
// registration.setName("xssFilter");
// registration.setOrder(Integer.MAX_VALUE);
// return registration;
// }
// @Override
// public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
@@ -74,8 +65,20 @@ public class WebMvcConfig implements WebMvcConfigurer {
//自定义配置...
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
config.setReaderFeatures(JSONReader.Feature.FieldBased, JSONReader.Feature.SupportArrayToBean);
config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue, JSONWriter.Feature.PrettyFormat);
// 添加更多解析特性以提高容错性
config.setReaderFeatures(
JSONReader.Feature.FieldBased,
JSONReader.Feature.SupportArrayToBean,
// JSONReader.Feature.IgnoreNoneFieldGetter,
JSONReader.Feature.InitStringFieldAsEmpty
);
config.setWriterFeatures(
JSONWriter.Feature.WriteMapNullValue,
JSONWriter.Feature.PrettyFormat
);
converter.setFastJsonConfig(config);
converter.setDefaultCharset(StandardCharsets.UTF_8);
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));

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

@@ -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,60 @@
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) {
if (value == null || value.trim().isEmpty()) {
return SQL;
}
String trimmedValue = value.trim();
// 首先尝试精确匹配枚举值
for (ParserTypeEnum type : ParserTypeEnum.values()) {
if (type.getValue().equals(trimmedValue)) {
return type;
}
}
// 如果精确匹配失败,尝试忽略大小写匹配
for (ParserTypeEnum type : ParserTypeEnum.values()) {
if (type.getValue().equalsIgnoreCase(trimmedValue)) {
return type;
}
}
// 尝试匹配枚举名称
for (ParserTypeEnum type : ParserTypeEnum.values()) {
if (type.name().equalsIgnoreCase(trimmedValue)) {
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

@@ -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,123 @@
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);
// 添加调试信息
log.debug("解析数据类型: {}, 解析结果: {}", dataType, parserType);
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,94 +1,82 @@
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;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
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;
/**
* 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();
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse(paramInfo.getTableSql());
List<SelectItem<?>> columnNameList = select.getSelectItems();
log.info("tableName:{}", select.getFromItem().toString());
String processedSql = paramInfo.getTableSql().trim()
.replaceAll("'", "`") // 将单引号替换为反引号
.replaceAll("\"", "`") // 将双引号替换为反引号
.replaceAll("", ","); // 将中文逗号替换为英文逗号
Statement statement = null;
CCJSqlParserManager parserManager = new CCJSqlParserManager();
statement = parserManager.parse(new StringReader(processedSql));
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); // 创建表名发现者对象
List<String> tableNameList = tablesNamesFinder.getTableList(statement); // 获取到表名列表
//一般这里应该只解析到一个表名除非多个表名取第一个
if (!CollectionUtils.isEmpty(tableNameList)) {
String tableName = tableNameList.get(0).trim();
classInfo.setTableName(tableName);
classInfo.setOriginTableName(tableName);
String className = StringUtilsPlus.upperCaseFirst(StringUtilsPlus.underlineToCamelCase(tableName)).replaceAll("`", "");
if (className.contains("_")) {
className = className.replaceAll("_", "");
}
classInfo.setClassName(className);
classInfo.setClassComment(paramInfo.getTableSql());
}
//解析查询元素
Select select = null;
select = (Select) CCJSqlParserUtil.parse(paramInfo.getTableSql());
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
List<SelectItem<?>> selectItems = plainSelect.getSelectItems();
// field List
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
columnNameList.forEach(t->{
selectItems.forEach(t->{
FieldInfo fieldInfo = new FieldInfo();
String fieldName = ((Column)t.getExpression()).getColumnName();
String fieldName = ((Column)t.getExpression()).getColumnName().replaceAll("`", "");
String aliasName = t.getAlias() != null ? t.getAlias().getName() : ((Column)t.getExpression()).getColumnName();
//存储原始字段名
fieldInfo.setFieldComment(aliasName);fieldInfo.setColumnName(aliasName);
@@ -96,21 +84,15 @@ public class GeneratorServiceImpl implements GeneratorService {
fieldName=fieldName.contains(".")?fieldName.substring(fieldName.indexOf(".")+1):fieldName;
//转换前
fieldInfo.setColumnName(fieldName);
switch ((String) paramInfo.getOptions().get("nameCaseType")) {
case ParamInfo.NAME_CASE_TYPE.CAMEL_CASE:
fieldName = switch ((String) paramInfo.getOptions().get("nameCaseType")) {
case ParamInfo.NameCaseType.CAMEL_CASE ->
// 2024-1-27 L&J 适配任意(maybe)原始风格转小写驼峰
fieldName = StringUtilsPlus.toLowerCamel(aliasName);
break;
case ParamInfo.NAME_CASE_TYPE.UNDER_SCORE_CASE:
fieldName = StringUtilsPlus.toUnderline(aliasName, false);
break;
case ParamInfo.NAME_CASE_TYPE.UPPER_UNDER_SCORE_CASE:
fieldName = StringUtilsPlus.toUnderline(aliasName.toUpperCase(), true);
break;
default:
fieldName = aliasName;
break;
}
StringUtilsPlus.toLowerCamel(aliasName);
case ParamInfo.NameCaseType.UNDER_SCORE_CASE -> StringUtilsPlus.toUnderline(aliasName, false);
case ParamInfo.NameCaseType.UPPER_UNDER_SCORE_CASE ->
StringUtilsPlus.toUnderline(aliasName.toUpperCase(), true);
default -> aliasName;
};
//转换后
fieldInfo.setFieldName(fieldName);
@@ -119,35 +101,85 @@ public class GeneratorServiceImpl implements GeneratorService {
fieldList.add(fieldInfo);
});
classInfo.setFieldList(fieldList);
String tableName = select.getFromItem().toString();
classInfo.setTableName(tableName);
//如果表名有空格取空格前的第一个单词作为类名
if(tableName.indexOf(" ")>0){
classInfo.setClassName(StringUtilsPlus.upperCaseFirst(StringUtilsPlus.underlineToCamelCase(tableName.substring(0,tableName.indexOf(" ")))));
}else{
classInfo.setClassName(StringUtilsPlus.upperCaseFirst(StringUtilsPlus.underlineToCamelCase(tableName)));
}
log.info("classInfo:{}", JSON.toJSONString(classInfo));
return classInfo;
}
/**
* 解析DDL SQL生成类信息(默认模式|核心模式)
*
* @param paramInfo
* @return
*/
@Override
public ClassInfo processTableIntoClassInfo(ParamInfo paramInfo)
throws IOException {
public ClassInfo generateCreateSqlBySQLPraser(ParamInfo paramInfo) throws Exception {
ClassInfo classInfo = new ClassInfo();
Statement statement = null;
// 对SQL进行预处理以提高解析成功率
String processedSql = paramInfo.getTableSql().trim()
.replaceAll("'", "`") // 将单引号替换为反引号
.replaceAll("\"", "`") // 将双引号替换为反引号
.replaceAll("", ","); // 将中文逗号替换为英文逗号
try {
statement = CCJSqlParserUtil.parse(processedSql);
}catch (Exception e) {
e.printStackTrace();
throw new SqlParseException("SQL语法错误:"+e.getMessage());
}
// 确保是CREATE TABLE语句
if (!(statement instanceof CreateTable createTable)) {
throw new SqlParseException("检测到SQL语句不是DLL CREATE TABLE语句");
}
// 提取表名
String tableName = createTable.getTable().getName().replaceAll("`", "");
classInfo.setTableName(tableName);
String className = StringUtilsPlus.upperCaseFirst(StringUtilsPlus.underlineToCamelCase(tableName));
if (className.contains("_")) {
className = className.replaceAll("_", "");
}
classInfo.setClassName(className);
classInfo.setOriginTableName(tableName);
classInfo.setClassComment(paramInfo.getTableSql());
// 提取字段信息
List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
List<ColumnDefinition> columnDefinitions = createTable.getColumnDefinitions();
if (columnDefinitions != null) {
for (ColumnDefinition columnDefinition : columnDefinitions) {
FieldInfo fieldInfo = new FieldInfo();
String columnName = columnDefinition.getColumnName().replaceAll("`", "");
fieldInfo.setColumnName(columnName);
fieldInfo.setFieldComment(columnDefinition.toString());
// 根据命名规则转换字段名
String fieldName = switch ((String) paramInfo.getOptions().get("nameCaseType")) {
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;
};
fieldInfo.setFieldName(fieldName);
// 设置字段类型为String因为无法准确推测类型
fieldInfo.setFieldClass("String");
fieldList.add(fieldInfo);
}
}
classInfo.setFieldList(fieldList);
log.info("classInfo:{}", JSON.toJSONString(classInfo));
return classInfo;
}
@Override
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()
@@ -163,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情况
@@ -201,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")) {
int ix = tableSql.lastIndexOf("comment=");
if (tableSql.toLowerCase().contains("comment=") || tableSql.toLowerCase().contains("comment on table")) {
int ix = tableSql.toLowerCase().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.toLowerCase().lastIndexOf("comment on table") + 17).trim();
if (classCommentTmp.contains("`")) {
classCommentTmp = classCommentTmp.substring(classCommentTmp.indexOf("`") + 1);
classCommentTmp = classCommentTmp.substring(0, classCommentTmp.indexOf("`"));
@@ -224,11 +256,11 @@ 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(")")).trim();
// 匹配 comment替换备注里的小逗号, 防止不小心被当成切割符号切割
String commentPattenStr1 = "comment `(.*?)\\`";
Matcher matcher1 = Pattern.compile(commentPattenStr1).matcher(fieldListTmp);
Matcher matcher1 = Pattern.compile(commentPattenStr1).matcher(fieldListTmp.toLowerCase());
while (matcher1.find()) {
String commentTmp = matcher1.group();
@@ -265,17 +297,29 @@ 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);
// 2025-12-07 zhengkai 修复对primary key的处理
boolean notSpecialFlag = (
!columnLine.contains("key ")
&& !columnLine.toLowerCase().contains("constraint")
&& !columnLine.toLowerCase().contains(" using ")
&& !columnLine.toLowerCase().contains("unique ")
&& !columnLine.toLowerCase().contains("fulltext ")
&& !columnLine.toLowerCase().contains("index ")
&& !columnLine.toLowerCase().contains("pctincrease")
&& !columnLine.toLowerCase().contains("buffer_pool")
&& !columnLine.toLowerCase().contains("tablespace")
&& !(columnLine.toLowerCase().contains("primary ") && columnLine.indexOf("storage") + 3 > columnLine.indexOf("("))
&& !(columnLine.toLowerCase().contains("primary ") && i > 3)
&& !columnLine.toLowerCase().contains("primary key")
);
if (notSpecialFlag) {
//如果是oracle的number(x,x)可能出现最后分割残留的,x)这里做排除处理
@@ -287,7 +331,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;
@@ -297,19 +341,22 @@ 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();
// 修复Oracle字段名不带引号的情况
if (columnLine.contains("`")) {
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("("));
}
@@ -330,23 +377,23 @@ public class GeneratorServiceImpl implements GeneratorService {
}
// field commentMySQL的一般位于field行而pgsql和oralce多位于后面
String fieldComment = null;
if (tableSql.contains("comment on column") && (tableSql.contains("." + columnName + " is ") || tableSql.contains(".`" + columnName + "` is"))) {
if (tableSql.toLowerCase().contains("comment on column") && (tableSql.toLowerCase().contains("." + columnName + " is ") || tableSql.toLowerCase().contains(".`" + columnName + "` is"))) {
//新增对pgsql/oracle的字段备注支持
//COMMENT ON COLUMN public.check_info.check_name IS '检查者名称';
//2018-11-22 lshz0088 正则表达式的点号前面应该加上两个反斜杠否则会认为是任意字符
//2019-4-29 zhengkai 优化对oracle注释comment on column的支持@liukex
tableSql = tableSql.replaceAll(".`" + columnName + "` is", "." + columnName + " is");
Matcher columnCommentMatcher = Pattern.compile("\\." + columnName + " is `").matcher(tableSql);
tableSql = tableSql.toLowerCase().replaceAll(".`" + columnName + "` is", "." + columnName + " is");
Matcher columnCommentMatcher = Pattern.compile("\\." + columnName + " is `").matcher(tableSql.toLowerCase());
fieldComment = columnName;
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")) {
} else if (columnLine.toLowerCase().contains(" comment")) {
//20200518 zhengkai 修复包含comment关键字的问题
String commentTmp = columnLine.substring(columnLine.lastIndexOf("comment") + 7).trim().get();
String commentTmp = columnLine.toLowerCase().substring(columnLine.toLowerCase().lastIndexOf("comment") + 7).trim();
// '用户ID',
if (commentTmp.contains("`") || commentTmp.indexOf("`") != commentTmp.lastIndexOf("`")) {
commentTmp = commentTmp.substring(commentTmp.indexOf("`") + 1, commentTmp.lastIndexOf("`"));
@@ -356,6 +403,9 @@ public class GeneratorServiceImpl implements GeneratorService {
commentTmp = commentTmp.substring(0, commentTmp.lastIndexOf(")") + 1);
}
fieldComment = commentTmp;
} else if (columnLine.contains("--")) {
// 支持Oracle风格的注释--
fieldComment = columnLine.substring(columnLine.indexOf("--") + 2).trim();
} else {
//修复comment不存在导致报错的问题
fieldComment = columnName;
@@ -374,10 +424,10 @@ public class GeneratorServiceImpl implements GeneratorService {
}
}
if (fieldList.size() < 1) {
throw new CodeGenerateException("表结构分析失败请检查语句或者提交issue给我");
if (fieldList.isEmpty()) {
throw new Exception("表结构分析失败请检查语句或者提交issue给我");
}
//build Class Info
ClassInfo codeJavaInfo = new ClassInfo();
codeJavaInfo.setTableName(tableName);
codeJavaInfo.setClassName(className);
@@ -388,63 +438,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>();
@@ -461,7 +455,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");
@@ -490,42 +484,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>();
@@ -558,12 +517,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);
@@ -573,14 +534,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

@@ -22,7 +22,7 @@ public class MapUtil {
public static Integer getInteger(Map map,String key){
if(map!=null && map.containsKey(key)){
try{
return (Integer) map.get(key);
return Integer.valueOf(map.get(key).toString());
}catch (Exception e){
e.printStackTrace();
return 0;
@@ -34,7 +34,7 @@ public class MapUtil {
public static Boolean getBoolean(Map map,String key){
if(map!=null && map.containsKey(key)){
try{
return (Boolean) map.get(key);
return Boolean.parseBoolean(map.get(key).toString()) || "true".equals(map.get(key).toString());
}catch (Exception e){
e.printStackTrace();
return false;

View File

@@ -0,0 +1,35 @@
package com.softdev.system.generator.util;
import java.io.Serial;
/**
* @author xuxueli 2018-05-02 21:10:28
*/
public class SqlException extends RuntimeException {
@Serial
private static final long serialVersionUID = 42L;
public SqlException() {
super();
}
public SqlException(String msg) {
super(msg);
}
public SqlException(String msg, Throwable cause) {
super(msg, cause);
}
public SqlException(Throwable cause) {
super(cause);
}
public SqlException(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;
/**
* 代码生成异常
*
* @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

@@ -27,6 +27,7 @@ public final class mysqlJavaTypeUtil {
//字符串
mysqlJavaTypeMap.put("char","String");
mysqlJavaTypeMap.put("varchar","String");
mysqlJavaTypeMap.put("varchar2","String"); // Oracle类型
mysqlJavaTypeMap.put("tinytext","String");
mysqlJavaTypeMap.put("text","String");
mysqlJavaTypeMap.put("mediumtext","String");
@@ -35,6 +36,8 @@ public final class mysqlJavaTypeUtil {
mysqlJavaTypeMap.put("date","Date");
mysqlJavaTypeMap.put("datetime","Date");
mysqlJavaTypeMap.put("timestamp","Date");
// 数字类型 - Oracle增强
mysqlJavaTypeMap.put("number","BigDecimal"); // Oracle的NUMBER类型默认映射为BigDecimal支持精度
mysqlSwaggerTypeMap.put("bigint","integer");
@@ -46,7 +49,10 @@ public final class mysqlJavaTypeUtil {
mysqlSwaggerTypeMap.put("boolean","boolean");
mysqlSwaggerTypeMap.put("float","number");
mysqlSwaggerTypeMap.put("double","number");
mysqlSwaggerTypeMap.put("decimal","Double");
mysqlSwaggerTypeMap.put("decimal","number");
// Oracle类型
mysqlSwaggerTypeMap.put("varchar2","string");
mysqlSwaggerTypeMap.put("number","number");
}
public static HashMap<String, String> getMysqlJavaTypeMap() {

View File

@@ -49,15 +49,15 @@ spring:
#mvc:
# static-path-pattern: /statics/**
OEM:
version: 2025 March
version: 2025 September
header: SQL转Java JPA、MYBATIS实现类代码生成平台
keywords: sql转实体类,sql转DAO,SQL转service,SQL转JPA实现,SQL转MYBATIS实现
title: JAVA在线代码生成
slogan: Release your hands from tedious and repetitive CRUD tasks.
description: <p>SpringBootCodeGenerator又名`大狼狗代码生成器`、`SQL转JAVA`、`SQL转JPA`、`SQL转Mybatis`、`Mybatis在线生成器`、`SQL转Java JPA、MYBATIS实现类代码生成平台`。</p><p>——从繁琐重复的`CRUD工作`中释放你的双手可通过DDL SQL语句或Select SQL语句或简单Json -> 生成JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper等相关模板代码。</p>
slogan: 👐 Free your hands from boring CRUD—let the code write itself! ⚡
description: <p>💻 SpringBootCodeGenerator又名 `大狼狗代码生成器`🐺🐶,支持 SQL 一键转 JAVA/JPA/Mybatis/MybatisPlus 等多种模板,轻松搞定 CRUD彻底解放双手 ✋!只需提供 DDL、SELECT SQL 或简单 JSON 👉 即可生成 生成JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper 等代码模板。</p><p>🔥🔥🔥 全新 JSqlParser 引擎上线,强力支持 DDL CREATE SQL与 SELECT SQL 解析!欢迎体验 & 反馈 💬!</p><p>👨‍💻 面向开发者的高效利器,已服务数万工程师,欢迎 Star ⭐、Fork 🍴、提 Issue 💬,一起打造更强大的代码生成平台!</p>
author: BEJSON.com
packageName: www.bejson.com
copyright: Powered by <a href="https://zhengkai.blog.csdn.net" target="_blank">Moshow郑锴</a> , Might the holy light be with you !
copyright: Powered by <a href="https://zhengkai.blog.csdn.net" target="_blank">Moshow郑锴</a><a href="https://www.bejson.com/">BeJSON</a> — may the holy light guide your code, your coffee, and your commits! ⚡🧙‍♂️💻
returnUtilSuccess: ResponseUtil.success
returnUtilFailure: ResponseUtil.error
outputStr: www.bejson.com

View File

@@ -47,16 +47,16 @@ spring:
#mvc:
# static-path-pattern: /statics/**
OEM:
version: 2025 March
version: 2025 September
header: SQL转Java JPA、MYBATIS实现类代码生成平台
keywords: sql转实体类,sql转DAO,SQL转service,SQL转JPA实现,SQL转MYBATIS实现
title: JAVA代码生成平台
slogan: Release your hands from tedious and repetitive CRUD tasks.
description: <p>SpringBootCodeGenerator又名`大狼狗代码生成器`、`SQL转JAVA`、`SQL转JPA`、`SQL转Mybatis`、`Mybatis在线生成器`、`SQL转Java JPA、MYBATIS实现类代码生成平台`。</p><p>——从繁琐重复的`CRUD工作`中释放你的双手可通过DDL SQL语句或Select SQL语句或简单Json -> 生成JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper等相关模板代码。</p>
title: 大狼狗代码生成
slogan: 👐 Free your hands from boring CRUD—let the code write itself! ⚡
description: <p>💻 SpringBootCodeGenerator又名 `大狼狗代码生成器`🐺🐶,支持 SQL 一键转 JAVA/JPA/Mybatis/MybatisPlus 等多种模板,轻松搞定 CRUD彻底解放双手 ✋!只需提供 DDL、SELECT SQL 或简单 JSON 👉 即可生成 生成JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper 等代码模板。</p><p>🔥🔥🔥 全新 JSqlParser 引擎上线,强力支持 DDL CREATE SQL与 SELECT SQL 解析!欢迎体验 & 反馈 💬!</p><p>👨‍💻 面向开发者的高效利器,已服务数万工程师,欢迎 Star ⭐、Fork 🍴、提 Issue 💬,一起打造更强大的代码生成平台!</p>
author: zhengkai.blog.csdn.net
packageName: com.software.system
copyright: Powered by <a href="https://zhengkai.blog.csdn.net" target="_blank">Moshow郑锴</a> , Might the holy light be with you !
copyright: Powered by <a href="https://zhengkai.blog.csdn.net" target="_blank">Moshow郑锴</a> — may the holy light guide your code, your coffee, and your commits! ⚡🧙‍♂️💻
returnUtilSuccess: ResponseUtil.success
returnUtilFailure: ResponseUtil.error
outputStr: http://zhengkai.blog.csdn.net
mode: CDN
mode: local

View File

@@ -0,0 +1,64 @@
server:
port: 1235
http2:
enabled: true
servlet:
context-path: /generator
#tomcat:
# remote-ip-header: x-forward-for
# uri-encoding: UTF-8
# max-threads: 10
# background-processor-delay: 30
# basedir: ${user.home}/tomcat/
undertow:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
# 不要设置过大,如果过大,启动项目会报错:打开文件数过多
io-threads: 4
# 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线程
# 它的值设置取决于系统线程执行任务的阻塞系数默认值是IO线程数*8
worker-threads: 64
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可
buffer-size: 1024
# 是否分配的直接内存(NIO直接分配的堆外内存)
direct-buffers: true
spring:
banner:
charset: UTF-8
web:
encoding:
force: true
charset: UTF-8
application:
name: spring-boot-code-generator
freemarker:
request-context-attribute: request
suffix: .html
content-type: text/html
enabled: true
cache: false
charset: UTF-8
allow-request-override: false
expose-request-attributes: true
expose-session-attributes: true
expose-spring-macro-helpers: true
settings:
number_format: 0.##
default_encoding: UTF-8
#template_loader: /templates/
#mvc:
# static-path-pattern: /statics/**
OEM:
version: 2025 September
header: SQL转Java JPA、MYBATIS实现类代码生成平台
keywords: sql转实体类,sql转DAO,SQL转service,SQL转JPA实现,SQL转MYBATIS实现
title: JAVA在线代码生成
slogan: 👐 Free your hands from boring CRUD—let the code write itself! ⚡
description: <p>💻 SpringBootCodeGenerator又名 `大狼狗代码生成器`🐺🐶,支持 SQL 一键转 JAVA/JPA/Mybatis/MybatisPlus 等多种模板,轻松搞定 CRUD彻底解放双手 ✋!只需提供 DDL、SELECT SQL 或简单 JSON 👉 即可生成 生成JPA/JdbcTemplate/Mybatis/MybatisPlus/BeetlSQL/CommonMapper 等代码模板。</p><p>🔥🔥🔥 全新 JSqlParser 引擎上线,强力支持 DDL CREATE SQL与 SELECT SQL 解析!欢迎体验 & 反馈 💬!</p><p>👨‍💻 面向开发者的高效利器,已服务数万工程师,欢迎 Star ⭐、Fork 🍴、提 Issue 💬,一起打造更强大的代码生成平台!</p>
author: https://www.json.cn/
packageName: www.json.cn
copyright: ✨ Powered by <a href="https://zhengkai.blog.csdn.net" target="_blank">Moshow郑锴</a><a href="https://www.json.cn/">JSON</a> — may the holy light guide your code, your coffee, and your commits! ⚡🧙‍♂️💻
returnUtilSuccess: ResponseUtil.success
returnUtilFailure: ResponseUtil.error
outputStr: www.json.cn
mode: local

View File

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 434 KiB

View File

@@ -123,18 +123,20 @@ const vm = new Vue({
//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语句!!!");
if(res.status===500||res.data.code===500){
console.log(res);
error("生成失败请检查SQL语句!!!"+res.data.msg);
return;
}
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();
@@ -158,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 () {

Some files were not shown because too many files have changed in this diff Show More