# RuoYi 框架使用经验
相关内容基于中州养老项目与若依框架
RuoYi(若依)是一款基于 Spring Boot 等主流技术栈、提供代码生成器和完整权限管理功能的 Java 开源快速开发平台,旨在帮助开发者快速构建企业级后台管理系统。
通过 gitee 即可获取源码:https://gitee.com/y_project/RuoYi-Vue
# 启动
# 创建数据库并导入
目前我这个是 3.9.x 版本的,用这个脚本导入:ry_20260417.sql

完成之后:

# 修改 MySQL 数据库连接信息
修改连接池的地址、库名和密码

# 修改 Redis 数据库连接信息
修改连接的地址和密码

# 启动后端

# 前端的安装与修改
- 安装 NPM 包
- 修改与后端的连接地址

# 端口占用错误
error when starting dev server:
Error: listen EACCES: permission denied 0.0.0.0:80

可以修改为 8081
# 启动前端

# 认识 RuoYi 的结构
RuoYi-Vue-master/ | |
├── .github/ # GitHub 相关配置(如 Issue 模板) | |
├── .idea/ # IDEA 编辑器配置文件(可忽略) | |
├── bin/ # 启动脚本目录(Windows/Linux 启动、停止脚本) | |
├── doc/ # 项目文档(如 SQL 脚本、部署文档等) | |
├── ruoyi-admin/ # 核心启动模块(Controller + 启动类) | |
├── ruoyi-common/ # 公共工具模块(工具类、常量、异常等) | |
├── ruoyi-framework/ # 框架核心模块(配置、拦截器、安全等) | |
├── ruoyi-generator/ # 代码生成器模块(自动生成前后端代码) | |
├── ruoyi-quartz/ # 定时任务模块(基于 Quartz 调度) | |
├── ruoyi-system/ # 系统业务模块(用户、角色、菜单等 Mapper/Service) | |
├── ruoyi-ui/ # 前端 Vue 项目(若依的独立前端,但这里为空?) | |
├── sql/ # 数据库 SQL 脚本 | |
│ ├── quarze.sql # Quartz 定时任务表结构 | |
│ └── ry_20260417.sql # 若依核心业务表结构及初始数据 | |
├── LICENSE # 开源协议(MIT) | |
├── pom.xml # Maven 父 POM(依赖管理) | |
├── README.md # 项目说明文档 | |
├── ry.bat # Windows 启动脚本 | |
└── ry.sh # Linux/Mac 启动脚本 |
# 了解 3.9.x 的其他集成版本
| 属性名 | 值 | 说明 |
|---|---|---|
java.version |
17 |
JDK 版本要求 |
spring-boot.version |
4.0.6 |
Spring Boot 版本 |
ruoyi.version |
3.9.2 |
项目自身版本号 |
druid.version |
1.2.28 |
阿里 Druid 连接池版本 |
pagehelper.boot.version |
4.1.0 |
分页插件版本 |
fastjson.version |
2.0.62 |
阿里 Fastjson2 版本 |
jwt.version |
0.9.1 |
JWT 工具 |
springdoc.version |
3.0.3 |
SpringDoc(Swagger 替代)版本 |
# 数据库主从配置
druid: | |
# 主库数据源 | |
master: | |
url: jdbc:mysql://192.168.100.168:3306/zzyl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 | |
username: root | |
password: heima123 | |
# 从库数据源 | |
slave: | |
# 从数据源开关 / 默认关闭 | |
enabled: false | |
url: | |
username: | |
password: |
# 鉴权
package com.ruoyi.framework.web.service; | |
import java.util.Set; | |
import org.springframework.stereotype.Service; | |
import org.springframework.util.CollectionUtils; | |
import com.ruoyi.common.constant.Constants; | |
import com.ruoyi.common.core.domain.entity.SysRole; | |
import com.ruoyi.common.core.domain.model.LoginUser; | |
import com.ruoyi.common.utils.SecurityUtils; | |
import com.ruoyi.common.utils.StringUtils; | |
import com.ruoyi.framework.security.context.PermissionContextHolder; | |
/** | |
* RuoYi 首创 自定义权限实现,ss 取自 SpringSecurity 首字母 | |
* | |
* @author ruoyi | |
*/ | |
@Service("ss") | |
public class PermissionService | |
{ | |
/** | |
* 验证用户是否具备某权限 | |
* | |
* @param permission 权限字符串 | |
* @return 用户是否具备某权限 | |
*/ | |
public boolean hasPermi(String permission) | |
{ | |
if (StringUtils.isEmpty(permission)) | |
{ | |
return false; | |
} | |
LoginUser loginUser = SecurityUtils.getLoginUser(); | |
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) | |
{ | |
return false; | |
} | |
PermissionContextHolder.setContext(permission); | |
return hasPermissions(loginUser.getPermissions(), permission); | |
} | |
/** | |
* 验证用户是否不具备某权限,与 hasPermi 逻辑相反 | |
* | |
* @param permission 权限字符串 | |
* @return 用户是否不具备某权限 | |
*/ | |
public boolean lacksPermi(String permission) | |
{ | |
return hasPermi(permission) != true; | |
} | |
/** | |
* 验证用户是否具有以下任意一个权限 | |
* | |
* @param permissions 以 PERMISSION_DELIMITER 为分隔符的权限列表 | |
* @return 用户是否具有以下任意一个权限 | |
*/ | |
public boolean hasAnyPermi(String permissions) | |
{ | |
if (StringUtils.isEmpty(permissions)) | |
{ | |
return false; | |
} | |
LoginUser loginUser = SecurityUtils.getLoginUser(); | |
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) | |
{ | |
return false; | |
} | |
PermissionContextHolder.setContext(permissions); | |
Set<String> authorities = loginUser.getPermissions(); | |
for (String permission : permissions.split(Constants.PERMISSION_DELIMITER)) | |
{ | |
if (permission != null && hasPermissions(authorities, permission)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* 判断用户是否拥有某个角色 | |
* | |
* @param role 角色字符串 | |
* @return 用户是否具备某角色 | |
*/ | |
public boolean hasRole(String role) | |
{ | |
if (StringUtils.isEmpty(role)) | |
{ | |
return false; | |
} | |
LoginUser loginUser = SecurityUtils.getLoginUser(); | |
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) | |
{ | |
return false; | |
} | |
for (SysRole sysRole : loginUser.getUser().getRoles()) | |
{ | |
String roleKey = sysRole.getRoleKey(); | |
if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* 验证用户是否不具备某角色,与 isRole 逻辑相反。 | |
* | |
* @param role 角色名称 | |
* @return 用户是否不具备某角色 | |
*/ | |
public boolean lacksRole(String role) | |
{ | |
return hasRole(role) != true; | |
} | |
/** | |
* 验证用户是否具有以下任意一个角色 | |
* | |
* @param roles 以 ROLE_DELIMITER 为分隔符的角色列表 | |
* @return 用户是否具有以下任意一个角色 | |
*/ | |
public boolean hasAnyRoles(String roles) | |
{ | |
if (StringUtils.isEmpty(roles)) | |
{ | |
return false; | |
} | |
LoginUser loginUser = SecurityUtils.getLoginUser(); | |
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) | |
{ | |
return false; | |
} | |
for (String role : roles.split(Constants.ROLE_DELIMITER)) | |
{ | |
if (hasRole(role)) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* 判断是否包含权限 | |
* | |
* @param permissions 权限列表 | |
* @param permission 权限字符串 | |
* @return 用户是否具备某权限 | |
*/ | |
private boolean hasPermissions(Set<String> permissions, String permission) | |
{ | |
return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); | |
} | |
} |
接口鉴权实例:
@Log(title = "登录日志", businessType = BusinessType.EXPORT) | |
@PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") | |
@PostMapping("/export") | |
public void export(HttpServletResponse response, SysLogininfor logininfor) | |
{ | |
List<SysLogininfor> list = logininforService.selectLogininforList(logininfor); | |
ExcelUtil<SysLogininfor> util = new ExcelUtil<SysLogininfor>(SysLogininfor.class); | |
util.exportExcel(response, list, "登录日志"); | |
} |
# 数据表
| 表名 | 说明 | 关系 |
|---|---|---|
| gen_table | 代码生成业务表 | 代码生成 |
| gen_table_column | 代码生成业务表字段 | |
| sys_config | 参数配置表 | 数据字典 |
| sys_dict_data | 字典数据表 | |
| sys_dict_type | 字典类型表 | |
| sys_job | 定时任务调度表 | 定时任务 |
| sys_job_log | 定时任务调度日志表 | |
| sys_logininfor | 系统访问记录 | 日志 |
| sys_oper_log | 操作日志记录 | |
| sys_notice | 通知公告表 | |
| sys_menu | 菜单表 | 权限 |
| sys_dept | 部门表 | |
| sys_post | 岗位信息表 | |
| sys_role | 角色信息表 | |
| sys_role_dept | 角色和部门关联表 | |
| sys_role_menu | 角色和菜单关联表 | |
| sys_user | 用户信息表 | |
| sys_user_post | 用户与岗位关联表 | |
| sys_user_role | 用户和角色关联表 |
主要这里围绕着鉴权和日志展开,其他的是作为辅助增强功能
# 基于单表代码生成的快速开发
通常我们一个人或者一个小型团队开发一个全栈项目(对于我来说还是一个人),一般是先开发后端接口,然后修改前端代码,这样是很有逻辑感的的一个过程。RuoYi 框架对于个人或者小型团队来讲,直接颠覆了传统的开发范式。
下面我们一起看看,如何通过 RuoYi 框架进行单表快速开发(为什么单表?因为单表一般是 CRUD,没有很强的业务逻辑,对于标准流程来说非常适用)
# 添加母菜单
# 表的导入
# 表的微调
这里在创建日期,更新日期一般由数据库自动完成,我们可以取消勾选,还有就是一些条件查询项目,比如说我们要根据名称查询啊,根据状态查询啊,可以把他勾上,其他的倒是可以取消勾选了。再有就是 int 型他这里识别对成了 Long 当然也是可以用的,这里我们还是改一下。最后是 id 列也是自动生成的,不需要插入。
然后就会生成这个压缩包:
解压后得到这个三个东西:
# 如何使用生成的内容
- 执行 SQL 脚本
- 将 main 下内容放到后端,这里放在了 ruoyi-admin 里
- 将 vue 下的内容放在前端
此时重启后端,单表 CRUD 开发就结束了。
更新表单日志:
19:59:01.129 [http-nio-8080-exec-16] DEBUG c.r.s.m.N.updateNursingProject - [debug,135] - ==> Preparing: update nursing_project SET name = ?, order_no = ?, unit = ?, price = ?, image = ?, nursing_requirement = ?, status = ?, create_time = ?, update_time = ? where id = ? | |
19:59:01.131 [http-nio-8080-exec-16] DEBUG c.r.s.m.N.updateNursingProject - [debug,135] - ==> Parameters: 3(String), 2(Integer), 1(String), 1(BigDecimal), /profile/upload/2026/07/06/翻身拍背@2x_20260706195627A001.png(String), 无(String), 1(Integer), 2026-07-06 19:56:35.0(Timestamp), 2026-07-06 19:59:01.128(Timestamp), 84(Long) | |
19:59:01.137 [http-nio-8080-exec-16] DEBUG c.r.s.m.N.updateNursingProject - [debug,135] - <== Updates: 1 | |
19:59:01.151 [schedule-pool-2] DEBUG c.r.s.m.S.insertOperlog - [debug,135] - ==> Preparing: insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, sysdate()) | |
19:59:01.152 [schedule-pool-2] DEBUG c.r.s.m.S.insertOperlog - [debug,135] - ==> Parameters: 护理项目(String), 2(Integer), com.ruoyi.serve.controller.NursingProjectController.edit()(String), PUT(String), 1(Integer), admin(String), 研发部门(String), /serve/project(String), 127.0.0.1(String), 内网IP(String), {"createTime":"2026-07-06 19:56:35","id":84,"image":"/profile/upload/2026/07/06/翻身拍背@2x_20260706195627A001.png","name":"3","nursingRequirement":"无","orderNo":2,"params":{},"price":1,"status":1,"unit":"1","updateTime":"2026-07-06 19:59:01"} (String), {"msg":"操作成功","code":200}(String), 0(Integer), null, 9(Long) | |
19:59:01.158 [schedule-pool-2] DEBUG c.r.s.m.S.insertOperlog - [debug,135] - <== Updates: 1 | |
19:59:01.163 [http-nio-8080-exec-17] DEBUG c.r.s.m.N.selectNursingProjectList_COUNT - [debug,135] - ==> Preparing: SELECT count(0) FROM nursing_project | |
19:59:01.164 [http-nio-8080-exec-17] DEBUG c.r.s.m.N.selectNursingProjectList_COUNT - [debug,135] - ==> Parameters: | |
19:59:01.172 [http-nio-8080-exec-17] DEBUG c.r.s.m.N.selectNursingProjectList_COUNT - [debug,135] - <== Total: 1 | |
19:59:01.175 [http-nio-8080-exec-17] DEBUG c.r.s.m.N.selectNursingProjectList - [debug,135] - ==> Preparing: select id, name, order_no, unit, price, image, nursing_requirement, status, create_by, update_by, remark, create_time, update_time from nursing_project LIMIT ? | |
19:59:01.176 [http-nio-8080-exec-17] DEBUG c.r.s.m.N.selectNursingProjectList - [debug,135] - ==> Parameters: 10(Integer) | |
19:59:01.179 [http-nio-8080-exec-17] DEBUG c.r.s.m.N.selectNursingProjectList - [debug,135] - <== Total: 1 |
伟大,无需多言。
# 集成 Knife4j 实现 Swagger 文档增强
官方文档:https://doc.ruoyi.vip/ruoyi-vue/document/cjjc.html# 集成 knife4j 实现 swagger 文档增强
ruoyi-springboot3/springdoc 版本 用 knife4j-openapi3-jakarta-spring-boot-starter 依赖
<!-- ruoyi-springboot3 /springdoc knife4j 配置 --> | |
<dependency> | |
<groupId>com.github.xiaoymin</groupId> | |
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> | |
<version>4.4.0</version> | |
</dependency> |
knife4j 简单配置
knife4j: | |
enable: true | |
production: false | |
basic: | |
enable: false | |
username: ruoyi | |
password: 123456 | |
setting: | |
swagger-model-name: 实体类列表 |
配置静态资源 /webjars/**, /doc.html 匿名访问
.requestMatchers("/webjars/**", "/doc.html").permitAll() |
修改 ry-ui\views\tool\swagger\index.vue 跳转地址
src: process.env.VUE_APP_BASE_API + "/doc.html |
# Velocity 模板引擎
Apache Velocity 是一款基于 Java 的后端模板引擎,诞生于 Apache 软件基金会。它提供了一套简单而强大的模板语言(VTL,Velocity Template Language),核心设计理念是强制分离业务逻辑与视图表现,允许将 Java 对象中的数据无缝渲染成 HTML、XML、SQL、JSON 或纯文本等任意文本格式。
虽然在前端渲染领域它已非主流,但在后端代码生成、自动化脚本生成、邮件模板等场景中,它依然是 Java 生态中的经典利器。
在若依框架中,生成代码的模块是 ruoyi-generator 模块
首先让我们来简单体验一下模板引擎:
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>velocity快速入门</title> | |
</head> | |
<body> | |
<h3>心怀梦想,坚持不懈,成功即在前方。加油少年!</h3> | |
</body> | |
</html> |
我们把这里替换一下:
${message} 这是一个动态变量,方便动态填充数据。
public class VelocityDemoTest { | |
public static void main(String[] args) throws IOException { | |
System.out.println("Hello, Velocity!"); | |
Properties p = new Properties(); | |
//velocity 资源加载器,告诉模板引擎到类路径下寻找资源(比如模板) | |
p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); | |
// 定义字符集 | |
p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); | |
// 初始化 velocity 引擎 | |
Velocity.init(p); | |
// 创建 velocity 上下文对象 | |
VelocityContext context = new VelocityContext(); | |
// 数据模型,这里的 key 需要跟模板中的变量对应上,不然填充不了数据 | |
context.put("message", "加油朋友!!!"); | |
// 获取模板 | |
Template template = Velocity.getTemplate("vms/index.html.vm", "UTF-8"); | |
// 输出 | |
FileWriter fileWriter = new FileWriter("zzyl-generator\\src\\main\\resources\\index.html"); | |
// 合并模板和数据模型 | |
template.merge(context, fileWriter); | |
// 关闭流 | |
fileWriter.close(); | |
} | |
} |
# 在 RuoYi 中集成 Lombok
# 在父工程中添加依赖
版本交给根 pom 管理:
在 common 中添加依赖,其他模块如需要用,只需要依赖 common 即可:
# 修改 vm 模板
domain:
package ${packageName}.domain; | |
#foreach ($import in $importList) | |
import ${import}; | |
#end | |
import com.baomidou.mybatisplus.annotation.IdType; | |
import com.baomidou.mybatisplus.annotation.TableField; | |
import com.baomidou.mybatisplus.annotation.TableId; | |
import com.baomidou.mybatisplus.annotation.TableName; | |
import com.fasterxml.jackson.annotation.JsonFormat; | |
import lombok.Data; | |
import lombok.AllArgsConstructor; | |
import lombok.NoArgsConstructor; | |
import lombok.ToString; | |
import com.zzyl.common.annotation.Excel; | |
#if($table.crud || $table.sub) | |
import com.zzyl.common.core.domain.BaseEntity; | |
#elseif($table.tree) | |
import com.zzyl.common.core.domain.TreeEntity; | |
#end | |
/** | |
* ${functionName} 对象 ${tableName} | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
#if($table.crud || $table.sub) | |
#set($Entity="BaseEntity") | |
#elseif($table.tree) | |
#set($Entity="TreeEntity") | |
#end | |
@Data | |
@NoArgsConstructor | |
@AllArgsConstructor | |
@ToString(callSuper = true) | |
@TableName("${tableName}") | |
public class ${ClassName} extends ${Entity} | |
{ | |
private static final long serialVersionUID = 1L; | |
#foreach ($column in $columns) | |
#if(!$table.isSuperColumn($column.javaField)) | |
/** $column.columnComment */ | |
#if($column.columnName == $pkColumn.columnName) | |
#if($pkColumn.increment) | |
@TableId(type = IdType.AUTO) | |
#end | |
#end | |
#if($column.list) | |
#set($parentheseIndex=$column.columnComment.indexOf("(")) | |
#if($parentheseIndex != -1) | |
#set($comment=$column.columnComment.substring(0, $parentheseIndex)) | |
#else | |
#set($comment=$column.columnComment) | |
#end | |
#if($parentheseIndex != -1) | |
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") | |
#elseif($column.javaType == 'Date') | |
@JsonFormat(pattern = "yyyy-MM-dd") | |
@Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") | |
#else | |
@Excel(name = "${comment}") | |
#end | |
#end | |
private $column.javaType $column.javaField; | |
#end | |
#end | |
#if($table.sub) | |
/** $table.subTable.functionName 信息 */ | |
@TableField(exist = false) | |
private List<${subClassName}> ${subclassName}List; | |
#end | |
} |
sub-domain:
package ${packageName}.domain; | |
#foreach ($import in $subImportList) | |
import ${import}; | |
#end | |
import com.baomidou.mybatisplus.annotation.IdType; | |
import com.baomidou.mybatisplus.annotation.TableId; | |
import com.baomidou.mybatisplus.annotation.TableName; | |
import com.fasterxml.jackson.annotation.JsonFormat; | |
import lombok.Data; | |
import lombok.AllArgsConstructor; | |
import lombok.NoArgsConstructor; | |
import lombok.ToString; | |
import com.zzyl.common.annotation.Excel; | |
import com.zzyl.common.core.domain.BaseEntity; | |
/** | |
* ${subTable.functionName} 对象 ${subTableName} | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
@Data | |
@NoArgsConstructor | |
@AllArgsConstructor | |
@ToString(callSuper = true) | |
@TableName("${subTableName}") | |
public class ${subClassName} extends BaseEntity | |
{ | |
private static final long serialVersionUID = 1L; | |
#foreach ($column in $subTable.columns) | |
#if(!$table.isSuperColumn($column.javaField)) | |
/** $column.columnComment */ | |
#if($column.pk) | |
@TableId(type = IdType.AUTO) | |
#end | |
#if($column.list) | |
#set($parentheseIndex=$column.columnComment.indexOf("(")) | |
#if($parentheseIndex != -1) | |
#set($comment=$column.columnComment.substring(0, $parentheseIndex)) | |
#else | |
#set($comment=$column.columnComment) | |
#end | |
#if($parentheseIndex != -1) | |
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") | |
#elseif($column.javaType == 'Date') | |
@JsonFormat(pattern = "yyyy-MM-dd") | |
@Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") | |
#else | |
@Excel(name = "${comment}") | |
#end | |
#end | |
private $column.javaType $column.javaField; | |
#end | |
#end | |
} |
下面是他生成的内容:
package com.zzyl.nursing.domain; | |
import java.math.BigDecimal; | |
import lombok.Data; | |
import lombok.AllArgsConstructor; | |
import lombok.NoArgsConstructor; | |
import lombok.ToString; | |
import com.zzyl.common.annotation.Excel; | |
import com.zzyl.common.core.domain.BaseEntity; | |
/** | |
* 护理项目对象 nursing_project | |
* | |
* @author ruoyi | |
* @date 2026-07-08 | |
*/ | |
@Data | |
@NoArgsConstructor | |
@AllArgsConstructor | |
@ToString(callSuper = true) | |
public class NursingProject extends BaseEntity | |
{ | |
private static final long serialVersionUID = 1L; | |
/** 编号 */ | |
private Long id; | |
/** 名称 */ | |
@Excel(name = "名称") | |
private String name; | |
/** 排序号 */ | |
@Excel(name = "排序号") | |
private Integer orderNo; | |
/** 单位 */ | |
@Excel(name = "单位") | |
private String unit; | |
/** 价格 */ | |
@Excel(name = "价格") | |
private BigDecimal price; | |
/** 图片 */ | |
@Excel(name = "图片") | |
private String image; | |
/** 护理要求 */ | |
@Excel(name = "护理要求") | |
private String nursingRequirement; | |
/** 状态(0:禁用,1:启用) */ | |
@Excel(name = "状态", readConverterExp = "0=:禁用,1:启用") | |
private Integer status; | |
} |
经过此时一切正常。
# 在 RuoYi 中集成 MyBatis-Plus
本次使用的 SpringBoot 框架为 4.0.0 + 版本的,所以这里的集成会与之前略有不同。
# 添加依赖
版本交给根 pom 管理:
<dependency> | |
<groupId>com.baomidou</groupId> | |
<artifactId>mybatis-plus-spring-boot4-starter</artifactId> | |
<version>${mybatis-plus-spring-boot.version}</version> | |
</dependency> | |
<dependency> | |
<groupId>com.baomidou</groupId> | |
<artifactId>mybatis-plus-jsqlparser</artifactId> | |
<version>${mybatis-plus-spring-boot.version}</version> | |
</dependency> |
在 common 中启用:
<dependency> | |
<groupId>com.baomidou</groupId> | |
<artifactId>mybatis-plus-spring-boot4-starter</artifactId> | |
</dependency> | |
<dependency> | |
<groupId>com.baomidou</groupId> | |
<artifactId>mybatis-plus-jsqlparser</artifactId> | |
</dependency> |
appliation.yml 文件中新增 MyBatisPlus 配置,同时删除 Mybatis 相关的配置
# MyBatisPlus 配置 | |
mybatis-plus: | |
# 搜索指定包别名 | |
typeAliasesPackage: com.zzyl.**.domain | |
# 配置 mapper 的扫描,找到所有的 mapper.xml 映射文件 | |
mapperLocations: classpath*:mapper/**/*Mapper.xml | |
# 全局配置 | |
global-config: | |
db-config: | |
id-type: auto #id 生成策略为自增 | |
configuration: | |
map-underscore-to-camel-case: true #字段与属性,自动转换为驼峰命名 |
新增核心配置类,删除 MyBatisConfig
package com.zzyl.framework.config; | |
import com.baomidou.mybatisplus.annotation.DbType; | |
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | |
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; | |
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; | |
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
/** | |
* Mybatis Plus 配置 | |
* | |
* @author ruoyi | |
*/ | |
@EnableTransactionManagement(proxyTargetClass = true) | |
@Configuration | |
public class MybatisPlusConfig { | |
@Bean | |
public MybatisPlusInterceptor mybatisPlusInterceptor() { | |
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | |
// 分页插件 | |
interceptor.addInnerInterceptor(paginationInnerInterceptor()); | |
// 乐观锁插件 | |
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); | |
// 阻断插件 | |
interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); | |
return interceptor; | |
} | |
/** | |
* 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html | |
*/ | |
public PaginationInnerInterceptor paginationInnerInterceptor() { | |
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); | |
// 设置数据库类型为 mysql | |
paginationInnerInterceptor.setDbType(DbType.MYSQL); | |
// 设置最大单页限制数量,默认 500 条,-1 不受限制 | |
paginationInnerInterceptor.setMaxLimit(-1L); | |
return paginationInnerInterceptor; | |
} | |
/** | |
* 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html | |
*/ | |
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { | |
return new OptimisticLockerInnerInterceptor(); | |
} | |
/** | |
* 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html | |
*/ | |
public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { | |
return new BlockAttackInnerInterceptor(); | |
} | |
} |
测试一切正常运行,因为 mybatis-plu 是完全兼容 mybatis 的。
# 改造 Mapper
添加 @Mapper 注解继承 BaseMapper
package ${packageName}.mapper; | |
import java.util.List; | |
import ${packageName}.domain.${ClassName}; | |
#if($table.sub) | |
import ${packageName}.domain.${subClassName}; | |
#end | |
import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
import org.apache.ibatis.annotations.Mapper; | |
/** | |
* ${functionName} Mapper 接口 | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
@Mapper | |
public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> | |
{ | |
/** | |
* 查询 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return ${functionName} | |
*/ | |
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
/** | |
* 查询 ${functionName} 列表 | |
* | |
* @param ${className} ${functionName} | |
* @return ${functionName} 集合 | |
*/ | |
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); | |
#if($table.sub) | |
/** | |
* 批量删除 ${subTable.functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的数据主键集合 | |
* @return 结果 | |
*/ | |
public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); | |
/** | |
* 批量新增 ${subTable.functionName} | |
* | |
* @param ${subclassName} List ${subTable.functionName} 列表 | |
* @return 结果 | |
*/ | |
public int batch${subClassName}(List<${subClassName}> ${subclassName}List); | |
/** | |
* 通过 ${functionName} 主键删除 ${subTable.functionName} 信息 | |
* | |
* @param ${pkColumn.javaField} ${functionName} ID | |
* @return 结果 | |
*/ | |
public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); | |
#end | |
} |
# 改造 IService
继承 IService
package ${packageName}.service; | |
import java.util.List; | |
import ${packageName}.domain.${ClassName}; | |
import com.baomidou.mybatisplus.extension.service.IService; | |
/** | |
* ${functionName} Service 接口 | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
public interface I${ClassName}Service extends IService<${ClassName}> | |
{ | |
/** | |
* 查询 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return ${functionName} | |
*/ | |
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
/** | |
* 查询 ${functionName} 列表 | |
* | |
* @param ${className} ${functionName} | |
* @return ${functionName} 集合 | |
*/ | |
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); | |
/** | |
* 新增 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
public int insert${ClassName}(${ClassName} ${className}); | |
/** | |
* 修改 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
public int update${ClassName}(${ClassName} ${className}); | |
/** | |
* 批量删除 ${functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的 ${functionName} 主键集合 | |
* @return 结果 | |
*/ | |
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); | |
/** | |
* 删除 ${functionName} 信息 | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return 结果 | |
*/ | |
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
} |
# 改造 ServiceImpl
package ${packageName}.service.impl; | |
import java.util.List; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
#if($table.sub) | |
import java.util.ArrayList; | |
import com.zzyl.common.utils.StringUtils; | |
import org.springframework.transaction.annotation.Transactional; | |
import ${packageName}.domain.${subClassName}; | |
#end | |
import ${packageName}.mapper.${ClassName}Mapper; | |
import ${packageName}.domain.${ClassName}; | |
import ${packageName}.service.I${ClassName}Service; | |
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | |
import java.util.Arrays; | |
/** | |
* ${functionName} Service 业务层处理 | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
@Service | |
public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}> implements I${ClassName}Service | |
{ | |
@Autowired | |
private ${ClassName}Mapper ${className}Mapper; | |
/** | |
* 查询 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return ${functionName} | |
*/ | |
@Override | |
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) | |
{ | |
return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); | |
} | |
/** | |
* 查询 ${functionName} 列表 | |
* | |
* @param ${className} ${functionName} | |
* @return ${functionName} | |
*/ | |
@Override | |
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) | |
{ | |
return ${className}Mapper.select${ClassName}List(${className}); | |
} | |
/** | |
* 新增 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int insert${ClassName}(${ClassName} ${className}) | |
{ | |
#if($table.sub) | |
int rows = save(${className}) ? 1 : 0; | |
insert${subClassName}(${className}); | |
return rows; | |
#else | |
return save(${className}) ? 1 : 0; | |
#end | |
} | |
/** | |
* 修改 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int update${ClassName}(${ClassName} ${className}) | |
{ | |
#if($table.sub) | |
${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); | |
insert${subClassName}(${className}); | |
#end | |
return updateById(${className}) ? 1 : 0; | |
} | |
/** | |
* 批量删除 ${functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的 ${functionName} 主键 | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) | |
{ | |
#if($table.sub) | |
${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); | |
#end | |
return removeByIds(Arrays.asList(${pkColumn.javaField}s)) ? 1 : 0; | |
} | |
/** | |
* 删除 ${functionName} 信息 | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) | |
{ | |
#if($table.sub) | |
${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); | |
#end | |
return removeById(${pkColumn.javaField}) ? 1 : 0; | |
} | |
#if($table.sub) | |
/** | |
* 新增 ${subTable.functionName} 信息 | |
* | |
* @param ${className} ${functionName} 对象 | |
*/ | |
public void insert${subClassName}(${ClassName} ${className}) | |
{ | |
List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); | |
${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); | |
if (StringUtils.isNotNull(${subclassName}List)) | |
{ | |
List<${subClassName}> list = new ArrayList<${subClassName}>(); | |
for (${subClassName} ${subclassName} : ${subclassName}List) | |
{ | |
${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); | |
list.add(${subclassName}); | |
} | |
if (list.size() > 0) | |
{ | |
${className}Mapper.batch${subClassName}(list); | |
} | |
} | |
} | |
#end | |
} |
# 其他修改
因为 MP 会一股脑儿把实体类的所有字段都当成表字段去处理,但 BaseEntity 里定义的通用字段(比如操作人、备注)在某些表里其实是没有对应列的。所以必须在这些字段头上加上 @TableField(exist = false) ,明确告诉 MP:“别管这俩,数据库里没这列”,这样生成 SQL 时才会忽略它们,项目才能正常跑起来。
package com.zzyl.common.core.domain; | |
import com.baomidou.mybatisplus.annotation.TableField; | |
import com.fasterxml.jackson.annotation.JsonFormat; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import com.fasterxml.jackson.annotation.JsonInclude; | |
import java.io.Serializable; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* Entity 基类 | |
* | |
* @author ruoyi | |
*/ | |
public class BaseEntity implements Serializable { | |
private static final long serialVersionUID = 1L; | |
/** | |
* 搜索值 | |
*/ | |
@JsonIgnore | |
@TableField(exist = false) | |
private String searchValue; | |
/** | |
* 创建者 | |
*/ | |
private String createBy; | |
/** | |
* 创建时间 | |
*/ | |
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | |
private Date createTime; | |
/** | |
* 更新者 | |
*/ | |
private String updateBy; | |
/** | |
* 更新时间 | |
*/ | |
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | |
private Date updateTime; | |
/** | |
* 备注 | |
*/ | |
private String remark; | |
/** | |
* 请求参数 | |
*/ | |
@JsonInclude(JsonInclude.Include.NON_EMPTY) | |
@TableField(exist = false) | |
private Map<String, Object> params; | |
public String getSearchValue() { | |
return searchValue; | |
} | |
public void setSearchValue(String searchValue) { | |
this.searchValue = searchValue; | |
} | |
public String getCreateBy() { | |
return createBy; | |
} | |
public void setCreateBy(String createBy) { | |
this.createBy = createBy; | |
} | |
public Date getCreateTime() { | |
return createTime; | |
} | |
public void setCreateTime(Date createTime) { | |
this.createTime = createTime; | |
} | |
public String getUpdateBy() { | |
return updateBy; | |
} | |
public void setUpdateBy(String updateBy) { | |
this.updateBy = updateBy; | |
} | |
public Date getUpdateTime() { | |
return updateTime; | |
} | |
public void setUpdateTime(Date updateTime) { | |
this.updateTime = updateTime; | |
} | |
public String getRemark() { | |
return remark; | |
} | |
public void setRemark(String remark) { | |
this.remark = remark; | |
} | |
public Map<String, Object> getParams() { | |
if (params == null) { | |
params = new HashMap<>(); | |
} | |
return params; | |
} | |
public void setParams(Map<String, Object> params) { | |
this.params = params; | |
} | |
} |
经过测试一切正常,下面开始通过修改 vm 来一劳永逸。
# 修改 vm 模板
思想是一样的,就是变成模板了。
Mapper:
package ${packageName}.mapper; | |
import java.util.List; | |
import ${packageName}.domain.${ClassName}; | |
#if($table.sub) | |
import ${packageName}.domain.${subClassName}; | |
#end | |
import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
import org.apache.ibatis.annotations.Mapper; | |
/** | |
* ${functionName} Mapper 接口 | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
@Mapper | |
public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> | |
{ | |
/** | |
* 查询 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return ${functionName} | |
*/ | |
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
/** | |
* 查询 ${functionName} 列表 | |
* | |
* @param ${className} ${functionName} | |
* @return ${functionName} 集合 | |
*/ | |
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); | |
/** | |
* 新增 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
public int insert${ClassName}(${ClassName} ${className}); | |
/** | |
* 修改 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
public int update${ClassName}(${ClassName} ${className}); | |
/** | |
* 删除 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return 结果 | |
*/ | |
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
/** | |
* 批量删除 ${functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的数据主键集合 | |
* @return 结果 | |
*/ | |
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); | |
#if($table.sub) | |
/** | |
* 批量删除 ${subTable.functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的数据主键集合 | |
* @return 结果 | |
*/ | |
public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); | |
/** | |
* 批量新增 ${subTable.functionName} | |
* | |
* @param ${subclassName} List ${subTable.functionName} 列表 | |
* @return 结果 | |
*/ | |
public int batch${subClassName}(List<${subClassName}> ${subclassName}List); | |
/** | |
* 通过 ${functionName} 主键删除 ${subTable.functionName} 信息 | |
* | |
* @param ${pkColumn.javaField} ${functionName} ID | |
* @return 结果 | |
*/ | |
public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); | |
#end | |
} |
IService:
package ${packageName}.service; | |
import java.util.List; | |
import ${packageName}.domain.${ClassName}; | |
import com.baomidou.mybatisplus.extension.service.IService; | |
/** | |
* ${functionName} Service 接口 | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
public interface I${ClassName}Service extends IService<${ClassName}> | |
{ | |
/** | |
* 查询 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return ${functionName} | |
*/ | |
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
/** | |
* 查询 ${functionName} 列表 | |
* | |
* @param ${className} ${functionName} | |
* @return ${functionName} 集合 | |
*/ | |
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); | |
/** | |
* 新增 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
public int insert${ClassName}(${ClassName} ${className}); | |
/** | |
* 修改 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
public int update${ClassName}(${ClassName} ${className}); | |
/** | |
* 批量删除 ${functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的 ${functionName} 主键集合 | |
* @return 结果 | |
*/ | |
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); | |
/** | |
* 删除 ${functionName} 信息 | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return 结果 | |
*/ | |
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); | |
} |
ServiceImpl:
package ${packageName}.service.impl; | |
import java.util.List; | |
#foreach ($column in $columns) | |
#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') | |
import com.zzyl.common.utils.DateUtils; | |
#break | |
#end | |
#end | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
#if($table.sub) | |
import java.util.ArrayList; | |
import com.zzyl.common.utils.StringUtils; | |
import org.springframework.transaction.annotation.Transactional; | |
import ${packageName}.domain.${subClassName}; | |
#end | |
import ${packageName}.mapper.${ClassName}Mapper; | |
import ${packageName}.domain.${ClassName}; | |
import ${packageName}.service.I${ClassName}Service; | |
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | |
import java.util.Arrays; | |
/** | |
* ${functionName} Service 业务层处理 | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
@Service | |
public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}> implements I${ClassName}Service | |
{ | |
@Autowired | |
private ${ClassName}Mapper ${className}Mapper; | |
/** | |
* 查询 ${functionName} | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return ${functionName} | |
*/ | |
@Override | |
public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) | |
{ | |
## return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); | |
return getById(${pkColumn.javaField}); | |
} | |
/** | |
* 查询 ${functionName} 列表 | |
* | |
* @param ${className} ${functionName} | |
* @return ${functionName} | |
*/ | |
@Override | |
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) | |
{ | |
return ${className}Mapper.select${ClassName}List(${className}); | |
} | |
/** | |
* 新增 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int insert${ClassName}(${ClassName} ${className}) | |
{ | |
#foreach ($column in $columns) | |
#if($column.javaField == 'createTime') | |
${className}.setCreateTime(DateUtils.getNowDate()); | |
#end | |
#end | |
#if($table.sub) | |
int rows = ${className}Mapper.insert${ClassName}(${className}); | |
insert${subClassName}(${className}); | |
return rows; | |
#else | |
## return ${className}Mapper.insert${ClassName}(${className}); | |
return save(${className}) ? 1 : 0; | |
#end | |
} | |
/** | |
* 修改 ${functionName} | |
* | |
* @param ${className} ${functionName} | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int update${ClassName}(${ClassName} ${className}) | |
{ | |
#foreach ($column in $columns) | |
#if($column.javaField == 'updateTime') | |
${className}.setUpdateTime(DateUtils.getNowDate()); | |
#end | |
#end | |
#if($table.sub) | |
${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); | |
insert${subClassName}(${className}); | |
#end | |
## return ${className}Mapper.update${ClassName}(${className}); | |
return updateById(${className}) ? 1 : 0; | |
} | |
/** | |
* 批量删除 ${functionName} | |
* | |
* @param ${pkColumn.javaField} s 需要删除的 ${functionName} 主键 | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) | |
{ | |
#if($table.sub) | |
${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); | |
#end | |
## return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); | |
return removeByIds(Arrays.asList(${pkColumn.javaField}s)) ? 1 : 0; | |
} | |
/** | |
* 删除 ${functionName} 信息 | |
* | |
* @param ${pkColumn.javaField} ${functionName} 主键 | |
* @return 结果 | |
*/ | |
#if($table.sub) | |
@Transactional | |
#end | |
@Override | |
public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) | |
{ | |
#if($table.sub) | |
${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); | |
#end | |
## return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); | |
return removeById(${pkColumn.javaField}) ? 1 : 0; | |
} | |
#if($table.sub) | |
/** | |
* 新增 ${subTable.functionName} 信息 | |
* | |
* @param ${className} ${functionName} 对象 | |
*/ | |
public void insert${subClassName}(${ClassName} ${className}) | |
{ | |
List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); | |
${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); | |
if (StringUtils.isNotNull(${subclassName}List)) | |
{ | |
List<${subClassName}> list = new ArrayList<${subClassName}>(); | |
for (${subClassName} ${subclassName} : ${subclassName}List) | |
{ | |
${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); | |
list.add(${subclassName}); | |
} | |
if (list.size() > 0) | |
{ | |
${className}Mapper.batch${subClassName}(list); | |
} | |
} | |
} | |
#end | |
} |
# MP 字段自动填充
-
在实体类中,使用
@TableField注解,来标明哪些字段是需要自动填充的,并且需要指定填充策略package com.zzyl.common.core.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/*** Entity 基类
*
* @author ruoyi
*/
@Schema(description = "Entity基类")
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/*** 搜索值
*/
@JsonIgnore@Schema(description = "搜索值")
@TableField(exist = false)
private String searchValue;
/*** 创建者
*/
@Schema(description = "创建者")
@TableField(fill = FieldFill.INSERT)
private String createBy;
/*** 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/*** 更新者
*/
@Schema(description = "更新者")
@TableField(fill = FieldFill.UPDATE)
private String updateBy;
/*** 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private Date updateTime;
/*** 备注
*/
private String remark;
/*** 请求参数
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private Map<String, Object> params;
public String getSearchValue() {
return searchValue;
}public void setSearchValue(String searchValue) {
this.searchValue = searchValue;
}public String getCreateBy() {
return createBy;
}public void setCreateBy(String createBy) {
this.createBy = createBy;
}public Date getCreateTime() {
return createTime;
}public void setCreateTime(Date createTime) {
this.createTime = createTime;
}public String getUpdateBy() {
return updateBy;
}public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}public Date getUpdateTime() {
return updateTime;
}public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}public String getRemark() {
return remark;
}public void setRemark(String remark) {
this.remark = remark;
}public Map<String, Object> getParams() {
if (params == null) {
params = new HashMap<>();
}return params;
}public void setParams(Map<String, Object> params) {
this.params = params;
}} -
framework 模块中新增 MyMetaObjectHandler 来处理字段自动填充
package com.zzyl.framework.interceptor;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.zzyl.common.core.domain.model.LoginUser;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.common.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {
@Overridepublic void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, DateUtils.getNowDate());
this.strictInsertFill(metaObject, "createBy", String.class, String.valueOf(getLoginUser()));
}@Overridepublic void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", String.valueOf(getLoginUser()), metaObject);
// this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());// this.strictUpdateFill(metaObject, "updateBy", String.class, String.valueOf(getLoginUser()));}/*** 获取当前登录人的 ID
*
* @return 登录人 ID
*/
public Long getLoginUser() {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null) {
return loginUser.getUserId();
}return 1L;
}}
# Swagger 的模板化
上一步我们集成了 Swagger,现在我们来更新 Controller 模板:
package ${packageName}.controller; | |
import java.util.List; | |
import jakarta.servlet.http.HttpServletResponse; | |
import org.springframework.security.access.prepost.PreAuthorize; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.PostMapping; | |
import org.springframework.web.bind.annotation.PutMapping; | |
import org.springframework.web.bind.annotation.DeleteMapping; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestBody; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
import com.zzyl.common.annotation.Log; | |
import com.zzyl.common.core.controller.BaseController; | |
import com.zzyl.common.core.domain.AjaxResult; | |
import com.zzyl.common.enums.BusinessType; | |
import ${packageName}.domain.${ClassName}; | |
import ${packageName}.service.I${ClassName}Service; | |
import com.zzyl.common.utils.poi.ExcelUtil; | |
#if($table.crud || $table.sub) | |
import com.zzyl.common.core.page.TableDataInfo; | |
#elseif($table.tree) | |
#end | |
import io.swagger.v3.oas.annotations.Operation; | |
import io.swagger.v3.oas.annotations.Parameter; | |
import io.swagger.v3.oas.annotations.tags.Tag; | |
/** | |
* ${functionName}Controller | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
@Tag(name = "${functionName}接口") | |
@RestController | |
@RequestMapping("/${moduleName}/${businessName}") | |
public class ${ClassName}Controller extends BaseController | |
{ | |
@Autowired | |
private I${ClassName}Service ${className}Service; | |
/** | |
* 查询 ${functionName} 列表 | |
*/ | |
@PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") | |
@Operation(summary = "查询${functionName}列表") | |
@GetMapping("/list") | |
#if($table.crud || $table.sub) | |
public TableDataInfo list(${ClassName} ${className}) | |
{ | |
startPage(); | |
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); | |
return getDataTable(list); | |
} | |
#elseif($table.tree) | |
public AjaxResult list(${ClassName} ${className}) | |
{ | |
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); | |
return success(list); | |
} | |
#end | |
/** | |
* 导出 ${functionName} 列表 | |
*/ | |
@PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") | |
@Log(title = "${functionName}", businessType = BusinessType.EXPORT) | |
@Operation(summary = "导出${functionName}列表") | |
@PostMapping("/export") | |
public void export(HttpServletResponse response, ${ClassName} ${className}) | |
{ | |
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); | |
ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); | |
util.exportExcel(response, list, "${functionName}数据"); | |
} | |
/** | |
* 获取 ${functionName} 详细信息 | |
*/ | |
@PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") | |
@Operation(summary = "获取${functionName}详细信息") | |
@GetMapping(value = "/{${pkColumn.javaField}}") | |
public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) | |
{ | |
return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); | |
} | |
/** | |
* 新增 ${functionName} | |
*/ | |
@PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") | |
@Log(title = "${functionName}", businessType = BusinessType.INSERT) | |
@Operation(summary = "新增${functionName}") | |
@PostMapping | |
public AjaxResult add(@RequestBody ${ClassName} ${className}) | |
{ | |
return toAjax(${className}Service.insert${ClassName}(${className})); | |
} | |
/** | |
* 修改 ${functionName} | |
*/ | |
@PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") | |
@Log(title = "${functionName}", businessType = BusinessType.UPDATE) | |
@Operation(summary = "修改${functionName}") | |
@PutMapping | |
public AjaxResult edit(@RequestBody ${ClassName} ${className}) | |
{ | |
return toAjax(${className}Service.update${ClassName}(${className})); | |
} | |
/** | |
* 删除 ${functionName} | |
*/ | |
@PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") | |
@Log(title = "${functionName}", businessType = BusinessType.DELETE) | |
@Operation(summary = "删除${functionName}") | |
@DeleteMapping("/{${pkColumn.javaField}s}") | |
public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) | |
{ | |
return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); | |
} | |
} |
# 将 Date 类型改为 LocalDateTime
Date 类型与 LocalDateTime 的区别?
- 不可变性与线程安全性
- Date 一旦创建 Date 对象,可以通过 setTime (long time) 进行修改,多线程下存在线程安全问题
- LocalDateTime 是不可变的,线程安全
- 时间精度
- Date 可以表示到毫秒的时间值
- LocalDateTime 可以表示到纳秒的时间值
- 易用性:LocalDateTime 的 API 设计的更现代化,易于使用
# 前端的修改
打开前端项目,找到 src/views/tool/gen/editTable.vue 文件,在这个文件添加中一行代码:
<el-option label="LocalDataTime" value="LocalDataTime" />
位置:
<template #default="scope">
<el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" />
<el-option label="String" value="String" />
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" />
<el-option label="LocalDataTime" value="LocalDataTime" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
# 修改后端
打开 VelocityUtils 类,这里面有处理导入包的逻辑,找到 getImportList 方法,在其中添加对应的包导入:
package com.zzyl.generator.util; | |
import java.util.ArrayList; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
import org.apache.velocity.VelocityContext; | |
import com.alibaba.fastjson2.JSON; | |
import com.alibaba.fastjson2.JSONObject; | |
import com.zzyl.common.constant.GenConstants; | |
import com.zzyl.common.utils.DateUtils; | |
import com.zzyl.common.utils.StringUtils; | |
import com.zzyl.generator.domain.GenTable; | |
import com.zzyl.generator.domain.GenTableColumn; | |
/** | |
* 模板处理工具类 | |
* | |
* @author ruoyi | |
*/ | |
public class VelocityUtils | |
{ | |
/** 项目空间路径 */ | |
private static final String PROJECT_PATH = "main/java"; | |
/** mybatis 空间路径 */ | |
private static final String MYBATIS_PATH = "main/resources/mapper"; | |
/** 默认上级菜单,系统工具 */ | |
private static final String DEFAULT_PARENT_MENU_ID = "3"; | |
/** Vue3 Element Plus 模版 */ | |
private static final String ELEMENT_PLUS = "element-plus"; | |
/** Vue3 Element Plus TypeScript 模版 */ | |
private static final String ELEMENT_PLUS_TYPESSRIPT = "element-plus-typescript"; | |
/** | |
* 设置模板变量信息 | |
* | |
* @return 模板列表 | |
*/ | |
public static VelocityContext prepareContext(GenTable genTable) | |
{ | |
String moduleName = genTable.getModuleName(); | |
String businessName = genTable.getBusinessName(); | |
String packageName = genTable.getPackageName(); | |
String tplCategory = genTable.getTplCategory(); | |
String functionName = genTable.getFunctionName(); | |
VelocityContext velocityContext = new VelocityContext(); | |
velocityContext.put("tplCategory", genTable.getTplCategory()); | |
velocityContext.put("tableName", genTable.getTableName()); | |
velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); | |
velocityContext.put("ClassName", genTable.getClassName()); | |
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); | |
velocityContext.put("moduleName", genTable.getModuleName()); | |
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); | |
velocityContext.put("businessName", genTable.getBusinessName()); | |
velocityContext.put("basePackage", getPackagePrefix(packageName)); | |
velocityContext.put("packageName", packageName); | |
velocityContext.put("author", genTable.getFunctionAuthor()); | |
velocityContext.put("colSpan", getColSpan(genTable.getFormColNum())); | |
velocityContext.put("datetime", DateUtils.getDate()); | |
velocityContext.put("pkColumn", genTable.getPkColumn()); | |
velocityContext.put("importList", getImportList(genTable)); | |
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); | |
velocityContext.put("columns", genTable.getColumns()); | |
velocityContext.put("table", genTable); | |
velocityContext.put("dicts", getDicts(genTable)); | |
setExtensionsContext(velocityContext, genTable.getOptions()); | |
setMenuVelocityContext(velocityContext, genTable); | |
if (GenConstants.TPL_TREE.equals(tplCategory)) | |
{ | |
setTreeVelocityContext(velocityContext, genTable); | |
} | |
if (GenConstants.TPL_SUB.equals(tplCategory)) | |
{ | |
setSubVelocityContext(velocityContext, genTable); | |
} | |
return velocityContext; | |
} | |
public static void setExtensionsContext(VelocityContext context, String options) | |
{ | |
JSONObject paramsObj = JSONObject.parseObject(options); | |
boolean genView = genView(paramsObj); | |
context.put("genView", genView); | |
} | |
public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) | |
{ | |
String options = genTable.getOptions(); | |
JSONObject paramsObj = JSON.parseObject(options); | |
String parentMenuId = getParentMenuId(paramsObj); | |
context.put("parentMenuId", parentMenuId); | |
} | |
public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) | |
{ | |
String options = genTable.getOptions(); | |
JSONObject paramsObj = JSON.parseObject(options); | |
String treeCode = getTreecode(paramsObj); | |
String treeParentCode = getTreeParentCode(paramsObj); | |
String treeName = getTreeName(paramsObj); | |
context.put("treeCode", treeCode); | |
context.put("treeParentCode", treeParentCode); | |
context.put("treeName", treeName); | |
context.put("expandColumn", getExpandColumn(genTable)); | |
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) | |
{ | |
context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); | |
} | |
if (paramsObj.containsKey(GenConstants.TREE_NAME)) | |
{ | |
context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); | |
} | |
} | |
public static void setSubVelocityContext(VelocityContext context, GenTable genTable) | |
{ | |
GenTable subTable = genTable.getSubTable(); | |
String subTableName = genTable.getSubTableName(); | |
String subTableFkName = genTable.getSubTableFkName(); | |
String subClassName = genTable.getSubTable().getClassName(); | |
String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); | |
context.put("subTable", subTable); | |
context.put("subTableName", subTableName); | |
context.put("subTableFkName", subTableFkName); | |
context.put("subTableFkClassName", subTableFkClassName); | |
context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); | |
context.put("subClassName", subClassName); | |
context.put("subclassName", StringUtils.uncapitalize(subClassName)); | |
context.put("subImportList", getImportList(genTable.getSubTable())); | |
} | |
/** | |
* 获取模板信息 | |
* @param tplCategory 生成的模板 | |
* @param tplWebType 前端类型 | |
* @return 模板列表 | |
*/ | |
public static List<String> getTemplateList(GenTable table) | |
{ | |
String tplWebType = table.getTplWebType(); | |
String tplCategory = table.getTplCategory(); | |
JSONObject paramsObj = JSONObject.parseObject(table.getOptions()); | |
boolean isView = genView(paramsObj); | |
String useWebType = "vm/vue"; | |
String apiTemplate = "vm/js/api.js.vm"; | |
if (StringUtils.equals(ELEMENT_PLUS, tplWebType)) | |
{ | |
useWebType = "vm/vue/v3"; | |
} | |
else if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType)) | |
{ | |
useWebType = "vm/vue/v3ts"; | |
apiTemplate = "vm/ts/api.ts.vm"; | |
} | |
List<String> templates = new ArrayList<String>(); | |
templates.add("vm/java/domain.java.vm"); | |
templates.add("vm/java/mapper.java.vm"); | |
templates.add("vm/java/service.java.vm"); | |
templates.add("vm/java/serviceImpl.java.vm"); | |
templates.add("vm/java/controller.java.vm"); | |
templates.add("vm/xml/mapper.xml.vm"); | |
templates.add("vm/sql/sql.vm"); | |
templates.add(apiTemplate); | |
if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType)) | |
{ | |
templates.add("vm/ts/type.ts.vm"); | |
templates.add("vm/ts/index.ts.vm"); | |
} | |
if (GenConstants.TPL_CRUD.equals(tplCategory)) | |
{ | |
templates.add(useWebType + "/index.vue.vm"); | |
} | |
else if (GenConstants.TPL_TREE.equals(tplCategory)) | |
{ | |
templates.add(useWebType + "/index-tree.vue.vm"); | |
} | |
else if (GenConstants.TPL_SUB.equals(tplCategory)) | |
{ | |
templates.add(useWebType + "/index.vue.vm"); | |
templates.add("vm/java/sub-domain.java.vm"); | |
} | |
if (isView) | |
{ | |
templates.add(useWebType + "/view.vue.vm"); | |
} | |
return templates; | |
} | |
/** | |
* 获取文件名 | |
*/ | |
public static String getFileName(String template, GenTable genTable) | |
{ | |
// 文件名称 | |
String fileName = ""; | |
// 包路径 | |
String packageName = genTable.getPackageName(); | |
// 模块名 | |
String moduleName = genTable.getModuleName(); | |
// 大写类名 | |
String className = genTable.getClassName(); | |
// 业务名称 | |
String businessName = genTable.getBusinessName(); | |
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); | |
String mybatisPath = MYBATIS_PATH + "/" + moduleName; | |
String vuePath = "vue"; | |
if (template.contains("domain.java.vm")) | |
{ | |
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); | |
} | |
if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) | |
{ | |
fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); | |
} | |
else if (template.contains("mapper.java.vm")) | |
{ | |
fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); | |
} | |
else if (template.contains("service.java.vm")) | |
{ | |
fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); | |
} | |
else if (template.contains("serviceImpl.java.vm")) | |
{ | |
fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); | |
} | |
else if (template.contains("controller.java.vm")) | |
{ | |
fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); | |
} | |
else if (template.contains("mapper.xml.vm")) | |
{ | |
fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); | |
} | |
else if (template.contains("sql.vm")) | |
{ | |
fileName = businessName + "Menu.sql"; | |
} | |
else if (template.contains("api.js.vm")) | |
{ | |
fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); | |
} | |
else if (template.contains("api.ts.vm")) | |
{ | |
fileName = StringUtils.format("{}/api/{}/{}.ts", vuePath, moduleName, businessName); | |
} | |
else if (template.contains("type.ts.vm")) | |
{ | |
fileName = StringUtils.format("{}/types/api/{}/{}.ts", vuePath, moduleName, businessName); | |
} | |
else if (template.contains("index.ts.vm")) | |
{ | |
fileName = StringUtils.format("{}/types/api/index-bak.ts", vuePath); | |
} | |
else if (template.contains("index.vue.vm")) | |
{ | |
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); | |
} | |
else if (template.contains("index-tree.vue.vm")) | |
{ | |
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); | |
} | |
else if (template.contains("view.vue.vm")) | |
{ | |
fileName = StringUtils.format("{}/views/{}/{}/view.vue", vuePath, moduleName, businessName); | |
} | |
return fileName; | |
} | |
/** | |
* 获取包前缀 | |
* | |
* @param packageName 包名称 | |
* @return 包前缀名称 | |
*/ | |
public static String getPackagePrefix(String packageName) | |
{ | |
int lastIndex = packageName.lastIndexOf("."); | |
return StringUtils.substring(packageName, 0, lastIndex); | |
} | |
/** | |
* 根据列类型获取导入包 | |
* | |
* @param genTable 业务表对象 | |
* @return 返回需要导入的包列表 | |
*/ | |
public static HashSet<String> getImportList(GenTable genTable) | |
{ | |
List<GenTableColumn> columns = genTable.getColumns(); | |
GenTable subGenTable = genTable.getSubTable(); | |
HashSet<String> importList = new HashSet<String>(); | |
if (StringUtils.isNotNull(subGenTable)) | |
{ | |
importList.add("java.util.List"); | |
} | |
for (GenTableColumn column : columns) | |
{ | |
if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) | |
{ | |
importList.add("java.util.Date"); | |
importList.add("com.fasterxml.jackson.annotation.JsonFormat"); | |
} | |
// 如果字段类型在前端选择的是 LocalDateTime,则导入对应的包 | |
if (!column.isSuperColumn() && GenConstants.TYPE_LOCAL_DATE_TYPE.equals(column.getJavaType())) | |
{ | |
importList.add("java.time.LocalDateTime"); | |
// 导入这个是为了格式化日期 | |
importList.add("com.fasterxml.jackson.annotation.JsonFormat"); | |
} | |
else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) | |
{ | |
importList.add("java.math.BigDecimal"); | |
} | |
} | |
return importList; | |
} | |
/** | |
* 根据列类型获取字典组 | |
* | |
* @param genTable 业务表对象 | |
* @return 返回字典组 | |
*/ | |
public static String getDicts(GenTable genTable) | |
{ | |
List<GenTableColumn> columns = genTable.getColumns(); | |
Set<String> dicts = new HashSet<String>(); | |
addDicts(dicts, columns); | |
if (StringUtils.isNotNull(genTable.getSubTable())) | |
{ | |
List<GenTableColumn> subColumns = genTable.getSubTable().getColumns(); | |
addDicts(dicts, subColumns); | |
} | |
return StringUtils.join(dicts, ", "); | |
} | |
/** | |
* 添加字典列表 | |
* | |
* @param dicts 字典列表 | |
* @param columns 列集合 | |
*/ | |
public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) | |
{ | |
for (GenTableColumn column : columns) | |
{ | |
if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( | |
column.getHtmlType(), | |
new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) | |
{ | |
dicts.add("'" + column.getDictType() + "'"); | |
} | |
} | |
} | |
/** | |
* 获取权限前缀 | |
* | |
* @param moduleName 模块名称 | |
* @param businessName 业务名称 | |
* @return 返回权限前缀 | |
*/ | |
public static String getPermissionPrefix(String moduleName, String businessName) | |
{ | |
return StringUtils.format("{}:{}", moduleName, businessName); | |
} | |
/** | |
* 获取上级菜单 ID 字段 | |
* | |
* @param paramsObj 生成其他选项 | |
* @return 上级菜单 ID 字段 | |
*/ | |
public static String getParentMenuId(JSONObject paramsObj) | |
{ | |
if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) | |
&& StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) | |
{ | |
return paramsObj.getString(GenConstants.PARENT_MENU_ID); | |
} | |
return DEFAULT_PARENT_MENU_ID; | |
} | |
/** | |
* 获取树编码 | |
* | |
* @param paramsObj 生成其他选项 | |
* @return 树编码 | |
*/ | |
public static String getTreecode(JSONObject paramsObj) | |
{ | |
if (paramsObj.containsKey(GenConstants.TREE_CODE)) | |
{ | |
return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); | |
} | |
return StringUtils.EMPTY; | |
} | |
/** | |
* 获取树父编码 | |
* | |
* @param paramsObj 生成其他选项 | |
* @return 树父编码 | |
*/ | |
public static String getTreeParentCode(JSONObject paramsObj) | |
{ | |
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) | |
{ | |
return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); | |
} | |
return StringUtils.EMPTY; | |
} | |
/** | |
* 扩展功能 / 生成详情页 | |
* | |
* @param paramsObj 生成其他选项 | |
* @return 是否生成详细页 | |
*/ | |
public static boolean genView(JSONObject paramsObj) | |
{ | |
if (StringUtils.isNotNull(paramsObj) && paramsObj.containsKey(GenConstants.GEN_VIEW)) | |
{ | |
return paramsObj.getBoolean(GenConstants.GEN_VIEW); | |
} | |
return false; | |
} | |
/** | |
* 获取树名称 | |
* | |
* @param paramsObj 生成其他选项 | |
* @return 树名称 | |
*/ | |
public static String getTreeName(JSONObject paramsObj) | |
{ | |
if (paramsObj.containsKey(GenConstants.TREE_NAME)) | |
{ | |
return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); | |
} | |
return StringUtils.EMPTY; | |
} | |
/** | |
* 获取需要在哪一列上面显示展开按钮 | |
* | |
* @param genTable 业务表对象 | |
* @return 展开按钮列序号 | |
*/ | |
public static int getExpandColumn(GenTable genTable) | |
{ | |
String options = genTable.getOptions(); | |
JSONObject paramsObj = JSON.parseObject(options); | |
String treeName = paramsObj.getString(GenConstants.TREE_NAME); | |
int num = 0; | |
for (GenTableColumn column : genTable.getColumns()) | |
{ | |
if (column.isList()) | |
{ | |
num++; | |
String columnName = column.getColumnName(); | |
if (columnName.equals(treeName)) | |
{ | |
break; | |
} | |
} | |
} | |
return num; | |
} | |
/** | |
* 获取表单 el-col span | |
* | |
* @param formColNum 表单布局方式(1 单列 2 双列 3 三列) | |
* @return span 数值字符串 | |
*/ | |
public static String getColSpan(int formColNum) | |
{ | |
if (formColNum == 2) | |
{ | |
return "12"; | |
} | |
else if (formColNum == 3) | |
{ | |
return "8"; | |
} | |
return "24"; | |
} | |
} |
新增代码中的 TYPE_LOCAL_DATE_TYPE 需要定义在 GenConstants 常量类中。
package com.zzyl.common.constant; | |
/** | |
* 代码生成通用常量 | |
* | |
* @author ruoyi | |
*/ | |
public class GenConstants | |
{ | |
/** 单表(增删改查) */ | |
public static final String TPL_CRUD = "crud"; | |
/** 树表(增删改查) */ | |
public static final String TPL_TREE = "tree"; | |
/** 主子表(增删改查) */ | |
public static final String TPL_SUB = "sub"; | |
/** 树编码字段 */ | |
public static final String TREE_CODE = "treeCode"; | |
/** 树父编码字段 */ | |
public static final String TREE_PARENT_CODE = "treeParentCode"; | |
/** 树名称字段 */ | |
public static final String TREE_NAME = "treeName"; | |
/** 上级菜单 ID 字段 */ | |
public static final String PARENT_MENU_ID = "parentMenuId"; | |
/** 上级菜单名称字段 */ | |
public static final String PARENT_MENU_NAME = "parentMenuName"; | |
/** 生成详情页开关 */ | |
public static final String GEN_VIEW = "genView"; | |
/** 数据库字符串类型 */ | |
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; | |
/** 数据库文本类型 */ | |
public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; | |
/** 数据库时间类型 */ | |
public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; | |
/** 数据库数字类型 */ | |
public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", | |
"bit", "bigint", "float", "double", "decimal" }; | |
/** 页面不需要编辑字段 */ | |
public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; | |
/** 页面不需要显示的列表字段 */ | |
public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", | |
"update_time" }; | |
/** 页面不需要查询字段 */ | |
public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", | |
"update_time", "remark" }; | |
/** Entity 基类字段 */ | |
public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; | |
/** Tree 基类字段 */ | |
public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; | |
/** 文本框 */ | |
public static final String HTML_INPUT = "input"; | |
/** 文本域 */ | |
public static final String HTML_TEXTAREA = "textarea"; | |
/** 下拉框 */ | |
public static final String HTML_SELECT = "select"; | |
/** 单选框 */ | |
public static final String HTML_RADIO = "radio"; | |
/** 复选框 */ | |
public static final String HTML_CHECKBOX = "checkbox"; | |
/** 日期控件 */ | |
public static final String HTML_DATETIME = "datetime"; | |
/** 图片上传控件 */ | |
public static final String HTML_IMAGE_UPLOAD = "imageUpload"; | |
/** 文件上传控件 */ | |
public static final String HTML_FILE_UPLOAD = "fileUpload"; | |
/** 富文本控件 */ | |
public static final String HTML_EDITOR = "editor"; | |
/** 字符串类型 */ | |
public static final String TYPE_STRING = "String"; | |
/** 整型 */ | |
public static final String TYPE_INTEGER = "Integer"; | |
/** 长整型 */ | |
public static final String TYPE_LONG = "Long"; | |
/** 浮点型 */ | |
public static final String TYPE_DOUBLE = "Double"; | |
/** 高精度计算类型 */ | |
public static final String TYPE_BIGDECIMAL = "BigDecimal"; | |
/** 时间类型 */ | |
public static final String TYPE_DATE = "Date"; | |
/** 模糊查询 */ | |
public static final String QUERY_LIKE = "LIKE"; | |
/** 相等查询 */ | |
public static final String QUERY_EQ = "EQ"; | |
/** 需要 */ | |
public static final String REQUIRE = "1"; | |
/** JDK8 时间类型 */ | |
public static final String TYPE_LOCAL_DATE_TYPE= "LocalDateTime"; | |
} |
# 更新 domain
package ${packageName}.domain; | |
#foreach ($import in $importList) | |
import ${import}; | |
#end | |
import com.baomidou.mybatisplus.annotation.IdType; | |
import com.baomidou.mybatisplus.annotation.TableField; | |
import com.baomidou.mybatisplus.annotation.TableId; | |
import com.baomidou.mybatisplus.annotation.TableName; | |
import com.fasterxml.jackson.annotation.JsonFormat; | |
import lombok.Data; | |
import lombok.AllArgsConstructor; | |
import lombok.NoArgsConstructor; | |
import lombok.ToString; | |
import com.zzyl.common.annotation.Excel; | |
#if($table.crud || $table.sub) | |
import com.zzyl.common.core.domain.BaseEntity; | |
#elseif($table.tree) | |
import com.zzyl.common.core.domain.TreeEntity; | |
#end | |
/** | |
* ${functionName} 对象 ${tableName} | |
* | |
* @author ${author} | |
* @date ${datetime} | |
*/ | |
#if($table.crud || $table.sub) | |
#set($Entity="BaseEntity") | |
#elseif($table.tree) | |
#set($Entity="TreeEntity") | |
#end | |
@Data | |
@NoArgsConstructor | |
@AllArgsConstructor | |
@ToString(callSuper = true) | |
@TableName("${tableName}") | |
public class ${ClassName} extends ${Entity} | |
{ | |
private static final long serialVersionUID = 1L; | |
#foreach ($column in $columns) | |
#if(!$table.isSuperColumn($column.javaField)) | |
/** $column.columnComment */ | |
#if($column.columnName == $pkColumn.columnName) | |
#if($pkColumn.increment) | |
@TableId(type = IdType.AUTO) | |
#end | |
#end | |
#if($column.list) | |
#set($parentheseIndex=$column.columnComment.indexOf("(")) | |
#if($parentheseIndex != -1) | |
#set($comment=$column.columnComment.substring(0, $parentheseIndex)) | |
#else | |
#set($comment=$column.columnComment) | |
#end | |
#if($parentheseIndex != -1) | |
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") | |
#elseif($column.javaType == 'Date' ||$column.javaType == 'LocalDateTime') | |
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | |
@Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") | |
#else | |
@Excel(name = "${comment}") | |
#end | |
#end | |
private $column.javaType $column.javaField; | |
#end | |
#end | |
#if($table.sub) | |
/** $table.subTable.functionName 信息 */ | |
@TableField(exist = false) | |
private List<${subClassName}> ${subclassName}List; | |
#end | |
} |
# 修改生成包路径和生成模块名
修改 zzyl-generator 模块下的 generator.yml 配置文件,修改后的内容如下:
# 代码生成 | |
gen: | |
# 作者 | |
author: alexis | |
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool | |
packageName: com.zzyl.nursing | |
# 自动去除表前缀,默认是 false | |
autoRemovePre: false | |
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔) | |
tablePrefix: sys_ | |
# 是否允许生成文件覆盖到本地(自定义路径),默认不允许 | |
allowOverwrite: false |
# 修改物理类型对应的 Java 类型
同时要在这里加上常量:
# 数据字典
字典管理用来维护字典类型的数据,例如:状态的启用、禁用;性别的男、女;订单的已下单、支付中、已完成;通常用在如下拉框、单选按钮、复选框、树选择中的数据中,方便系统管理员维护。
# 创建数据字典
# 修改页面代码
<template> | |
<div class="app-container"> | |
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px"> | |
<el-form-item label="名称" prop="name"> | |
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" /> | |
</el-form-item> | |
<el-form-item label="状态" prop="name"> | |
<el-select v-model="queryParams.status" placeholder="请选择" clearable> | |
<el-option | |
v-for="item in nursing_project_status" | |
:key="item.value" | |
:label="item.label" | |
:value="item.value" /> | |
</el-select> | |
</el-form-item> | |
<el-form-item> | |
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> | |
<el-button icon="Refresh" @click="resetQuery">重置</el-button> | |
</el-form-item> | |
</el-form> | |
<!-- 其他代码略 --> | |
</div> | |
</template> | |
<script setup name="Project"> | |
import { listProject, getProject, delProject, addProject, updateProject } from "@/api/nursing/project"; | |
// 获取当前组件的实例 | |
const { proxy } = getCurrentInstance(); | |
// 引用数据字典 | |
const { nursing_project_status } = proxy.useDict("nursing_project_status"); | |
/* | |
const options = ref ([ | |
{ | |
value: 1, | |
label: "启用" | |
}, | |
{ | |
value: 0, | |
label: "停用" | |
} | |
]) */ | |
</script> |