news 2026/9/16 9:17:06

RuoYi-Vue + MyBatis-Plus + Hutool 组合拳,让后台管理开发告别重复编码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RuoYi-Vue + MyBatis-Plus + Hutool 组合拳,让后台管理开发告别重复编码

如果你也跟我一样,常年跟业务系统打交道,那你大概率也有这样一种体验:真正让人心累的从来不是那点核心业务逻辑,而是永远在重复的基础代码。用户管理、角色权限、菜单分配、列表增删改查、分页查询、操作日志,这些东西你做过几十遍,代码都能背下来,但每开一个新项目照样得重新写一遍。更气人的是,写这些东西还不能出半点差错,因为整个系统的基础全靠它们撑着。

后来我花了一段时间把手头的工具链彻底翻新了一遍,换完以后最大的感受就是一句话:重复编码至少少了一半。不是说业务逻辑变简单了,而是很多以前必须手写的“基础工程”,现在直接被开源项目替代了。这三个项目如果大家有印象,应该都见过,但它们组合起来的效果,远比单独使用要猛得多。

这三个项目分别是:RuoYi-Vue 后台管理脚手架、MyBatis-Plus 持久层增强工具、Hutool 通用工具库。它们处在整套开发流程的三个不同层次,RuoYi-Vue 把项目骨架搭好,权限、用户、菜单、日志这些基础功能开箱即用;MyBatis-Plus 把数据访问层的大量 SQL 和对象映射直接干掉;Hutool 则把日常开发中百分之七八十的工具方法全包了。今天我把这套组合拳的经验完整拆出来,包括部署方式、实操步骤、常见坑和组合使用的心得。

1. RuoYi-Vue:把后台管理系统的基础设施一次到位

1.1 这个项目到底解决了什么问题

RuoYi 是基于 Spring Boot 的快速开发平台,在 GitHub 上的热度一直很高。它最核心的价值就是“把后台管理系统里那些通用的东西提前做好”,注意,是“做好”,不是“做了个半成品”。

如果你自己搭过后台系统,你应该懂这个工程量。用户表、角色表、菜单表、部门表之间的关系设计,登录认证、验证码、操作日志、登录日志、系统监控、定时任务、数据字典、参数配置……这些东西真要自己从零开始手写,没有两三周根本下不来,而且架不住各种前端页面的调整。RuoYi-Vue 版本把这些全部整理好了,后端用 Spring Boot,前端用 Vue + Element UI,跑起来就是一个可以直接用来做二次开发的完整系统。

很多刚接触它的人容易把若依当成一个“成品系统”,这是最大的误解。它不是一个“软件”,而是一个“开发骨架”。你在这个骨架上叠加业务模块,不用再去重复搭建基础设施,这才是它省时间的本质。

1.2 在 IDEA 里把若依跑起来

第一件事肯定是把项目跑通。RuoYi-Vue 前端和后端是分离的两个工程,后端是 RuoYi-Vue 主仓库,前端是 ruoyi-ui 目录。我在 IDEA 里部署时通常分这么几步。

后端部分比较简单。把项目 clone 下来之后,用 IDEA 直接打开根目录的 pom.xml,Maven 会自动拉取依赖。注意这里有一个容易踩的坑:若依后端分了好几个模块,包括 ruoyi-admin、ruoyi-framework、ruoyi-system、ruoyi-quartz、ruoyi-generator、ruoyi-common,一定要等 Maven 全部加载完再启动,不然经常会出现“程序包 com.ruoyi.common.utils.StringUtils 不存在”这类的编译错误,实际上就是依赖没下载完。

数据库需要初始化两套脚本:一个 ry_2024xxx.sql(对应业务库),一个 quartz.sql(对应定时任务库)。这个在若依的文档里有说明,我用的是 MySQL 8.0,在创建好数据库后直接 source 进去就行。跑之前还需要本地装一个 Redis,若依的缓存、验证码存储都依赖 Redis,Redis 没启动的话后端会一直报连接异常。

