📝 主要改进: • Cursor配置模块化:拆分为前端、后端、移动端、文档、API测试5个专门规范文件 • API文档重新组织:按业务模块拆分到docs/开发文档/API/目录下 • 新增维修分类功能完整测试方案和自动化测试脚本 • 后端热启动脚本完善,支持Linux/Windows双平台 • 强化文档同步更新规范,确保代码与文档一致性 🔧 技术改进: • 优化Cursor AI工作流程和代码生成质量 • 完善API测试规范(AES+RSA双重加密登录方法) • 提升开发效率和代码质量标准化程度 �� 测试覆盖: • 维修分类管理功能端到端测试验证通过 • 包含页面加载、CRUD操作、搜索功能等核心测试用例 更新时间:2025-07-01
31 KiB
31 KiB
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-extend/ # 扩展模块组
└── script/ # 脚本和SQL
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
🚨 强制性核心规范
- 所有业务表必须包含标准基础字段
- 统一使用雪花 ID (bigint(20)) 作为主键
- 强制使用逻辑删除,禁止物理删除
- 所有表必须支持多租户
- 完整的中文注释是必须的
- 禁止使用数据库外键约束,关联关系通过应用层维护
🔴 强制性基础字段模板
-- 所有业务表必须包含以下字段(顺序和类型必须完全一致)
`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 进行数据库开发时:
- 必须先阅读
docs/设计文档/database/mysql-database-standard.md规范 - 必须使用标准模板,不得随意修改基础字段
- 必须保持与现有表结构的一致性
- 建表后必须立即更新
hzdyjs_xg.sql文件 - 必须在变更记录中记录 数据库变更
🔴 API 接口规范(强制性)
⚠️ 超级重要:所有 API 接口必须严格遵循以下统一规范!
🔐 请求头规范(强制性)
所有需要认证的接口必须在请求头中包含 Token,格式如下:
{
"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 后面没有空格)
📊 统一响应格式(必须遵循)
基础响应格式(分页列表):
{
"total": 7,
"rows": [],
"code": 200,
"msg": "查询成功"
}
🎯 响应格式详细说明
1. 分页列表响应
{
"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. 非分页列表响应
{
"code": 200,
"msg": "查询成功",
"data": [
{
"id": 1,
"name": "示例数据1"
},
{
"id": 2,
"name": "示例数据2"
}
]
}
3. 单条数据响应
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"name": "示例数据",
"createTime": "2025-06-30 12:00:00"
}
}
4. 操作成功响应(无数据)
{
"code": 200,
"msg": "操作成功",
"data": null
}
5. 错误响应
{
"code": 400,
"msg": "参数错误:用户名不能为空",
"data": null
}
📋 状态码规范
成功状态码:
200- 操作成功201- 创建成功
客户端错误(4xx):
400- 请求参数错误401- 未认证/认证失败403- 无权限访问404- 资源不存在409- 数据冲突
服务端错误(5xx):
500- 服务器内部错误502- 网关错误503- 服务不可用
🚨 接口开发强制要求
1. Controller 返回格式
// ✅ 正确 - 分页接口直接返回TableDataInfo
@GetMapping("/list")
public TableDataInfo<UserVo> list(UserBo bo, PageQuery pageQuery) {
TableDataInfo<UserVo> result = userService.queryPageList(bo, pageQuery);
return result;
}
// ✅ 正确 - 非分页接口使用R类型包装
@GetMapping("/options")
public R<List<UserVo>> options() {
List<UserVo> list = userService.selectUserOptions();
return R.ok(list);
}
// ❌ 错误 - 分页接口使用R类型包装
@GetMapping("/list")
public R<TableDataInfo<UserVo>> list(UserBo bo, PageQuery pageQuery) {
TableDataInfo<UserVo> result = userService.queryPageList(bo, pageQuery);
return R.ok(result); // 这会产生多层嵌套
}
2. 分页接口规范
// 分页查询直接返回 TableDataInfo 数据
@GetMapping("/list")
public TableDataInfo<UserVo> list(UserBo bo, PageQuery pageQuery) {
TableDataInfo<UserVo> result = userService.queryPageList(bo, pageQuery);
return result;
}
// 返回格式:
// {
// "total": 100,
// "rows": [...],
// "code": 200,
// "msg": "查询成功"
// }
3. 非分页列表接口
// 非分页列表查询(下拉选项等)
@GetMapping("/options")
public R<List<UserVo>> options() {
List<UserVo> list = userService.selectUserOptions();
return R.ok(list);
}
// 返回格式:
// {
// "code": 200,
// "msg": "查询成功",
// "data": [...]
// }
4. 单条数据接口
// 单条数据查询
@GetMapping("/{id}")
public R<UserVo> getInfo(@PathVariable Long id) {
UserVo user = userService.queryById(id);
return R.ok(user);
}
// 返回格式:
// {
// "code": 200,
// "msg": "操作成功",
// "data": {...}
// }
5. 操作类接口
// 新增/修改/删除操作
@PostMapping
public R<Void> 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- 禁用用户
🛡️ 权限控制规范
// 每个接口必须添加权限注解
@SaCheckPermission("system:user:list")
@GetMapping("/list")
public R<TableDataInfo<UserVo>> list(UserBo bo, PageQuery pageQuery) {
// 接口实现
}
📝 参数验证规范
// 使用Bean Validation进行参数校验
@PostMapping
public R<Void> 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 开发强制性工作流程
步骤 1:编写后端代码
- 创建 Controller、Service、Mapper 等
- 实现业务逻辑和数据处理
- 添加权限控制和参数验证
步骤 2:立即更新 API 文档(强制性)
⚠️ 代码写完后立即执行,不得延后!
- 打开
docs/开发文档/API.md文件 - 按照
docs/开发文档/API/API文档模板.md的标准格式添加或更新接口文档 - 包含完整的接口信息(地址、参数、响应、权限等)
- 添加真实的请求和响应示例
步骤 3:记录代码调整日志
- 在
docs/变更管理/code-adjust-record.md中记录本次变更 - 特别说明 API 接口的变更内容
- 标注文档已同步更新
步骤 4:验证和测试
- 测试 API 接口功能是否正常
- 验证 API 文档的准确性
- 确保文档示例与实际响应一致
🚨 违规后果
如果发现 API 变更未同步更新文档,该代码变更将被视为不完整,必须立即补齐文档。
4. 🔐 菜单界面和接口权限配置规范
🚨 权限配置核心原理
⚠️ 超级重要:每个菜单界面和相关接口都必须在sys_menu表中配置对应的权限数据!
权限配置核心概念
- 菜单权限:控制用户是否能看到某个菜单项
- 按钮权限:控制用户是否能看到和使用某个功能按钮
- 接口权限:通过 Sa-Token 注解控制用户是否能调用某个 API 接口
- 角色权限:通过
sys_role_menu表关联,控制哪个角色拥有哪些权限
权限验证机制
用户登录 → 获取用户角色 → 查询角色权限(sys_role_menu) → 验证接口权限(@SaCheckPermission) → 允许/拒绝访问
🔴 sys_menu 表结构和权限配置
sys_menu 表核心字段说明
-- 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 逻辑(先判断后操作):
-- 权限配置标准模板(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:更新现有权限
-- 更新现有权限信息(如果需要修改)
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 模板:
-- 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 权限注解规范
标准权限注解使用
@RestController
@RequestMapping("/pm/repairOrder")
@Api(tags = "报修工单管理")
public class PmRepairOrderController extends BaseController {
// 列表查询 - 对应菜单权限
@SaCheckPermission("pm:repairOrder:list")
@GetMapping("/list")
@Operation(summary = "查询报修工单列表")
public TableDataInfo<PmRepairOrderVo> list(PmRepairOrderBo bo, PageQuery pageQuery) {
return repairOrderService.queryPageList(bo, pageQuery);
}
// 详情查询 - 对应按钮权限
@SaCheckPermission("pm:repairOrder:query")
@GetMapping("/{id}")
@Operation(summary = "获取报修工单详细信息")
public R<PmRepairOrderVo> getInfo(@PathVariable Long id) {
return R.ok(repairOrderService.queryById(id));
}
// 新增功能 - 对应按钮权限
@SaCheckPermission("pm:repairOrder:add")
@PostMapping
@Operation(summary = "新增报修工单")
public R<Void> add(@RequestBody PmRepairOrderBo bo) {
return toAjax(repairOrderService.insertByBo(bo));
}
// 修改功能 - 对应按钮权限
@SaCheckPermission("pm:repairOrder:edit")
@PutMapping
@Operation(summary = "修改报修工单")
public R<Void> edit(@RequestBody PmRepairOrderBo bo) {
return toAjax(repairOrderService.updateByBo(bo));
}
// 删除功能 - 对应按钮权限
@SaCheckPermission("pm:repairOrder:remove")
@DeleteMapping("/{ids}")
@Operation(summary = "删除报修工单")
public R<Void> remove(@PathVariable Long[] ids) {
return toAjax(repairOrderService.deleteWithValidByIds(List.of(ids)));
}
// 统计数据 - 对应功能权限
@SaCheckPermission("pm:repairOrder:statistics")
@GetMapping("/statistics")
@Operation(summary = "获取报修统计数据")
public R<Map<String, Object>> statistics() {
return R.ok(repairOrderService.getStatistics());
}
// 下拉选项 - 对应功能权限
@SaCheckPermission("pm:repairOrder:generateOrderNo")
@PostMapping("/generateOrderNo")
@Operation(summary = "生成工单编号")
public R<String> generateOrderNo() {
return R.ok(repairOrderService.generateOrderNo());
}
}
权限注解检查清单
- 每个 Controller 方法都有@SaCheckPermission 注解
- 权限标识与 sys_menu 表中的 perms 字段一致
- 权限标识命名符合[模块]:[功能]:[操作]规范
- 所有权限标识都已在 sys_menu 表中配置
🖥️ 前端权限控制规范
Vue 页面权限控制
<template>
<div class="app-container">
<!-- 功能按钮权限控制 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['pm:repairOrder:add']"
type="primary"
plain
icon="Plus"
@click="handleAdd"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['pm:repairOrder:edit']"
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
>
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['pm:repairOrder:remove']"
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
>
删除
</el-button>
</el-col>
</el-row>
<!-- 表格操作列权限控制 -->
<el-table-column label="操作" align="center" width="160">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button
v-hasPermi="['pm:repairOrder:edit']"
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
v-hasPermi="['pm:repairOrder:remove']"
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</div>
</template>
API 调用权限检查
// 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}`);
};
📋 权限配置标准模板
完整权限配置脚本模板
-- ==========================================
-- [模块名称]权限配置脚本
-- 功能:为[模块名称]添加完整权限配置
-- 时间: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. **权限缓存测试**
- 重启服务后权限是否正常
- 重新登录后权限是否正确加载
- 权限变更后是否需要重新登录
权限测试脚本模板
// 权限验证测试脚本
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);
});
🚨 权限配置强制要求总结
必须遵循的规范
- ⚠️ 每个新功能都必须配置权限:菜单权限 + 按钮权限 + 接口权限
- ⚠️ 使用 UPSERT 逻辑:权限配置脚本必须支持重复执行
- ⚠️ 权限标识规范:严格按照[模块]:[功能]:[操作]格式命名
- ⚠️ Controller 注解必须:每个接口方法都要有@SaCheckPermission 注解
- ⚠️ 前端权限控制:功能按钮必须使用 v-hasPermi 指令控制
- ⚠️ 权限测试验证:配置完成后必须进行完整的权限测试
- ⚠️ 文档同步更新:权限配置必须在 API 文档中说明
违规检查清单
- 新功能是否缺少权限配置?
- Controller 方法是否缺少权限注解?
- 权限标识命名是否符合规范?
- 前端按钮是否有权限控制?
- 权限配置是否支持重复执行?
- 是否进行了权限功能测试?
- API 文档是否更新了权限信息?
5. 标准代码模板
Controller 模板
@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<SysUserVo> list(SysUserBo user, PageQuery pageQuery) {
return userService.queryPageList(user, pageQuery);
}
}
Service 模板
@Service
public class SysUserServiceImpl implements ISysUserService {
@Autowired
private SysUserMapper userMapper;
@Override
public TableDataInfo<SysUserVo> queryPageList(SysUserBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysUser> lqw = buildQueryWrapper(bo);
Page<SysUserVo> result = userMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
}
Mapper 模板
@Mapper
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
/**
* 查询用户列表
*/
List<SysUserVo> selectUserList(SysUserBo bo);
/**
* 根据条件分页查询用户列表
*/
Page<SysUserVo> selectUserPage(Page<SysUser> page, @Param("bo") SysUserBo bo);
}