# Cursor Rules - 后端开发规范 ## 适用范围 - **项目目录**: `backend/` (基于 RuoYi-Vue-Plus + Spring Boot 3.4.6) - **Java 版本**: JDK 17/21 - **后端服务**: http://localhost:8080 --- # 后端开发规范 (backend/) ## 1. 技术栈规范 ### 核心技术 - **框架**: Spring Boot 3.4.6 - **Java 版本**: JDK 17/21 - **Web 容器**: Undertow - **ORM 框架**: MyBatis-Plus 3.5.12 - **数据库**: MySQL/Oracle/PostgreSQL/SQLServer (多数据库支持) - **缓存**: Redis 5-7 + Redisson 3.45.1 - **权限认证**: Sa-Token 1.42.0 + JWT - **任务调度**: SnailJob 1.5.0 - **工作流**: Warm-Flow 1.7.3 ## 2. 项目结构规范 ### 后端模块结构 ``` backend/ ├── ruoyi-admin/ # 管理后台启动模块 ├── ruoyi-common/ # 公共模块组 │ ├── ruoyi-common-core/ # 核心模块 │ ├── ruoyi-common-mybatis/ # MyBatis配置 │ ├── ruoyi-common-redis/ # Redis配置 │ ├── ruoyi-common-satoken/ # Sa-Token配置 │ └── ... (其他公共模块) ├── ruoyi-modules/ # 业务模块组 │ ├── ruoyi-system/ # 系统管理模块 │ ├── ruoyi-demo/ # 示例模块 │ ├── ruoyi-hq/ # 后勤管理模块 (hq) │ ├── ruoyi-rs/ # 人事管理模块 (rs) │ ├── ruoyi-cw/ # 财务管理模块 (cw) │ ├── ruoyi-aqbw/ # 安全保卫管理模块 (aqbw) │ ├── ruoyi-zsjy/ # 招生就业管理模块 (zsjy) │ ├── ruoyi-xg/ # 学工管理模块 (xg) │ └── ... (其他业务模块) ├── ruoyi-extend/ # 扩展模块组 └── script/ # 脚本和SQL ``` ## 🔴 模块工程管理规范(强制性,2025-01-23新增) ### 📦 模块工程创建规范 **⚠️ 基于ruoyi-hq工程实践制定,后续所有新模块必须遵循此规范!** #### 🏗️ 模块命名和位置规范 **模块位置**: `backend/ruoyi-modules/ruoyi-{模块简称}/` **已规划模块列表**: - ✅ `ruoyi-hq` - 后勤管理模块 (已完成) - 🚧 `ruoyi-rs` - 人事管理模块 - 🚧 `ruoyi-cw` - 财务管理模块 - 🚧 `ruoyi-aqbw` - 安全保卫管理模块 - 🚧 `ruoyi-zsjy` - 招生就业管理模块 - 🚧 `ruoyi-xg` - 学工管理模块 #### 📋 Maven配置规范 **pom.xml标准模板**: ```xml ruoyi-modules org.dromara ${revision} 4.0.0 ruoyi-{模块简称} {模块名称}管理模块 org.dromara ruoyi-common-core org.dromara ruoyi-common-doc org.dromara ruoyi-common-mybatis org.dromara ruoyi-common-log org.dromara ruoyi-common-security org.dromara ruoyi-common-web org.dromara ruoyi-common-excel org.dromara ruoyi-common-idempotent org.dromara ruoyi-common-tenant org.dromara ruoyi-common-social org.dromara ruoyi-common-ratelimiter org.dromara ruoyi-common-mail org.dromara ruoyi-system org.dromara ruoyi-job org.dromara ruoyi-generator org.dromara ruoyi-demo org.dromara ruoyi-workflow ``` #### 🏛️ 包结构规范 **Java包结构**: `org.dromara.{模块简称}` **标准目录结构**: ``` ruoyi-{模块简称}/ ├── pom.xml └── src/main/ ├── java/org/dromara/{模块简称}/ │ ├── controller/ # REST API控制器层 │ ├── service/ # 业务服务接口层 │ │ └── impl/ # 业务服务实现层 │ ├── mapper/ # 数据访问层接口 │ └── domain/ # 数据对象层 │ ├── [Entity].java # 实体类 (继承TenantEntity) │ ├── bo/ # 业务对象 (Business Object) │ └── vo/ # 视图对象 (View Object) └── resources/ └── mapper/ # MyBatis XML映射文件 ``` #### 🎯 代码规范要求 **Entity实体类**: ```java @Data @EqualsAndHashCode(callSuper = true) @TableName("表名") public class EntityName extends TenantEntity { @Serial private static final long serialVersionUID = 1L; @TableId(value = "id") private Long id; // 业务字段... } ``` **Service接口**: ```java public interface IEntityService { EntityVo queryById(Long id); TableDataInfo queryPageList(EntityBo bo, PageQuery pageQuery); List queryList(EntityBo bo); Boolean insertByBo(EntityBo bo); Boolean updateByBo(EntityBo bo); Boolean deleteWithValidByIds(Collection ids, Boolean isValid); } ``` **Controller控制器**: ```java @Validated @RequiredArgsConstructor @RestController @RequestMapping("/{模块简称}/entity") public class EntityController extends BaseController { private final IEntityService entityService; @SaCheckPermission("{模块简称}:entity:list") @GetMapping("/list") public TableDataInfo list(EntityBo bo, PageQuery pageQuery) { return entityService.queryPageList(bo, pageQuery); } // 其他标准接口... } ``` **Mapper接口**: ```java public interface EntityMapper extends BaseMapperPlus { // 自定义查询方法... } ``` #### 🔐 权限命名规范 **权限编码格式**: `{模块简称}:{功能}:{操作}` **示例**: - `hq:repair:list` - 后勤模块报修列表查询权限 - `rs:employee:add` - 人事模块员工新增权限 - `cw:budget:export` - 财务模块预算导出权限 #### 📚 模块开发最佳实践 **1. 遵循ruoyi-hq标准模式**: - 继承TenantEntity支持多租户 - 使用@SaCheckPermission进行权限控制 - 遵循TableDataInfo分页响应格式 - 使用MapstructUtils进行对象转换 **2. 代码质量要求**: - 使用Lombok减少样板代码 - 完整的中文注释和文档 - 统一的异常处理机制 - 完善的参数校验 **3. 模块间解耦**: - 模块间通过Service接口交互 - 避免直接依赖其他模块的内部实现 - 共用功能提取到common模块 **4. 数据库设计**: - 必须遵循数据库规范基础字段 - 支持逻辑删除和多租户 - 合理的索引设计 - 完整的数据字典配置 #### 🚨 模块创建检查清单 **创建新模块前必须检查**: - [ ] 模块名称符合规范?(`ruoyi-{简称}`) - [ ] Maven配置是否正确? - [ ] 包结构是否遵循标准? - [ ] 是否包含完整的分层架构? - [ ] 权限编码是否符合规范? - [ ] 是否遵循ruoyi-hq的最佳实践? - [ ] 数据库设计是否符合规范? - [ ] 是否添加了相关文档? ## 3. 开发规范 ### 代码规范 - **严格遵守**: Alibaba Java 开发手册 - **命名规范**: - 类名: PascalCase (如: UserController) - 方法名: camelCase (如: getUserList) - 常量: UPPER_SNAKE_CASE (如: MAX_RETRY_COUNT) - 包名: 全小写 (如: org.dromara.system) ### 🔴 数据库规范(强制性) - **⚠️ 超级重要**: 所有数据库操作必须严格遵循 `docs/设计文档/database/mysql-database-standard.md` 规范 - **现有数据库模型**: 参考 `docs/设计文档/database/hzdyjs_xg.sql` 中的完整表结构 - **环境配置**: 开发环境数据库信息见 `docs/设计文档/database/database_msg.md` #### 🚨 强制性核心规范 1. **所有业务表必须包含标准基础字段** 2. **统一使用雪花 ID (bigint(20)) 作为主键** 3. **强制使用逻辑删除,禁止物理删除** 4. **所有表必须支持多租户** 5. **完整的中文注释是必须的** 6. **禁止使用数据库外键约束**,关联关系通过应用层维护 #### 🔴 表命名规范(强制性) **⚠️ 超级重要:所有数据库表必须按业务模块使用统一前缀命名!** **模块表名前缀规范**: - **后勤管理**:`hq_` 开头 (如:`hq_repair`, `hq_maintenance`) - **人事管理**:`rs_` 开头 (如:`rs_employee`, `rs_department`) - **财务管理**:`cw_` 开头 (如:`cw_budget`, `cw_expense`) - **学工管理**:`xg_` 开头 (如:`xg_student`, `xg_dormitory`) - **招生就业**:`zsjy_` 开头 (如:`zsjy_application`, `zsjy_job`) - **安全保卫**:`aqbw_` 开头 (如:`aqbw_security`, `aqbw_patrol`) - **系统公共**:`common_` 开头 (如:`common_dict`, `common_config`) **命名示例**: ```sql -- 后勤管理模块表 CREATE TABLE `hq_repair` ( `id` bigint(20) NOT NULL COMMENT '报修ID', -- 其他字段... ) COMMENT='报修管理表'; -- 人事管理模块表 CREATE TABLE `rs_employee` ( `id` bigint(20) NOT NULL COMMENT '员工ID', -- 其他字段... ) COMMENT='员工信息表'; -- 财务管理模块表 CREATE TABLE `cw_budget` ( `id` bigint(20) NOT NULL COMMENT '预算ID', -- 其他字段... ) COMMENT='预算管理表'; -- 学工管理模块表 CREATE TABLE `xg_student` ( `id` bigint(20) NOT NULL COMMENT '学生ID', -- 其他字段... ) COMMENT='学生信息表'; -- 招生就业模块表 CREATE TABLE `zsjy_application` ( `id` bigint(20) NOT NULL COMMENT '申请ID', -- 其他字段... ) COMMENT='招生申请表'; -- 安全保卫模块表 CREATE TABLE `aqbw_security` ( `id` bigint(20) NOT NULL COMMENT '安全记录ID', -- 其他字段... ) COMMENT='安全管理表'; -- 系统公共模块表 CREATE TABLE `common_dict` ( `id` bigint(20) NOT NULL COMMENT '字典ID', -- 其他字段... ) COMMENT='数据字典表'; ``` **强制性要求**: - ✅ 新建表时必须使用对应模块前缀 - ✅ 表名采用下划线命名法(snake_case) - ✅ 表名必须是英文,禁止使用中文或拼音 - ✅ 表注释必须用中文清楚说明表的用途 - ❌ 禁止跨模块混用前缀(如人事模块不能创建 cw_ 开头的表) - ❌ 禁止使用无前缀的表名(系统核心表除外) **实体类命名对应**: ```java // 表名: hq_repair -> 实体类: HqRepair @TableName("hq_repair") public class HqRepair extends TenantEntity { // ... } // 表名: rs_employee -> 实体类: RsEmployee @TableName("rs_employee") public class RsEmployee extends TenantEntity { // ... } // 表名: cw_budget -> 实体类: CwBudget @TableName("cw_budget") public class CwBudget extends TenantEntity { // ... } ``` #### 🔴 强制性基础字段模板 ```sql -- 所有业务表必须包含以下字段(顺序和类型必须完全一致) `id` bigint(20) NOT NULL COMMENT '主键ID', `tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号', `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `deleted` tinyint(1) DEFAULT 0 COMMENT '删除标志(0代表存在 1代表删除)', -- 业务字段在此处添加 PRIMARY KEY (`id`) USING BTREE, KEY `idx_tenant_id` (`tenant_id`), KEY `idx_deleted` (`deleted`), KEY `idx_create_time` (`create_time`) ``` #### 🔴 AI 开发强制要求 当使用 AI 进行数据库开发时: 1. **必须先阅读** `docs/设计文档/database/mysql-database-standard.md` 规范 2. **必须使用标准模板**,不得随意修改基础字段 3. **必须保持与现有表结构的一致性** 4. **建表后必须立即更新** `hzdyjs_xg.sql` 文件 5. **必须在变更记录中记录** 数据库变更 ### 🔴 API 接口规范(强制性) **⚠️ 超级重要:所有 API 接口必须严格遵循以下统一规范!** #### 🔐 请求头规范(强制性) **所有需要认证的接口必须在请求头中包含 Token,格式如下:** ```json { "Content-Type": "application/json;charset=utf-8", "Authorization": "Bearer {access_token}", "clientid": "e5cd7e4891bf95d1d19206ce24a7b32e", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "referer": "http://localhost/login?redirect=%252Findex" } ``` **请求头字段说明**: - **Authorization**: `Bearer ` + Token 值(⚠️ 注意 Bearer 后面有一个空格) - **clientid**: 客户端 ID,用于权限验证 - **Content-Type**: 请求内容类型,必须为 application/json;charset=utf-8 - **Accept-Language**: 语言设置,用于国际化 - **referer**: 来源页面,用于安全验证 **Token 格式强制要求**: - ✅ 正确:`"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."` - ❌ 错误:`"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."`(缺少 Bearer 前缀) - ❌ 错误:`"Authorization": "Bearer eyJ0eXAi..."`(Bearer 后面没有空格) #### 📊 统一响应格式(必须遵循) **基础响应格式(分页列表)**: ```json { "total": 7, "rows": [], "code": 200, "msg": "查询成功" } ``` #### 🎯 响应格式详细说明 **1. 分页列表响应** ```json { "total": 100, "rows": [ { "id": 1, "name": "示例数据1", "createTime": "2025-06-30 12:00:00" }, { "id": 2, "name": "示例数据2", "createTime": "2025-06-30 12:01:00" } ], "code": 200, "msg": "查询成功" } ``` **2. 非分页列表响应** ```json { "code": 200, "msg": "查询成功", "data": [ { "id": 1, "name": "示例数据1" }, { "id": 2, "name": "示例数据2" } ] } ``` **3. 单条数据响应** ```json { "code": 200, "msg": "操作成功", "data": { "id": 1, "name": "示例数据", "createTime": "2025-06-30 12:00:00" } } ``` **4. 操作成功响应(无数据)** ```json { "code": 200, "msg": "操作成功", "data": null } ``` **5. 错误响应** ```json { "code": 400, "msg": "参数错误:用户名不能为空", "data": null } ``` #### 📋 状态码规范 **成功状态码**: - `200` - 操作成功 - `201` - 创建成功 **客户端错误(4xx)**: - `400` - 请求参数错误 - `401` - 未认证/认证失败 - `403` - 无权限访问 - `404` - 资源不存在 - `409` - 数据冲突 **服务端错误(5xx)**: - `500` - 服务器内部错误 - `502` - 网关错误 - `503` - 服务不可用 #### 🚨 接口开发强制要求 **1. Controller 返回格式** ```java // ✅ 正确 - 分页接口直接返回TableDataInfo @GetMapping("/list") public TableDataInfo list(UserBo bo, PageQuery pageQuery) { TableDataInfo result = userService.queryPageList(bo, pageQuery); return result; } // ✅ 正确 - 非分页接口使用R类型包装 @GetMapping("/options") public R> options() { List list = userService.selectUserOptions(); return R.ok(list); } // ❌ 错误 - 分页接口使用R类型包装 @GetMapping("/list") public R> list(UserBo bo, PageQuery pageQuery) { TableDataInfo result = userService.queryPageList(bo, pageQuery); return R.ok(result); // 这会产生多层嵌套 } ``` **2. 分页接口规范** ```java // 分页查询直接返回 TableDataInfo 数据 @GetMapping("/list") public TableDataInfo list(UserBo bo, PageQuery pageQuery) { TableDataInfo result = userService.queryPageList(bo, pageQuery); return result; } // 返回格式: // { // "total": 100, // "rows": [...], // "code": 200, // "msg": "查询成功" // } ``` **3. 非分页列表接口** ```java // 非分页列表查询(下拉选项等) @GetMapping("/options") public R> options() { List list = userService.selectUserOptions(); return R.ok(list); } // 返回格式: // { // "code": 200, // "msg": "查询成功", // "data": [...] // } ``` **4. 单条数据接口** ```java // 单条数据查询 @GetMapping("/{id}") public R getInfo(@PathVariable Long id) { UserVo user = userService.queryById(id); return R.ok(user); } // 返回格式: // { // "code": 200, // "msg": "操作成功", // "data": {...} // } ``` **5. 操作类接口** ```java // 新增/修改/删除操作 @PostMapping public R add(@RequestBody UserBo bo) { boolean result = userService.insertByBo(bo); return result ? R.ok() : R.fail("操作失败"); } // 成功返回格式: // { // "code": 200, // "msg": "操作成功", // "data": null // } // 失败返回格式: // { // "code": 500, // "msg": "操作失败", // "data": null // } ``` #### 🔍 接口命名规范 **RESTful 风格**: - `GET /api/users` - 查询用户列表 - `GET /api/users/{id}` - 查询单个用户 - `POST /api/users` - 新增用户 - `PUT /api/users` - 修改用户(传完整对象) - `PATCH /api/users/{id}` - 部分更新用户 - `DELETE /api/users/{id}` - 删除用户 **特殊功能接口**: - `GET /api/users/options` - 获取用户下拉选项 - `GET /api/users/statistics` - 获取用户统计数据 - `POST /api/users/batch` - 批量操作用户 - `POST /api/users/{id}/enable` - 启用用户 - `POST /api/users/{id}/disable` - 禁用用户 #### 🛡️ 权限控制规范 ```java // 每个接口必须添加权限注解 @SaCheckPermission("system:user:list") @GetMapping("/list") public R> list(UserBo bo, PageQuery pageQuery) { // 接口实现 } ``` #### 📝 参数验证规范 ```java // 使用Bean Validation进行参数校验 @PostMapping public R add(@Validated(AddGroup.class) @RequestBody UserBo bo) { // 接口实现 } // VO类中的验证注解 public class UserBo { @NotBlank(message = "用户名不能为空", groups = {AddGroup.class, EditGroup.class}) @Size(max = 30, message = "用户名长度不能超过30个字符") private String userName; @NotBlank(message = "手机号不能为空", groups = {AddGroup.class, EditGroup.class}) @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String phone; } ``` #### 🚨 接口开发检查清单 **每个新接口必须检查**: - [ ] 是否在请求头中正确使用了 Bearer Token 格式? - [ ] 分页接口是否直接返回 TableDataInfo 格式? - [ ] 非分页接口是否使用 R 类型包装返回结果? - [ ] 是否遵循了统一的响应格式规范? - [ ] 是否添加了@SaCheckPermission 权限注解? - [ ] 是否添加了@Operation 接口描述? - [ ] 是否进行了参数校验? - [ ] 是否符合 RESTful 命名规范? - [ ] 是否包含了必要的请求头(clientid、Content-Type 等)? - [ ] 是否在 API 文档中记录了接口信息? #### 💥 违规后果 **如果发现接口不符合统一规范,必须立即修改!不符合规范的接口不允许合并到主分支。** ### 🔴 API 开发强制性工作流程 #### 💡 基于场地预约 API 开发实践的标准流程 **⚠️ 本流程基于场地预约管理系统的成功开发经验制定,必须严格遵循!** #### 步骤 1:需求分析和文档创建 **在编写任何代码之前必须完成**: - 📋 **创建需求文档**:`docs/需求文档/[模块名]/` 详细的业务需求说明 - 🏗️ **业务模块文档**:`docs/业务模块/[模块名]/README.md` 技术架构说明 - 🗄️ **数据库设计**:完整的表结构、字典数据、权限配置设计 **参考示例**: - `docs/需求文档/后勤场地预约需求/场地预约系统需求说明.md` - `docs/业务模块/venue-reservation/README.md` #### 步骤 2:数据库初始化(必须先行) **在编写业务代码之前必须完成数据库设计**: - 🗄️ **数据表创建**:`docs/设计文档/database/[模块名]-module.sql` - 📊 **字典数据配置**:`docs/设计文档/database/[模块名]-data.sql` - 🔐 **菜单权限配置**:`docs/设计文档/database/[模块名]-menu.sql` - 🧪 **示例数据插入**:便于开发和测试 **参考示例**: ```sql -- 04-venue-reservation-module.sql (数据表) -- 04-venue-reservation-data.sql (字典数据) -- 04-venue-reservation-menu.sql (菜单权限) ``` #### 步骤 3:后端模块开发 **完整的 ruoyi 模块开发结构**: ``` ruoyi-[模块名]/ ├── pom.xml # 模块依赖配置 ├── src/main/java/org/dromara/[模块名]/ │ ├── domain/ # 实体类 │ │ ├── [Entity].java # 数据库实体(继承TenantEntity) │ │ ├── bo/[EntityBo].java # 业务对象(含验证注解) │ │ └── vo/[EntityVo].java # 视图对象(含Excel注解) │ ├── mapper/ # 数据访问层 │ │ ├── [Entity]Mapper.java # Mapper接口 │ │ └── xml/[Entity]Mapper.xml # SQL映射文件 │ ├── service/ # 业务逻辑层 │ │ ├── I[Entity]Service.java # 服务接口 │ │ └── impl/[Entity]ServiceImpl.java # 服务实现 │ └── controller/ # 控制器层 │ └── [Entity]Controller.java # REST API控制器 └── src/main/resources/ └── mapper/[Entity]Mapper.xml # MyBatis映射文件 ``` **开发要点**: - ✅ **实体类**:必须继承`TenantEntity`,包含完整注释 - ✅ **业务对象**:添加`@Validated`参数校验注解 - ✅ **视图对象**:支持 Excel 导出,添加`@ExcelProperty`注解 - ✅ **权限控制**:每个接口必须添加`@SaCheckPermission`注解 - ✅ **响应格式**:分页用`TableDataInfo`,非分页用`R`类型 #### 步骤 4:API 接口规范实现 **16 个标准 API 接口设计模式**(基于场地预约实践): **基础管理接口(6 个)**: ```java @GetMapping("/list") // 分页查询列表 @GetMapping("/{id}") // 查询详情 @PostMapping // 新增 @PutMapping // 修改 @DeleteMapping("/{ids}") // 删除 @GetMapping("/available") // 获取可用选项 ``` **业务功能接口(4 个)**: ```java @GetMapping("/my") // 查询我的数据 @GetMapping("/pending") // 查询待处理数据 @GetMapping("/conflict") // 业务规则检查 @PostMapping("/approve") // 审批操作 ``` **辅助功能接口(6 个)**: ```java @PostMapping("/export") // 导出Excel @PostMapping("/import") // 导入Excel @GetMapping("/statistics") // 统计数据 @PostMapping("/batch") // 批量操作 @PostMapping("/{id}/enable") // 启用操作 @PostMapping("/{id}/disable") // 禁用操作 ``` #### 步骤 5:立即更新 API 文档(强制性) **⚠️ 代码写完后立即执行,不得延后!** - 📖 **创建模块 API 文档**:`docs/开发文档/API/[模块名]管理API.md` - 📋 **按标准模板编写**:参考`docs/开发文档/API/API文档模板.md` - 🔗 **更新文档总览**:在`docs/开发文档/API/API文档总览.md`中添加导航 - ✅ **包含完整信息**:16 个接口的详细参数、响应、权限、业务规则 **参考示例**: - `docs/开发文档/API/场地预约管理API.md` (400+行完整文档) #### 步骤 6:创建自动化测试脚本 **完整的 Python 测试脚本开发**: - 🐍 **测试脚本**:`test/[模块名]API测试脚本.py` - 🔐 **AES+RSA 加密登录**:严格按照`cursorrules-api-test.md`规范 - 🧪 **完整功能覆盖**:测试所有 16 个 API 接口 - 📋 **业务规则测试**:验证时间冲突、权限控制等 - 🧹 **自动数据清理**:测试完成后清理测试数据 **测试脚本标准结构**: ```python class [Module]APITest: def __init__(self): # 初始化和配置 def login(self): # AES+RSA双重加密登录 def test_[entity]_management(self): # 基础管理功能测试 def test_[business]_features(self): # 业务功能测试 def test_business_rules(self): # 业务规则验证 def cleanup_test_data(self): # 测试数据清理 def run_all_tests(self): # 执行完整测试 ``` #### 步骤 7:模块集成和配置 **模块集成到主项目**: - 📦 **添加模块依赖**:在`ruoyi-modules/pom.xml`中添加子模块 - 🔗 **集成到启动模块**:在`ruoyi-admin/pom.xml`中添加依赖 - ⚙️ **版本管理**:在根`pom.xml`的`dependencyManagement`中配置版本 #### 步骤 8:记录变更日志(强制性) **详细记录开发过程**: - 📝 **变更记录**:`docs/变更管理/code-adjust-record-YYYY-MM-DD-{username}.md` - ⏰ **按时间记录**:需求分析 → 数据库 → 后端开发 → 文档 → 测试的完整流程 - 📊 **包含统计**:文件数量、接口数量、测试覆盖率等 #### 步骤 9:验证和测试 **完整的验证流程**: - 🖥️ **后端服务启动**:确保服务正常运行 - 🧪 **API 功能测试**:运行自动化测试脚本 - 📖 **文档准确性验证**:确保文档示例与实际响应一致 - 🔐 **权限控制测试**:验证接口权限和数据权限 #### 🚨 违规后果 **如果发现以下问题,该代码变更将被视为不完整:** - ❌ 数据库变更未创建对应 SQL 脚本 - ❌ API 接口变更未同步更新文档 - ❌ 缺少自动化测试脚本 - ❌ 未记录详细的变更日志 - ❌ 模块未正确集成到主项目 #### 📊 开发完成标准 **一个完整的 API 模块开发必须包含**: - ✅ 完整的需求和技术文档(2-3 个文档) - ✅ 数据库脚本和权限配置(3 个 SQL 文件) - ✅ 完整的后端模块代码(15-20 个 Java 文件) - ✅ 详细的 API 接口文档(400+行) - ✅ 自动化测试脚本(450+行) - ✅ 完整的变更记录日志 - ✅ 模块成功集成和功能验证 #### 💡 最佳实践总结 **基于场地预约 API 开发的成功经验**: 1. **先文档后代码**:需求分析完成后再开始编码 2. **数据库优先**:表结构确定后再开发业务逻辑 3. **标准化接口**:16 个接口模式覆盖 90%的业务场景 4. **文档同步更新**:代码完成立即更新文档 5. **完整测试验证**:自动化测试确保质量 6. **规范化集成**:严格按照项目架构规范集成 ## 4. 🔐 菜单界面和接口权限配置规范 ### 🚨 权限配置核心原理 **⚠️ 超级重要:每个菜单界面和相关接口都必须在`sys_menu`表中配置对应的权限数据!** #### 权限配置核心概念 1. **菜单权限**:控制用户是否能看到某个菜单项 2. **按钮权限**:控制用户是否能看到和使用某个功能按钮 3. **接口权限**:通过 Sa-Token 注解控制用户是否能调用某个 API 接口 4. **角色权限**:通过`sys_role_menu`表关联,控制哪个角色拥有哪些权限 #### 权限验证机制 ``` 用户登录 → 获取用户角色 → 查询角色权限(sys_role_menu) → 验证接口权限(@SaCheckPermission) → 允许/拒绝访问 ``` ### 🔴 sys_menu 表结构和权限配置 #### sys_menu 表核心字段说明 ```sql -- sys_menu表核心字段 `menu_id` bigint(20) 菜单ID(主键) `menu_name` varchar(50) 菜单名称 `parent_id` bigint(20) 父菜单ID(0表示一级菜单) `order_num` int(4) 显示顺序 `path` varchar(200) 路由地址 `component` varchar(255) 组件路径 `menu_type` char(1) 菜单类型(M目录 C菜单 F按钮) `visible` char(1) 菜单状态(0显示 1隐藏) `status` char(1) 菜单状态(0正常 1停用) `perms` varchar(100) 权限标识(如:system:user:list) `icon` varchar(100) 菜单图标 ``` #### 权限标识(perms)命名规范 ``` 格式:[模块名]:[功能名]:[操作名] 示例: - pm:repairOrder:list # 报修工单列表查询 - pm:repairOrder:add # 报修工单新增 - pm:repairOrder:edit # 报修工单修改 - pm:repairOrder:remove # 报修工单删除 - pm:repairOrder:statistics # 报修统计数据 - pm:category:list # 维修分类列表 - pm:category:options # 维修分类下拉选项 ``` ### 🔧 权限配置标准工作流程 #### 📋 新增功能权限配置流程 **步骤 1:分析权限需求** ``` 1. 确定主菜单名称和路径 2. 列出所有功能按钮(新增、修改、删除、查询等) 3. 列出所有API接口及对应权限 4. 确定权限标识命名 ``` **步骤 2:权限配置 SQL 编写** 使用 UPSERT 逻辑(先判断后操作): ```sql -- 权限配置标准模板(UPSERT逻辑) -- ========================================== -- [模块名]权限配置脚本 -- 功能:为[模块名]添加完整权限配置 -- 时间:YYYY-MM-DD HH:mm:ss -- 说明:使用UPSERT逻辑,避免重复插入 -- ========================================== -- 第一步:检查并插入主菜单 INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) SELECT 2100, '物业管理', 0, 6, 'pm', NULL, '', 1, 0, 'M', '0', '0', '', 'tool', 1, now(), 1, now(), '物业管理模块' WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2100); -- 第二步:检查并插入子菜单 INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) SELECT 2110, '报修管理', 2100, 1, 'repairOrder', 'pm/repairOrder/index', '', 1, 0, 'C', '0', '0', 'pm:repairOrder:list', 'form', 1, now(), 1, now(), '报修工单管理' WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2110); -- 第三步:批量插入功能权限(如果不存在) INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) SELECT * FROM ( SELECT 2111, '查询', 2110, 1, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:query', '#', 1, now(), 1, now(), '查询权限' UNION ALL SELECT 2112, '新增', 2110, 2, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:add', '#', 1, now(), 1, now(), '新增权限' UNION ALL SELECT 2113, '修改', 2110, 3, '', '', '', 1, 0, 'F', '0', '0', 'pm:repairOrder:edit', '#', 1, now(), 1, now(), '修改权限' ) AS temp WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = temp.menu_id); -- 第四步:为admin角色分配权限(如果未分配) INSERT IGNORE INTO sys_role_menu (role_id, menu_id) SELECT 1, menu_id FROM sys_menu WHERE menu_id BETWEEN 2100 AND 2199; ``` **步骤 3:更新现有权限** ```sql -- 更新现有权限信息(如果需要修改) UPDATE sys_menu SET menu_name = '新的菜单名称', path = '新的路径', perms = '新的权限标识', update_time = now(), update_by = 1, remark = '权限更新说明' WHERE menu_id = 2110 AND EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2110); ``` ### 🔄 权限配置操作规范 #### 🚨 强制性检查清单 **每次新增功能时必须检查**: - [ ] 是否需要新增主菜单? - [ ] 是否需要新增子菜单? - [ ] 列出所有需要的功能权限(CRUD 等) - [ ] 列出所有 API 接口及权限标识 - [ ] 确认权限标识命名符合规范 - [ ] 权限配置是否使用 UPSERT 逻辑? - [ ] 是否为相关角色分配了权限? #### 🔍 权限配置验证方法 **验证 SQL 模板**: ```sql -- 1. 验证权限是否已正确配置 SELECT menu_id, menu_name, perms, menu_type FROM sys_menu WHERE perms LIKE 'pm:%' ORDER BY menu_id; -- 2. 验证角色权限分配 SELECT rm.role_id, r.role_name, m.menu_name, m.perms FROM sys_role_menu rm JOIN sys_role r ON rm.role_id = r.role_id JOIN sys_menu m ON rm.menu_id = m.menu_id WHERE m.perms LIKE 'pm:%' ORDER BY rm.role_id, m.menu_id; -- 3. 验证用户权限(通过角色) SELECT u.user_name, r.role_name, m.menu_name, m.perms FROM sys_user u JOIN sys_user_role ur ON u.user_id = ur.user_id JOIN sys_role r ON ur.role_id = r.role_id JOIN sys_role_menu rm ON r.role_id = rm.role_id JOIN sys_menu m ON rm.menu_id = m.menu_id WHERE u.user_name = 'admin' AND m.perms LIKE 'pm:%' ORDER BY m.menu_id; ``` ### 🎯 Controller 权限注解规范 #### 标准权限注解使用 ```java @RestController @RequestMapping("/pm/repairOrder") @Api(tags = "报修工单管理") public class PmRepairOrderController extends BaseController { // 列表查询 - 对应菜单权限 @SaCheckPermission("pm:repairOrder:list") @GetMapping("/list") @Operation(summary = "查询报修工单列表") public TableDataInfo list(PmRepairOrderBo bo, PageQuery pageQuery) { return repairOrderService.queryPageList(bo, pageQuery); } // 详情查询 - 对应按钮权限 @SaCheckPermission("pm:repairOrder:query") @GetMapping("/{id}") @Operation(summary = "获取报修工单详细信息") public R getInfo(@PathVariable Long id) { return R.ok(repairOrderService.queryById(id)); } // 新增功能 - 对应按钮权限 @SaCheckPermission("pm:repairOrder:add") @PostMapping @Operation(summary = "新增报修工单") public R add(@RequestBody PmRepairOrderBo bo) { return toAjax(repairOrderService.insertByBo(bo)); } // 修改功能 - 对应按钮权限 @SaCheckPermission("pm:repairOrder:edit") @PutMapping @Operation(summary = "修改报修工单") public R edit(@RequestBody PmRepairOrderBo bo) { return toAjax(repairOrderService.updateByBo(bo)); } // 删除功能 - 对应按钮权限 @SaCheckPermission("pm:repairOrder:remove") @DeleteMapping("/{ids}") @Operation(summary = "删除报修工单") public R remove(@PathVariable Long[] ids) { return toAjax(repairOrderService.deleteWithValidByIds(List.of(ids))); } // 统计数据 - 对应功能权限 @SaCheckPermission("pm:repairOrder:statistics") @GetMapping("/statistics") @Operation(summary = "获取报修统计数据") public R> statistics() { return R.ok(repairOrderService.getStatistics()); } // 下拉选项 - 对应功能权限 @SaCheckPermission("pm:repairOrder:generateOrderNo") @PostMapping("/generateOrderNo") @Operation(summary = "生成工单编号") public R generateOrderNo() { return R.ok(repairOrderService.generateOrderNo()); } } ``` #### 权限注解检查清单 - [ ] 每个 Controller 方法都有@SaCheckPermission 注解 - [ ] 权限标识与 sys_menu 表中的 perms 字段一致 - [ ] 权限标识命名符合[模块]:[功能]:[操作]规范 - [ ] 所有权限标识都已在 sys_menu 表中配置 ### 🖥️ 前端权限控制规范 #### Vue 页面权限控制 ```vue ``` #### API 调用权限检查 ```typescript // api调用前的权限检查(可选) import { checkPermi } from '@/utils/permission'; // 在调用API前检查权限 const handleAdd = () => { if (!checkPermi(['pm:repairOrder:add'])) { ElMessage.error('您没有新增权限'); return; } // 执行新增操作 router.push('/pm/repairOrder/add'); }; const handleEdit = (row: any) => { if (!checkPermi(['pm:repairOrder:edit'])) { ElMessage.error('您没有修改权限'); return; } // 执行修改操作 router.push(`/pm/repairOrder/edit/${row.id}`); }; ``` ### 📋 权限配置标准模板 #### 完整权限配置脚本模板 ```sql -- ========================================== -- [模块名称]权限配置脚本 -- 功能:为[模块名称]添加完整权限配置 -- 时间:YYYY-MM-DD HH:mm:ss (⚠️必须填写真实时间!) -- 说明:使用UPSERT逻辑,支持重复执行 -- ========================================== -- 清理旧数据(谨慎使用,仅在测试环境) -- DELETE FROM sys_role_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID]; -- DELETE FROM sys_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID]; -- 第一部分:主菜单配置 INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) SELECT [主菜单ID], '[主菜单名称]', 0, [排序号], '[路径]', NULL, '', 1, 0, 'M', '0', '0', '', '[图标]', 1, now(), 1, now(), '[模块描述]' WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = [主菜单ID]); -- 第二部分:子菜单配置 INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) SELECT [子菜单ID], '[子菜单名称]', [主菜单ID], [排序号], '[路径]', '[组件路径]', '', 1, 0, 'C', '0', '0', '[模块]:[功能]:list', '[图标]', 1, now(), 1, now(), '[功能描述]' WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = [子菜单ID]); -- 第三部分:功能权限配置(批量插入) INSERT INTO sys_menu ( menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark ) SELECT * FROM ( -- 基础CRUD权限 SELECT [权限ID1], '[功能名称]查询', [子菜单ID], 1, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:query', '#', 1, now(), 1, now(), '查询权限' UNION ALL SELECT [权限ID2], '[功能名称]新增', [子菜单ID], 2, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:add', '#', 1, now(), 1, now(), '新增权限' UNION ALL SELECT [权限ID3], '[功能名称]修改', [子菜单ID], 3, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:edit', '#', 1, now(), 1, now(), '修改权限' UNION ALL SELECT [权限ID4], '[功能名称]删除', [子菜单ID], 4, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:remove', '#', 1, now(), 1, now(), '删除权限' UNION ALL -- 特殊功能权限 SELECT [权限ID5], '[特殊功能名称]', [子菜单ID], 5, '', '', '', 1, 0, 'F', '0', '0', '[模块]:[功能]:[特殊操作]', '#', 1, now(), 1, now(), '[特殊功能描述]' ) AS temp WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = temp.menu_id); -- 第四部分:权限分配(为admin角色分配权限) INSERT IGNORE INTO sys_role_menu (role_id, menu_id) SELECT 1, menu_id FROM sys_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID]; -- 第五部分:验证配置结果 SELECT '权限配置验证' as message, COUNT(*) as menu_count, (SELECT COUNT(*) FROM sys_role_menu WHERE role_id = 1 AND menu_id BETWEEN [起始ID] AND [结束ID]) as role_permission_count FROM sys_menu WHERE menu_id BETWEEN [起始ID] AND [结束ID]; ``` ### 🧪 权限配置测试和验证 #### 权限测试标准流程 ``` 1. **权限配置验证** - 检查sys_menu表中权限记录 - 检查sys_role_menu表中角色权限分配 - 验证权限标识命名规范 ↓ 2. **后端接口测试** - 使用Token调用接口 - 验证权限认证是否正常 - 测试无权限用户访问是否被拒绝 ↓ 3. **前端界面测试** - 登录系统查看菜单是否显示 - 检查功能按钮是否根据权限显示/隐藏 - 验证无权限用户的界面表现 ↓ 4. **权限缓存测试** - 重启服务后权限是否正常 - 重新登录后权限是否正确加载 - 权限变更后是否需要重新登录 ``` #### 权限测试脚本模板 ```javascript // 权限验证测试脚本 async function testPermissions(token, permissions) { const results = []; for (const perm of permissions) { try { const response = await callAPIWithToken(token, perm.endpoint, perm.data, perm.method); results.push({ permission: perm.name, endpoint: perm.endpoint, success: response.success, status: response.status, message: response.success ? '权限验证通过' : '权限验证失败', }); } catch (error) { results.push({ permission: perm.name, endpoint: perm.endpoint, success: false, status: 'ERROR', message: error.message, }); } } return results; } // 使用示例 const permissionTests = [ { name: 'pm:repairOrder:list', endpoint: '/pm/repairOrder/list', method: 'GET', data: null }, { name: 'pm:repairOrder:add', endpoint: '/pm/repairOrder', method: 'POST', data: {} }, { name: 'pm:repairOrder:statistics', endpoint: '/pm/repairOrder/statistics', method: 'GET', data: null, }, ]; // 执行权限测试 testPermissions(token, permissionTests).then((results) => { console.log('权限测试结果:', results); }); ``` ### 🚨 权限配置强制要求总结 #### 必须遵循的规范 1. **⚠️ 每个新功能都必须配置权限**:菜单权限 + 按钮权限 + 接口权限 2. **⚠️ 使用 UPSERT 逻辑**:权限配置脚本必须支持重复执行 3. **⚠️ 权限标识规范**:严格按照[模块]:[功能]:[操作]格式命名 4. **⚠️ Controller 注解必须**:每个接口方法都要有@SaCheckPermission 注解 5. **⚠️ 前端权限控制**:功能按钮必须使用 v-hasPermi 指令控制 6. **⚠️ 权限测试验证**:配置完成后必须进行完整的权限测试 7. **⚠️ 文档同步更新**:权限配置必须在 API 文档中说明 #### 违规检查清单 - [ ] 新功能是否缺少权限配置? - [ ] Controller 方法是否缺少权限注解? - [ ] 权限标识命名是否符合规范? - [ ] 前端按钮是否有权限控制? - [ ] 权限配置是否支持重复执行? - [ ] 是否进行了权限功能测试? - [ ] API 文档是否更新了权限信息? ### 🔴 VO 类基础字段规范(强制性) **⚠️ 超级重要:为了项目一致性,所有 VO 类必须包含统一的基础字段!** #### 🎯 规范背景 基于实际开发中发现的问题,不同查询方式对 VO 类字段的要求存在差异: | 查询方式 | 基础字段处理 | VO 类要求 | 示例 | | ------------------------- | -------------- | -------------- | ---------------------- | | **MyBatis-Plus 自动查询** | 不包含继承字段 | 无需基础字段 | `HqSatCategoryVo` | | **自定义 SQL 查询** | 明确包含字段 | 必须有对应字段 | `HqVenueReservationVo` | **问题案例**: ``` org.mybatis.spring.MyBatisSystemException: Could not set property 'createBy' of 'class org.dromara.hq.domain.vo.HqVenueReservationVo' with value '1' Cause: There is no setter for property named 'createBy' ``` #### 🚨 强制性统一规范 **为了避免开发过程中的不一致问题,所有 VO 类必须包含完整的基础字段!** #### 📋 标准 VO 类基础字段模板 ```java /** * 标准VO类模板 * * @author [作者] */ @Data @ExcelIgnoreUnannotated @AutoMapper(target = [Entity].class) public class [Entity]Vo implements Serializable { @Serial private static final long serialVersionUID = 1L; // ========== 业务字段 ========== /** * 主键ID */ @ExcelProperty(value = "主键ID") private Long id; // ... 其他业务字段 ... // ========== 基础字段(强制性,所有VO类必须包含) ========== /** * 创建者 */ private Long createBy; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @ExcelProperty(value = "创建时间") private Date createTime; /** * 更新者 */ private Long updateBy; /** * 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; } ``` #### 🛠️ 基础字段说明 | 字段 | 类型 | 必需 | 说明 | JSON 格式化 | Excel 导出 | | -------------- | ------ | ---- | --------- | ----------- | ---------- | | **createBy** | `Long` | ✅ | 创建者 ID | 否 | 否 | | **createTime** | `Date` | ✅ | 创建时间 | ✅ | ✅ | | **updateBy** | `Long` | ✅ | 更新者 ID | 否 | 否 | | **updateTime** | `Date` | ✅ | 更新时间 | ✅ | 否 | #### 📝 字段使用注解规范 ```java // 时间字段必须使用JSON格式化注解 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; // 创建时间可选择性导出到Excel @ExcelProperty(value = "创建时间") private Date createTime; // 更新时间通常不导出到Excel private Date updateTime; ``` #### 🚨 强制性要求 1. **⚠️ 所有 VO 类必须包含四个基础字段**:createBy、createTime、updateBy、updateTime 2. **⚠️ 字段类型必须统一**:createBy/updateBy 为 Long,createTime/updateTime 为 Date 3. **⚠️ 时间字段必须使用 JSON 格式化注解**:`@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` 4. **⚠️ 新生成的 VO 类必须检查基础字段完整性** 5. **⚠️ 修复现有 VO 类时必须补全缺失的基础字段** #### 🔍 VO 类检查清单 **新增 VO 类检查**: - [ ] 是否包含 createBy 字段? - [ ] 是否包含 createTime 字段? - [ ] 是否包含 updateBy 字段? - [ ] 是否包含 updateTime 字段? - [ ] 时间字段是否有 JSON 格式化注解? - [ ] 字段类型是否正确(Long/Date)? **现有 VO 类修复**: - [ ] 检查 MyBatis 映射错误日志中提到的字段 - [ ] 补全缺失的基础字段 - [ ] 重新构建模块 jar 包 - [ ] 触发热重启验证修复效果 #### 💡 最佳实践 **1. 代码生成器配置** ```java // 在代码生成模板中包含基础字段 // generator/template/vo.java.vm /** * 创建者 */ private Long createBy; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @ExcelProperty(value = "创建时间") private Date createTime; /** * 更新者 */ private Long updateBy; /** * 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; ``` **2. 开发流程规范** ``` 新增VO类 → 使用标准模板 → 包含基础字段 → 编译测试 修复VO类 → 补全缺失字段 → 重新构建 → 热重启验证 ``` **3. 问题预防措施** - 定期检查 VO 类与 Entity 类的字段对应关系 - MyBatis 映射错误要及时检查字段定义 - 使用 IDE 插件自动检查 VO 类完整性 - 在代码评审中重点检查 VO 类基础字段 #### 🎯 修复现有问题的标准流程 **当遇到 MyBatis 映射错误时**: ```bash # 1. 分析错误日志,确认缺失字段 grep "Could not set property" logs/error.log # 2. 修复VO类,添加缺失字段 # 编辑对应的VO类文件 # 3. 重新构建模块 cd ruoyi-modules/[模块名] mvn clean install -U -Dmaven.test.skip=true # 4. 重新构建主应用 cd ../../ruoyi-admin mvn clean compile -U -Dmaven.test.skip=true # 5. 触发热重启 echo "reload" > src/main/resources/.reloadtrigger ``` #### 📊 实际修复案例总结 **案例 1:HqVenueInfoVo** ```java // 问题:缺少createBy、updateBy、updateTime字段 // 修复:添加标准基础字段 // 结果:场地管理界面正常加载 ``` **案例 2:HqVenueReservationVo** ```java // 问题:缺少createBy、updateBy、updateTime字段 // 修复:添加标准基础字段 // 结果:场地预约界面正常加载 ``` **案例 3:HqSatCategoryVo** ```java // 情况:使用MyBatis-Plus自动查询,无需基础字段 // 建议:为了一致性,仍建议添加基础字段 // 优势:支持未来可能的自定义SQL查询 ``` ### 📋 VO 类规范违规检查 **开发阶段检查**: - 新增 VO 类时未包含基础字段 - 时间字段未使用 JSON 格式化注解 - 字段类型不符合规范(如用 String 代替 Date) **测试阶段检查**: - MyBatis 映射错误日志中提到字段缺失 - Excel 导出功能异常 - 时间格式显示不正确 **生产阶段检查**: - 接口返回数据中基础字段为空 - 前端显示创建时间/更新时间异常 - 数据审计功能不完整 ### 🎯 规范总结 **统一 VO 类基础字段规范的目标**: 1. **🔄 兼容性**:无论使用 MyBatis-Plus 自动查询还是自定义 SQL,都能正常工作 2. **📊 一致性**:所有 VO 类具有统一的基础字段结构 3. **🚀 可维护性**:减少因字段缺失导致的运行时错误 4. **📈 可扩展性**:支持未来可能的查询方式变更 **⚠️ 记住:宁可多加字段保证兼容性,也不要因为字段缺失导致系统异常!** ### 🔴 MyBatis-Plus 自定义 SQL 规范(强制性) **⚠️ 基于实际开发中遇到的 SQL 拼接错误,制定强制性规范!** #### 🎯 常见问题 **SQL 拼接错误案例**: ```xml ``` **生成的错误 SQL**: ```sql WHERE and deleted = 0 AND (applicant_id = ?) ORDER BY create_time DESC order by create_time desc ``` #### 🚨 强制性规范 **1. `${ew.sqlSegment}`使用规范** ```xml ``` **要点**: - ✅ `${ew.sqlSegment}`内容**不包含前置 AND** - ✅ 固定条件和动态条件之间**必须显式添加 AND** - ✅ 使用``标签进行安全的条件拼接 - ✅ 使用``标签自动处理 WHERE 关键字 **2. ORDER BY 重复问题** ```java // Java代码中设置排序(推荐) LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.orderByDesc(Entity::getCreateTime); ``` ```xml ``` **3. 标准 Mapper.xml 模板** ```xml select id, [业务字段], create_by, create_time, update_by, update_time from [table_name] ``` #### 🔍 SQL 拼接检查清单 **开发阶段检查**: - [ ] `${ew.sqlSegment}`前是否误加了`AND`? - [ ] 是否存在重复的 ORDER BY 子句? - [ ] ``标签是否正确使用? - [ ] VO 类是否包含完整的基础字段? **测试阶段检查**: - [ ] 分页查询是否正常工作? - [ ] SQL 日志中是否有语法错误? - [ ] 条件查询是否按预期执行? #### 🛠️ 调试和排错 **启用 MyBatis SQL 日志**: ```yaml # application-dev.yml logging: level: org.dromara.[module].mapper: debug ``` **SQL 错误快速定位**: ```bash # 1. 查看错误日志 grep "Failed to process, Error SQL" logs/error.log # 2. 检查生成的SQL结构 # 关注WHERE子句和ORDER BY的拼接 # 3. 验证Mapper.xml中的拼接逻辑 ``` #### 💡 最佳实践 **1. 统一的 Mapper 方法命名**: ```java // 分页查询方法 Page selectEntityPage(Page page, @Param("ew") LambdaQueryWrapper lqw); // 列表查询方法 List selectEntityList(@Param("ew") LambdaQueryWrapper lqw); ``` **2. Service 中的条件构造**: ```java private LambdaQueryWrapper buildQueryWrapper(EntityBo bo) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); // 添加查询条件 lqw.eq(ObjectUtil.isNotNull(bo.getId()), Entity::getId, bo.getId()); // 设置排序(统一在Java代码中设置) lqw.orderByDesc(Entity::getCreateTime); return lqw; } ``` **3. 错误预防措施**: - 代码生成器模板中使用标准的 SQL 拼接格式 - 代码评审时重点检查 Mapper.xml 的 SQL 拼接 - 单元测试中验证生成的 SQL 语句 - 使用 IDE 插件检查 XML 语法 #### 🚨 强制性要求总结 1. **⚠️ 必须在固定条件和`${ew.sqlSegment}`之间显式添加 AND** 2. **⚠️ 必须使用``标签安全拼接`${ew.sqlSegment}`** 3. **⚠️ 禁止在 XML 和 Java 代码中重复设置 ORDER BY** 4. **⚠️ 必须使用``标签处理条件拼接** 5. **⚠️ 必须在 VO 类中包含完整的基础字段** 6. **⚠️ 必须在测试中验证 SQL 拼接结果** **违规处理**:不符合规范的 Mapper.xml 将导致运行时 SQL 错误,必须立即修复! **⚠️ 记住:宁可多加字段保证兼容性,也不要因为字段缺失导致系统异常!** ## 5. 标准代码模板 ### Controller 模板 ```java @RestController @RequestMapping("/system/user") @Api(tags = "用户管理") public class SysUserController extends BaseController { @Autowired private ISysUserService userService; @SaCheckPermission("system:user:list") @GetMapping("/list") @Operation(summary = "查询用户列表") public TableDataInfo list(SysUserBo user, PageQuery pageQuery) { return userService.queryPageList(user, pageQuery); } } ``` ### Service 模板 ```java @Service public class SysUserServiceImpl implements ISysUserService { @Autowired private SysUserMapper userMapper; @Override public TableDataInfo queryPageList(SysUserBo bo, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(bo); Page result = userMapper.selectVoPage(pageQuery.build(), lqw); return TableDataInfo.build(result); } } ``` ### Mapper 模板 ```java @Mapper public interface SysUserMapper extends BaseMapperPlus { /** * 查询用户列表 */ List selectUserList(SysUserBo bo); /** * 根据条件分页查询用户列表 */ Page selectUserPage(Page page, @Param("bo") SysUserBo bo); } ```