配置好 application-druid.yml 里的数据源、Redis 连接信息,直接启动 RuoYiApplication,默认端口是 8080。后端起来后前端继续处理:在 IDEA 终端里 cd 到 ruoyi-ui 目录,执行 npm install,这个过程会比较久,Node 版本不要用太新的,我用 Node 16 系列基本没有乱七八糟的兼容问题。装完依赖后执行 npm run dev,前端默认端口是 80,打开浏览器访问 localhost,看到登录页就算是跑通了。默认账号 admin / admin123。

1.3 代码生成器是怎么把重复代码省掉的

RuoYi 真正杀手级的特性,是它自带的在线代码生成器。这个东西我用过之后,最大的感受是:以前新建一个管理页面的工作量,从一天缩到了十分钟以内。

整个流程是这样的。首先在数据库里建好业务表。这里有一个细节,建表时注释一定要写清楚,尤其是字段注释,因为若依的代码生成器会读取注释来生成前端表单的 label 和校验规则。比如用户姓名这个字段,注释写“用户姓名”,生成的前端页面里 label 就是“用户姓名”,校验规则也会根据字段类型和是否必填来生成。

然后把表在若依后台“系统工具 -> 代码生成”里导入。导入后点击编辑,可以配置生成细节。最重要的几个配置项是:生成包名、模块名、业务名、生成模板。生成模板有三种:单表、树表、主子表。单表是最常见的,就是一个简单的 CRUD;树表适用于菜单分类这种层级结构;主子表适用于订单加订单明细这种一对多关系。

配置好之后点击提交,然后再点击生成代码,系统会把 Controller、Service、Service 实现类、Mapper、Mapper XML、实体类、前端 Vue 页面、前端 API 文件全部一次性生成好。可以直接下载 zip 包,解压后复制到项目对应目录里。后端代码复制到若依对应模块下,前端 Vue 文件放到 ruoyi-ui 的 views 目录下,API 文件放到 api 目录下。最后在系统管理的菜单管理里创建一个菜单,绑定生成的页面路径,刷新后这个 CRUD 页面就跑起来了。

这一套流程下来,一个典型的单表管理功能,从建表到页面可用,本质上只需要关注建表和生成配置,剩下的重复代码全部自动完成。

1.4 我在实际使用中遇到的坑

第一,代码生成之后,菜单路径和组件路径对不上是特别常见的“看着像没生成成功”的假象。一定要检查菜单管理里组件路径是否和 views 目录下的物理路径一致。比如 views/system/goods/index.vue,组件路径就应该是 system/goods/index。

第二,若依官方版本的数据访问层用的是 MyBatis,不是 MyBatis-Plus。有些人习惯用 MyBatis-Plus,跟若依混在一起发现各种不兼容,其实官方还有一个增强版分支叫 RuoYi-Vue-Plus,内部集成了 MyBatis-Plus,如果你跟我一样后面打算用 MyBatis-Plus,建议直接基于这个分支改。

第三,npm run dev 启动很慢是正常的,因为前端路由是动态加载的,第一次启动需要预编译大量内容。如果等了特别久还是不行,优先检查 Node 版本和 npm install 是否完整,很多问题是依赖缺失导致的。

2. MyBatis-Plus:把 CURD 的 SQL 从 100 行降到 0 行

2.1 为什么说它是持久层的“减负神器”

MyBatis-Plus 并不是要把 MyBatis 替换掉,它是在 MyBatis 之上做增强。核心原理我一句话解释清楚:它在启动阶段,会为每个继承 BaseMapper 的接口自动生成一系列 CRUD SQL 和对应的结果映射,不需要你写 XML,也不需要你手写 SQL,直接调用方法就能完成操作。

你可能觉得“那我还是要写具体的查询逻辑啊”,没错,所以 MyBatis-Plus 还提供了条件构造器,可以完全用 Java 代码来表达 SQL 的 where 条件。相比手写 SQL 和 XML,最直接的收益是:你不再需要反复维护一大堆 ResultMap 和基础 SQL 片段。

