RuoYi-Vue-Plus 后端 CRUD 开发规范:基于分层架构的单表增删改查实战指南
【免费下载链接】RuoYi-Vue-Plus多租户后台管理系统 重写RuoYi-Vue所有功能 集成 Sa-Token、Mybatis-Plus、WarmFlow、SpringDoc、Hutool、OSS 定期同步项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue-Plus
RuoYi-Vue-Plus 为开发者沉淀了一套"标准后端 CRUD"的工程规范,覆盖新增单表业务时从 entity/bo/vo/mapper/service/controller 的完整搭建链路,并统一了分页查询、导出、删除前校验等约定。本文以仓库中backend-crud规范文档为主体,结合ruoyi-system模块中角色管理等标准实现与ruoyi-common-mybatis的底层封装源码,逐层讲解这套 CRUD 开发模式的落地方式。读完本文,你将掌握在 RuoYi-Vue-Plus 中新增一个标准管理模块的全部约定、关键注解用法与可复制的代码骨架。
一、总览:一套稳定的 CRUD 分层结构
规范的首要原则是"先参考、再动手":
- 先参考
ruoyi-modules/ruoyi-gen模块下的代码生成器模板; - 再参考当前模块内最近似的标准管理模块(例如
ruoyi-system中的角色/用户/部门等模块); - 分层保持稳定,包结构固定为:
domain -- 实体类(Entity) domain.bo -- 业务对象(接收请求参数) domain.vo -- 视图对象(返回前端数据) mapper -- 数据访问层 service -- 业务接口 service.impl -- 业务实现 controller -- 控制器这一约定在仓库中得到了完整印证:以角色管理为例,其分层文件依次位于 SysRole.java、SysRoleBo.java、SysRoleVo.java、SysRoleMapper.java、ISysRoleService.java、SysRoleServiceImpl.java、SysRoleController.java。新增业务时照此包结构对号入座即可。
二、Entity 层约定:基类继承与 MyBatis-Plus 注解
实体(Entity)是数据库表的映射,规范要求:
- 默认继承
BaseEntity:BaseEntity 位于 BaseEntity.java,内置了createDept(创建部门)、createBy(创建者)、createTime(创建时间)、updateBy(更新者)、updateTime(更新时间)五个审计字段,并全部通过@TableField(fill = FieldFill.INSERT / FieldFill.INSERT_UPDATE)声明了自动填充策略,写入数据时无需手动赋值。 - 表名与主键注解:实体使用
@TableName标注表名,主键字段使用@TableId。 - 逻辑删除与乐观锁:若表存在
delFlag逻辑删除字段、乐观锁版本字段,必须保留@TableLogic、@Version注解,由 MyBatis-Plus 自动完成逻辑删除过滤与乐观锁校验。
这套约定保证了所有实体具备统一的审计字段与通用 CRUD 能力,也是后续BaseMapperPlus泛型方法能够直接生效的前提。
三、Mapper 层:继承 BaseMapperPlus<T, V>
规范要求 mapper 默认继承BaseMapperPlus<Entity, Vo>。该接口位于 BaseMapperPlus.java,是对 MyBatis-PlusBaseMapper的二次封装,核心能力包括:
- 泛型双绑定:
BaseMapperPlus<T, V>同时持有实体类型 T 与 VO 类型 V,并通过ClassValue缓存解析结果(见TYPE_ARGUMENT_CACHE),避免重复反射解析。 - VO 直查方法族:
selectVoById、selectVoByIds、selectVoByMap、selectVoOne、selectVoList、selectVoPage等,底层流程均为"按条件查出实体列表 → 通过MapstructUtils.convert转换为 VO",例如selectVoList的实现是this.selectList(wrapper)后调用MapstructUtils.convert(list, voClass)。 - 批量操作:
insertBatch、updateBatchById、insertOrUpdateBatch三个批量方法,底层委托 MyBatis-Plus 的Db.saveBatch/Db.updateBatchById/Db.saveOrUpdateBatch,并支持自定义batchSize。 - 链式查询入口:
lambda()返回LambdaCrudChainWrapper<T, V>,lambdaUpdate()返回LambdaUpdateChainWrapper<T>。其中lambda()内部通过 Spring 容器获取 Mapper 代理对象(mapperProxy()),确保 Mapper 上的切面注解继续生效。
实际使用示例(来自角色模块的分页查询):
Page<SysRoleVo> page = roleMapper.selectPageRoleList(pageQuery.build(), this.buildQueryWrapper(role)); return PageResult.build(page.getRecords(), page.getTotal());四、BO / VO / Entity 职责分离
规范强调三层职责严格分离:
- BO(业务对象):接收请求与查询扩展字段,使用
@AutoMapper(target = Entity.class, reverseConvertGenerate = false)声明到实体的映射关系。reverseConvertGenerate = false表示只生成"BO → Entity"方向的映射,不反向生成。BO 上同时承载参数校验注解(@NotBlank、@Size、@NotNull等),例如 SysRoleBo.java 中的角色名称校验:@NotBlank(message = "角色名称不能为空") @Size(min = 0, max = 30, message = "角色名称长度不能超过{max}个字符") private String roleName; - VO(视图对象):返回给前端的展示对象,使用
@AutoMapper(target = Entity.class)声明与实体的映射。展示派生字段、@Translation翻译注解、导出注解@ExcelProperty都放在 VO 上。例如 SysRoleVo.java 中大量使用@ExcelProperty(value = "角色序号")定义导出列,并用@ExcelProperty(value = "数据范围", converter = ExcelDictConvert.class)挂接字典转换器。 - BO/VO 转换:BO 转实体统一使用
MapstructUtils.convert(bo, Entity.class)(MapstructUtils 位于 ruoyi-common-core,由@AutoMapper注解在编译期生成映射代码)。
五、Service 层:默认方法集合
标准业务 Service 接口默认包含六个方法,构成了 CRUD 的完整闭环:
| 方法 | 职责 |
|---|---|
queryById | 按主键查询详情 |
queryPageList | 分页查询列表 |
queryList | 无条件/条件查询全部列表 |
insertByBo | 新增(接收 BO) |
updateByBo | 修改(接收 BO) |
deleteWithValidByIds | 批量删除,删除前进行业务校验 |
实现要点:
- 写入前校验:优先放在
validEntityBeforeSave(...)方法中统一完成,保证新增与修改走同一校验逻辑。 - 业务级校验:如唯一性校验、数据权限校验,可在
insertByBo/updateByBo内显式调用。角色模块即为典型参考:新增前调用checkRoleNameUnique、checkRoleKeyUnique(见 SysRoleServiceImpl.java)。 - 修改/删除后联动:数据变更后可发布 Spring 事件做联动清理,例如角色信息变更后发布
OnlineUserCleanEvent.byRole(role.getRoleId())清理在线用户缓存。
六、Controller 层:接口规则与标准路由
Controller 规范要点:
- 继承
BaseController:BaseController 位于 ruoyi-common-web,提供toAjax(...)等通用返回转换方法。 - 返回值统一
R<T>或R<Void>:成功用R.ok(...),失败用R.fail(...)。 - 标准 CRUD 路由(以 SysRoleController.java 为模板):
| 方法 | 路由 | 说明 |
|---|---|---|
| GET | /list | 分页列表,返回R<PageResult<Vo>> |
| POST | /export | 导出 Excel |
| GET | /{id} | 查询详情 |
| POST | (空) | 新增 |
| PUT | (空) | 修改 |
| DELETE | /{ids} | 批量删除,ids逗号分隔 |
注解使用约定:
- 每个接口按需添加
@SaCheckPermission权限校验,权限标识遵循${module}:${business}:${action}三段式,例如system:role:list、system:role:add、system:role:edit、system:role:export; - 写操作(增删改)添加
@Log(title = "...", businessType = BusinessType.INSERT / UPDATE / DELETE / EXPORT)记录操作日志; - 新增与修改添加
@RepeatSubmit()防重复提交(注解位于 ruoyi-common-redis); - 请求参数 BO 使用
@Validated @RequestBody触发校验。
- 每个接口按需添加
导出接口固定为
POST /export,实现方式参考角色模块:@Log(title = "角色管理", businessType = BusinessType.EXPORT) @SaCheckPermission("system:role:export") @PostMapping("/export") public void export(SysRoleBo role, HttpServletResponse response) { List<SysRoleVo> list = roleService.selectRoleList(role); ExcelBuilder.of(list, SysRoleVo.class).sheetName("角色数据").toResponse(response); }ExcelBuilder位于 ruoyi-common-excel,基于 VO 上的@ExcelProperty注解自动生成 Excel 响应流。
七、查询规则:QueryBuilder、LambdaQueryCondition 与分页
规范对查询层有明确的统一约定:
1. 查询构造入口
单表查询优先返回LambdaQueryWrapper;新增 generator 风格代码优先使用QueryBuilder.lambda(Entity.class).build()。QueryBuilder位于 QueryBuilder.java,提供三个静态入口:
lambda(Class<T> entityClass):单表 Lambda 查询,内部构造AggregateLambdaQueryWrapper;lambdaJoin(Class<T> entityClass):基于 MPJ(MyBatis-Plus Join)的联表查询;lambdaJoin(String alias, Class<T> entityClass):带主表别名的联表查询。
2. 链式条件风格
项目的公共链式查询支持QueryBuilder.lambda(...)、BaseMapperPlus#lambda()、LambdaCrudChainWrapper、LambdaQueryCondition四种风格。其中LambdaQueryCondition(位于 LambdaQueryCondition.java)扩展了eq/ne/gt/ge/lt/le等条件方法,统一支持布尔condition参数控制"条件是否生效",并在此基础上封装了IfPresent/IfText/IfNotEmpty风格方法——当入参为空时自动跳过该条件,避免手写大量if (x != null)判断。
3. 日期范围约定
日期范围查询默认从bo.getParams()中读取begin/end键值。BO 通常内置Map<String, Object> params字段存放这类非结构化查询扩展参数,Service 层构建 Wrapper 时从中提取beginTime/endTime拼接到查询条件。
4. 分页返回约定
分页优先返回PageResult<Vo>。PageQuery(位于 PageQuery.java)封装了pageNum(默认 1)、pageSize(默认Integer.MAX_VALUE)、orderByColumn、isAsc四个分页参数,提供build()方法将其转换为 MyBatis-Plus 的Page对象;PageResult.build(records, total)(位于 PageResult.java)统一组装分页响应结构。
5. 实体转换
BO 转实体统一使用MapstructUtils.convert(bo, Entity.class),与@AutoMapper注解配合实现编译期类型安全映射。
八、代码生成器与模板约定
规范的起点是代码生成器。文档约定生成器模板位于ruoyi-modules/ruoyi-gen/src/main/resources/vm/下(注:当前仓库快照中该资源目录未随代码一并提交,若需查看模板需在本地生成环境执行代码生成命令后产出),生成器模块的核心逻辑位于 GenController.java、GenTableServiceImpl.java 与 GenUtils.java。
使用生成器时需注意一个命名约定:
代码生成器模板按类名首字母小写命名 Mapper 字段,例如
SysRoleMapper→sysRoleMapper;手写业务代码时则可以使用具体业务短名。
即:由生成器产出的 Service 实现中,Mapper 注入字段名一律取类名首字母小写;而手写业务时可以用更贴合业务语义的短名。这一约定保证了生成代码与手写代码在团队内保持一致的辨识度。
此外,规范明确"不能只满足于 generator 裸产物":生成后需要继续补齐项目约定——包括 BO/VO 职责分离是否到位、导出与分页与删除前校验是否齐全、权限标识是否按三段式规范命名等,最终交付的是符合工程规范的代码而非"能跑就行"的生成物。
九、前端同步约定
CRUD 改动往往伴随前端联调,规范要求前端api/types与 Vue 的index.vue(或 React 的index.tsx)同步更新,且必须与后端保持一致:
- 接口路径:与后端 Controller 路由一一对应(
/list、/export、/{id}、POST、PUT、DELETE /{ids}); - 返回结构:列表接口对应
PageResult<Vo>结构,其余接口对应R<T>/R<Void>结构; - 日期范围参数:查询表单提交的日期区间字段名(
begin/end或自定义别名)需与后端bo.getParams()中读取的键一致,否则会导致查询条件丢失。
十、交付前自检清单
规范在最后给出了 CRUD 交付前的自检项,这也是每次新增模块的验收标准:
- CRUD 链路是否完整:
queryById、queryPageList、queryList、insertByBo、updateByBo、deleteWithValidByIds六个默认方法是否齐全; - BO / VO / Entity 职责是否分离:请求与查询扩展字段是否放在 BO,展示派生字段与翻译/导出注解是否放在 VO;
- 导出、分页、删除前校验是否齐全:
POST /export导出、PageResult<Vo>分页、validEntityBeforeSave/ 删除前业务校验是否都已实现; - 是否只是 generator 裸产物:如果是,需要继续补齐项目约定(权限标识、日志注解、防重复提交、命名规范等);
- 前端是否同步:
api/types与页面组件的接口路径、返回结构、日期范围参数是否与后端一致。
以 SysRoleController.java 为代表的角色管理模块,就是上述全部约定的完整落地样例——从@SaCheckPermission权限、@Log日志、@RepeatSubmit防重、PageResult分页到ExcelBuilder导出,一应俱全,可作为新增单表 CRUD 模块时的"最近似标准管理模块"参照物直接对照开发。
【免费下载链接】RuoYi-Vue-Plus多租户后台管理系统 重写RuoYi-Vue所有功能 集成 Sa-Token、Mybatis-Plus、WarmFlow、SpringDoc、Hutool、OSS 定期同步项目地址: https://gitcode.com/GitHub_Trending/ru/RuoYi-Vue-Plus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考