# RuoYi 框架使用经验

相关内容基于中州养老项目与若依框架

RuoYi(若依)是一款基于 Spring Boot 等主流技术栈、提供代码生成器和完整权限管理功能的 Java 开源快速开发平台,旨在帮助开发者快速构建企业级后台管理系统。

通过 gitee 即可获取源码:https://gitee.com/y_project/RuoYi-Vue

# 启动

# 创建数据库并导入

目前我这个是 3.9.x 版本的,用这个脚本导入:ry_20260417.sql

1

完成之后:

2

# 修改 MySQL 数据库连接信息

修改连接池的地址、库名和密码

3

# 修改 Redis 数据库连接信息

修改连接的地址和密码

4

# 启动后端

5

# 前端的安装与修改

  1. 安装 NPM 包
  2. 修改与后端的连接地址

6

# 端口占用错误

error when starting dev server:
Error: listen EACCES: permission denied 0.0.0.0:80

7

可以修改为 8081

# 启动前端

8

# 认识 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,没有很强的业务逻辑,对于标准流程来说非常适用)

# 添加母菜单

8

# 表的导入

8

# 表的微调

这里在创建日期,更新日期一般由数据库自动完成,我们可以取消勾选,还有就是一些条件查询项目,比如说我们要根据名称查询啊,根据状态查询啊,可以把他勾上,其他的倒是可以取消勾选了。再有就是 int 型他这里识别对成了 Long 当然也是可以用的,这里我们还是改一下。最后是 id 列也是自动生成的,不需要插入。

8 8

然后就会生成这个压缩包:

8

解压后得到这个三个东西:

8

# 如何使用生成的内容

  1. 执行 SQL 脚本
  2. 将 main 下内容放到后端,这里放在了 ruoyi-admin 里
  3. 将 vue 下的内容放在前端
8 8

此时重启后端,单表 CRUD 开发就结束了。

8

更新表单日志:

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 模块

8

首先让我们来简单体验一下模板引擎:

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

我们把这里替换一下:

8

${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();
    }
}
8

# 在 RuoYi 中集成 Lombok

# 在父工程中添加依赖

版本交给根 pom 管理:

8

在 common 中添加依赖,其他模块如需要用,只需要依赖 common 即可:

8

# 修改 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 字段自动填充

  1. 在实体类中,使用 @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;
        }
    }
  2. framework 模块中新增 MyMetaObjectHandler 来处理字段自动填充

    8
    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;
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
            this.strictInsertFill(metaObject, "createTime", Date.class, DateUtils.getNowDate());
            this.strictInsertFill(metaObject, "createBy", String.class, String.valueOf(getLoginUser()));
        }
        @Override
        public 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 的区别?

  1. 不可变性与线程安全性
    1. Date 一旦创建 Date 对象,可以通过 setTime (long time) 进行修改,多线程下存在线程安全问题
    2. LocalDateTime 是不可变的,线程安全
  2. 时间精度
    1. Date 可以表示到毫秒的时间值
    2. LocalDateTime 可以表示到纳秒的时间值
  3. 易用性: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 类型

8

同时要在这里加上常量:

8

# 数据字典

字典管理用来维护字典类型的数据,例如:状态的启用、禁用;性别的男、女;订单的已下单、支付中、已完成;通常用在如下拉框、单选按钮、复选框、树选择中的数据中,方便系统管理员维护。

# 创建数据字典

8 8

# 修改页面代码

<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>
更新于

请我喝[茶]~( ̄▽ ̄)~*

KarryLiu 微信支付

微信支付

KarryLiu 支付宝

支付宝