举个例子。以前用 MyBatis 写一个分页列表查询,需要写 XML 里的 select、resultMap、count 语句、动态 where 条件,页面多一个搜索字段,XML 里就要加一段 if 判断。现在用了 BaseMapper 的方法配合 LambdaQueryWrapper,代码大概是这个画风:

LambdaQueryWrapper<Goods> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(query.getName()), Goods::getName, query.getName()) .eq(query.getType() != null, Goods::getType, query.getType()) .orderByDesc(Goods::getCreateTime); Page<Goods> page = baseMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()), wrapper);

这一段代码把“动态条件组合 + 分页查询”全干完了。SQL 由 MyBatis-Plus 在底层自动拼装,不需要写一行 XML。而且 Lambda 表达式的写法可以让你把类型安全也保住,字段名写错了在编译期就报错,不会拖到运行期才暴露。

2.2 条件构造器是核心中的核心

如果要在 MyBatis-Plus 里找最值得深挖的一个东西,那一定是条件构造器。它分为 QueryWrapper 和 LambdaQueryWrapper 两大类,前者用字符串指定字段名,后者用方法引用,强烈建议直接用后者,理由就是刚才说的编译期检查。

条件构造器本质上就是一套 Java 版的 SQL 条件 DSL。eq、ne、gt、ge、lt、le、like、between、in、isNull、groupBy、having、orderBy、limit 这些方法几乎把 SQL 语法全覆盖了。我最常用的是以下几种组合。

第一个是动态条件,这是最省代码的地方。以前如果条件不为空才过滤,需要写大量 if 包裹的 SQL 片段,现在直接用条件构造器第一个参数传布尔值,false 就直接忽略这个条件。上面例子里的 like(condition, column, value) 就是这种用法,一个方法调用替代一整段 XML 的动态 SQL。

第二个是嵌套条件。有些查询逻辑是 A and (B or C),在 XML 里写起来很容易乱。条件构造器里可以直接通过 and 和 or 方法实现:

wrapper.and(w -> w.eq(Goods::getType, 1).or().eq(Goods::getType, 2))

这种写法可读性高,而且避免了字符串拼接容易漏括号的问题。

第三个是表字段别名与 select 指定字段。如果某个表需要联表查询,条件构造器可以通过 apply 方法写一段原生 SQL 片段,或者直接配合自定义 XML 使用。日常简单列表 MyBatis-Plus 完全够用,复杂联表再走 XML,这样是最高效的组合。

2.3 自动填充与逻辑删除,两个细节减少大量脑细胞消耗

业务系统里几乎每张表都有 create_time、update_time、create_by、update_by 这几个通用字段。以前每次 insert 和 update 都要手动 set 一遍,漏掉一个就容易出脏数据。MyBatis-Plus 的自动填充功能完美解决这个问题。

实现方式很直接,字段上配置 @TableField(fill = FieldFill.INSERT) 或 fill = FieldFill.INSERT_UPDATE,然后实现一个 MetaObjectHandler 接口:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaInsertHandler metaInsertHandler) { this.strictInsertFill(metaInsertHandler, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaInsertHandler, "updateTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObjectHandler metaObjectHandler) { this.strictUpdateFill(metaObjectHandler, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

从那以后,所有实体的新增和更新都不用再手动管时间字段,框架自动填充,彻底跟 create_time 为 null 的问题说再见了。

逻辑删除也是类似。不建议物理删数据,这是业务系统的共识。以前要实现逻辑删除,每个表都要加一个 del_flag 字段,然后所有查询都要记得拼上 where del_flag = 0,写起来非常烦。MyBatis-Plus 的做法是在实体字段上加 @TableLogic,配置好逻辑删除标志后,框架自动在删除时转成 update,在查询时自动追加逻辑删除条件,完全无感。

注意:自动填充和逻辑删除都需要配置对应 Bean,而且用官方推荐的方式写,不要自己重写 sqlInjector,否则容易和分页插件冲突。

2.4 我用 MyBatis-Plus 时最容易踩的坑

第一,分页插件不注册,selectPage 返回的数据是错的。很多人加了分页依赖却忽略了配置 MybatisPlusInterceptor,导致分页查询实际上只是查了全部数据。正确配置是新建一个配置类,添加一个 MybatisPlusInterceptor,同时注册 PaginationInnerInterceptor。

第二,防全表更新/删除一定要开。MyBatis-Plus 默认如果不传条件调用 update 或 delete,会有全表更新的风险。我一开始没注意,后来在一次环境清理时差点把整张表数据更新掉,那次吓出一身汗。配置 PaginationInnerInterceptor 时可以顺带设置 BlockAttackInnerInterceptor,这样不带 where 条件的 update/delete 会直接抛异常,从源头封死风险。

第三,Service 层的批量操作要用 IService。很多人继承 BaseMapper 后,自己 for 循环调 insert,导致大量单条 SQL 反复执行。其实 MyBatis-Plus 提供了 IService 和 ServiceImpl,里面内置了 saveBatch、saveOrUpdateBatch 等方法,越用越香。

3. Hutool:把日常工具方法从“手写”变成“调用”

3.1 一个啥都有的工具类库,到底能省多少事

Hutool 是国产开源项目里口碑很好的一颗明珠。它的定位很朴素:把日常 Java 开发里频繁用到的工具方法,全部封装进统一规范的 API 里。字符串、日期、文件、加密、HTTP、Excel、图片、二维码、正则、反射、Bean 拷贝、集合操作,你能想到的基础操作它基本都有。

为什么它也能让重复编码少一半?因为日常业务里,纯粹与业务无关的“重复造轮子”代码其实非常多。我举个例子:字符串判空,很多老项目中需要自己写 StringUtils.isBlank 或者 Spring 的 StringUtils.hasText,换成 Hutool 的 StrUtil.isBlank 后,逻辑是全平台统一的,不会出现项目里三套判空工具类并存的奇葩结构。

日期处理更是重灾区。JDK 8 之前的 Date 和 SimpleDateFormat 又难用又有线程安全问题,好多人专门写了个 DateUtils 工具类,结果换一个项目又要重写一遍。Hutool 的 DateUtil 把格式化、解析、偏移、计算相差天数、获取年月日等一网打尽,而且腹黑得很,内部全部基于 java.time 包重写了,线程安全这块帮你省了心。

还有随机数、ID 生成、二维码生成、Bean 属性拷贝、树形结构构建,这类方法如果自己写,每写一个都要折腾半天,用 Hutool 基本就是一行调用的事。

3.2 用 Excel 导出对比体验一次“从灾难到救赎”

我觉得最能说明问题的例子是 Excel 导出。如果不接任何工具库,直接用原生 POI 导出一个带表头的 Excel,你需要创建 Workbook、创建 Sheet、创建 Row、创建 Cell,还要手动设置样式,写下来至少四五十行琐碎代码。如果还要设置单元格宽度、冻结表头、合并单元格、数据格式化,那这个“简单导出”直接会膨胀到一两百行。

我用 Hutool 的 ExcelUtil 重写后,代码量大概是这样的:

List<GoodsExportVO> list = goodsService.listExport(query); ExcelWriter writer = ExcelUtil.getWriter(true); writer.addHeaderAlias("goodsName", "商品名称"); writer.addHeaderAlias("price", "价格"); writer.addHeaderAlias("stock", "库存"); writer.write(list, true); writer.autoSizeColumnAll(); ServletOutputStream out = response.getOutputStream(); writer.flush(out); writer.close(); IoUtil.close(out);

这段代码里包含了多少隐藏工作量?别名映射、全量写入、自动调整列宽、流输出,这些以前都要手写。当然,Hutool 底层也是在用 POI 做引擎,但你把意义上毫无技术含量的代码全部省掉了。项目里遇到批量导出业务,这一套改造成本极低。

类似的例子还有 HTTP 请求工具。以前用 HttpURLConnection 发起一个 GET 请求,要设置连接参数、请求头、处理响应流,少说三四十行。现在:

String resp = HttpUtil.get("https://api.example.com/goods/list", 5000);

一行搞定,超时时间也顺带设置了。

3.3 使用 Hutool 时的版本与依赖注意事项

刚开始用 Hutool 的时候,最大的纠结就是不知道怎么引入。项目没有单独引入某个 GlassFish 的依赖情况下,我建议直接引入全量包 hutooll-all,省心。如果追求极致精简,可以按模块引入 hutool-core、hutool-http、hutool-poi 等。

版本上要注意:Hutool 5.8.x 是基于 JDK 8 的,如果你还在用 JDK 8,那是首选;Hutool 6.x 系列是基于 JDK 17 的,里面把所有模块统一成了 hutool-core 加上独立 scope 的 io 等,如果你的项目已经升级到 JDK 17+,也可以考虑,但迁移时要注意包路径变化。我现在大部分项目还是 JDK 8,所以一直稳定用 5.8.x。

另外一个常被忽略的坑是依赖传递。hutool-http 模块虽然默认使用 JDK 原生的 HttpURLConnection 发请求,但如果你项目里同时存在 OkHttp 或 HttpClient 的依赖,HttpUtil 也不会自动切换实现,不会帮你偷偷用上那些库。想要切换需要额外配置,我建议不要折腾,默认实现其实够稳,只是有些极端场景比如需要自定义代理、双向认证时,还是得走原生的 HttpClient。

还有一个实践经验:Hutool 的 ExcelUtil 虽然写列表导出很好用,但涉及复杂的动态合并单元格、自定义复杂样式时,还是建议直接使用原生 POI,或者用 Hutool 的模板导出功能。工具类是帮我们省基础代码的,不是用来包办一切复杂场景的。

4. 组合使用的实战建议与问题排查

4.1 三套开源项目该怎么搭配使用

三个项目并不是孤立的,它们可以在同一个项目里完美共存。我现在的开发模板就是三者组合的产物。

项目骨架采用 RuoYi-Vue,后端启动后自带权限系统和基础管理页面,前端也有了 Element UI 的组件体系。数据访问层在 RuoYi-Vue-Plus 分支上继续增强,使用 MyBatis-Plus 来写所有基础 CRUD,省掉大量手写 SQL。而工具层则全部交给 Hutool,不管字符串、日期、文件还是 Excel 导入导出,都用统一的一套 API,项目代码里基本见不到那些自己封装的杂七杂八工具类。

这样做有个很大的好处:团队协作时,每个人写出来的代码风格高度统一,都是在这些成熟框架的约定下进行,新人上手也很快,不会再出现一个人一个写法的混乱状态。

4.2 常见问题速查表与解决思路

我在反复实践后整理了一张高频问题表,基本能覆盖大多数开发中的卡点。

问题现象解决思路
若依前端启动白屏或接口 404登录页出不来或按钮请求全部失败检查后端 Redis 是否启动、数据源配置是否正确、前端代理是否配置后端地址
若依代码生成后页面提示 No match 路由菜单点击后页面找不到检查菜单管理中的组件路径是否和 views 下文件路径一致
MyBatis-Plus 分页不生效selectPage 返回总数正确但记录数是全部添加 MybatisPlusInterceptor 注册 PaginationInnerInterceptor
MyBatis-Plus 逻辑删除后查询还能查到已删记录逻辑删除字段未生效实体类删除计数字段加 @TableLogic 并配置逻辑删值
Hutool DateUtil 与 JDK 新版本兼容问题部分方法在高版本 JDK 中报类型错误使用最新 5.8.x 版本,避免旧版本残留
Hutool 引入后类冲突提示 ClassCastException 或 NoSuchMethodError检查其他依赖是否包含相同全限定名类,使用 mvn dependency:tree 排查

4.3 根据自己的实际经验给出几个补充技巧

说几个正文里没来得及细讲的点,都很实用。

若依的在线用户、定时任务管理、服务监控这几个模块,建议拿去仔细读源码,不要光当用户用。尤其是它的动态权限体系,读懂之后你可以在很多大型系统里复用同一套思路,省得以后面对复杂的权限需求还要从零设计。

使用 MyBatis-Plus 时,如果项目是从 MyBatis 升级过来的,不建议一次性把所有 XML 全部迁移到 Wrapper。实用策略是:新代码全部用 MyBatis-Plus 的 API,老代码暂时保留 XML,找一个迭代窗口再逐步替换。一次性盲改很容易把业务规则改丢,而且排查成本极高。

Hutool 虽然在工具方法方面极其丰富,但不要因此把业务逻辑封装进工具类。我曾经见过有同事把订单计算逻辑塞进 Hutool 的自定义扩展类里,结果整个工具类变成了一堆业务 if 的集合,维护成本直接爆炸。工具类负责通用操作,业务逻辑必须留在 Service 层,这个边界不能乱。

另外,很多人忽略了 Hutool 里一个非常好用的功能:树形结构构建。业务系统的人员组织架构、菜单分类、权限列表,经常需要从数据库的扁平结构构造成树形对象。手工写这个递归或者双循环算法并不复杂,但每次写都要小心翼翼处理 null 和空列表。Hutool 的 TreeUtil 可以几行完成,还支持排序、懒加载,强烈推荐长期做后台系统的朋友好好用起来。

一些自己的体会

用这套组合拳做了几个项目后,我回头看以前自己从零搭系统的日子,确实是工期紧、代码还容易出低级错误。现在开新项目,两个小时内后端框架和前端框架都能跑起来,基础功能从无到有基本是一天的事,整个开发节奏完全不一样了。

最后再分享一个小技巧:RuoYi 的代码生成器本身也是可以定制的,它的模板文件全部放在 ruoyi-generator 模块下的 resources/vm 目录里,如果你所在团队有自己的代码规范、注释风格、类名后缀规则,直接改模板文件,让生成出来的代码自动符合团队规范。我第一次改模板的时候,只是把类注释和接口注释调整成团队的 Javadoc 风格,改完再生成出来的代码直接就是团队标准样式,审查代码的人和我都省了不少事。这个东西才是最大潜力的“重复编码消除器”,因为每个项目团队真正重复的,从来不只是技术功能的实现,还有编码习惯和规范的一致性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 9:16:38

LightTools手动建模菲涅尔透镜:环带计算与旋转体设计指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 9:16:02

Unity镜子效果实现:RenderTexture与辅助相机方案完全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 9:15:14

锂电池SOC估计:卡尔曼滤波与Simulink实践

1. 锂电池SOC估计模型概述锂电池作为当前最主流的储能设备之一&#xff0c;其荷电状态(State of Charge, SOC)的准确估计对于电池管理系统(BMS)至关重要。SOC可以理解为电池的"电量百分比"&#xff0c;就像我们手机右上角显示的电量数字。但不同于手机简单的电量显示…

作者头像 李华
网站建设 2026/9/16 9:14:14

商场管理系统开发:SSM框架实战与零售业务数字化

1. 项目概述&#xff1a;商场管理系统的核心价值与定位商场管理系统作为现代零售业数字化转型的核心基础设施&#xff0c;其价值早已超越简单的收银记账功能。2026届毕业生选择这个课题作为毕业设计&#xff0c;实际上是在挑战一个融合了零售业务逻辑、供应链协同、用户行为分析…

作者头像 李华
网站建设 2026/9/16 9:13:55

Sqlite数据库文件格式简图

前 言最近在学习sqlite micro logger源码&#xff0c;涉及到sqlite数据库文件格式&#xff0c;对照“Sqlite Database File Format”以及sqlite micro logger源码使用的格式&#xff0c;画出sqlite数据库文件格式简图&#xff0c;随着对源码的分析和对sqlite文件格式官方文档的…

作者头像 李华