Sprint 3 接口文档 — 菜单权限 & 操作日志
TDD 流程:先定接口 → QA 出用例 → 后端按用例开发 → Code Review → QA 验收
版本:v1.0 Draft | 日期:2026-03-15 | 作者:小虎
0. 约定
沿用 Sprint 2 通用约定(响应格式、认证、分页、角色层级、防提权原则),此处不重复。
新增错误码
| 码 | 含义 | 说明 |
|---|---|---|
| 10101 | 菜单不存在 | 目标 menu_id 无效或已删除 |
| 10102 | 菜单层级过深 | 最多 3 级(目录/菜单/按钮) |
| 10103 | 父菜单不存在 | parent_id 指向无效记录 |
| 10104 | 菜单有子节点 | 删除前需先删除子菜单 |
| 10201 | 权限不存在 | 目标 perm_id 无效或已删除 |
| 10202 | 权限 key 已存在 | permission_key 唯一约束冲突 |
| 10203 | 权限已被引用 | 删除前需先解绑关联的菜单和群组 |
| 10301 | 日志不存在 | 目标 log_id 无效 |
菜单类型
| menu_type | 含义 | 说明 |
|---|---|---|
| 1 | 目录 | 仅用于分组,不关联路由 |
| 2 | 菜单 | 对应前端路由页面 |
| 3 | 按钮 | 页面内操作按钮(权限粒度) |
菜单层级约束
目录(1) → 菜单(2) → 按钮(3)
- 最多 3 级
- 目录下只能放菜单或目录
- 菜单下只能放按钮
- 按钮不能有子节点
1. 菜单管理(T-BE-15)
1.1 获取菜单树
GET /api/v1/menus/tree
权限:admin+ | 权限 key:system:menu:list
Query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| status | int | 否 | 0=禁用 1=启用,不传返回全部 |
响应:
{
"code": 0,
"data": [
{
"menu_id": 1,
"parent_id": 0,
"perm_id": 0,
"menu_name": "系统管理",
"menu_type": 1,
"path": "/system",
"component": "Layout",
"icon": "setting",
"is_visible": 1,
"is_cache": 0,
"sort_order": 1,
"status": 1,
"created_at": "2026-03-12T10:00:29Z",
"children": [
{
"menu_id": 2,
"parent_id": 1,
"perm_id": 1,
"menu_name": "用户管理",
"menu_type": 2,
"path": "/system/users",
"component": "system/user/index",
"icon": "user",
"is_visible": 1,
"is_cache": 0,
"sort_order": 10,
"status": 1,
"created_at": "2026-03-12T10:00:29Z",
"children": []
}
]
}
]
}
说明:
- 返回完整树形结构,按
sort_order ASC排序 - 不包含
is_deleted=1的记录 - 按钮节点也在树中(供权限配置使用)
1.2 获取菜单详情
GET /api/v1/menus/:id
权限:admin+ | 权限 key:system:menu:list
响应:
{
"code": 0,
"data": {
"menu_id": 2,
"parent_id": 1,
"perm_id": 1,
"menu_name": "用户管理",
"menu_type": 2,
"path": "/system/users",
"component": "system/user/index",
"icon": "user",
"is_visible": 1,
"is_cache": 0,
"sort_order": 10,
"status": 1,
"permission": {
"perm_id": 1,
"permission_key": "system:user:list",
"permission_name": "查看用户列表"
},
"created_at": "2026-03-12T10:00:29Z",
"updated_at": "2026-03-12T10:00:29Z"
}
}
1.3 创建菜单
POST /api/v1/menus
权限:superadmin | 权限 key:system:menu:create
请求体:
{
"parent_id": 1,
"menu_name": "角色管理",
"menu_type": 2,
"path": "/system/roles",
"component": "system/role/index",
"icon": "peoples",
"perm_id": 0,
"is_visible": 1,
"is_cache": 0,
"sort_order": 25,
"status": 1
}
校验规则:
| 字段 | 规则 |
|---|---|
| parent_id | 必填,0=顶级目录 |
| menu_name | 必填,1-64 字符 |
| menu_type | 必填,1=目录 2=菜单 3=按钮 |
| path | 目录/菜单必填;按钮可空 |
| component | 菜单必填(Vue 组件路径);目录传 Layout;按钮可空 |
| icon | 可选 |
| perm_id | 可选,关联的权限 ID |
| is_visible | 可选,默认 1 |
| is_cache | 可选,默认 0 |
| sort_order | 可选,默认 0 |
| status | 可选,默认 1 |
层级校验:
- parent_id=0 时 menu_type 只能是 1(目录)
- parent 是目录 → 子节点只能是菜单(2)或目录(1)
- parent 是菜单 → 子节点只能是按钮(3)
- parent 是按钮 → 不允许(10102)
响应:
{
"code": 0,
"data": { "menu_id": 8 }
}
1.4 更新菜单
PUT /api/v1/menus/:id
权限:superadmin | 权限 key:system:menu:update
请求体:同创建(除 menu_type 创建后不可变)
注意:
menu_type不可修改(防止树结构异常)parent_id可修改(拖拽排序),需重新校验层级约束- 修改
perm_id或status后,所有拥有该菜单权限的用户auth_version + 1
1.5 删除菜单
DELETE /api/v1/menus/:id
权限:superadmin | 权限 key:system:menu:delete
规则:
- 有子节点时不允许删除(10104),需先删子菜单
- 软删除:
is_deleted = 1 - 删除后相关用户
auth_version + 1
响应:
{ "code": 0, "data": { "deleted": true } }
1.6 菜单排序(批量更新排序)
PUT /api/v1/menus/sort
权限:superadmin | 权限 key:system:menu:update
请求体:
{
"items": [
{ "menu_id": 2, "sort_order": 10 },
{ "menu_id": 3, "sort_order": 20 },
{ "menu_id": 4, "sort_order": 30 }
]
}
响应:
{ "code": 0, "data": { "updated": 3 } }
2. 权限管理(T-BE-16)
2.1 获取权限列表(按模块分组)
GET /api/v1/permissions
权限:admin+ | 权限 key:system:permission:list
Query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| module | string | 否 | 按模块筛选 |
| keyword | string | 否 | 搜索 permission_name / permission_key |
| status | int | 否 | 0=禁用 1=启用 |
响应:
{
"code": 0,
"data": {
"modules": [
{
"module": "system",
"permissions": [
{
"perm_id": 1,
"permission_key": "system:user:list",
"permission_name": "查看用户列表",
"description": "查看和搜索用户列表及详情",
"status": 1,
"sort_order": 10,
"ref_count": {
"menus": 1,
"groups": 3
},
"created_at": "2026-03-12T10:00:29Z"
}
]
}
],
"total": 24
}
}
说明:
ref_count.menus:关联该权限的菜单数ref_count.groups:关联该权限的群组数- 用于前端展示引用情况,防止误删
2.2 获取权限详情
GET /api/v1/permissions/:id
权限:admin+ | 权限 key:system:permission:list
响应:
{
"code": 0,
"data": {
"perm_id": 1,
"permission_key": "system:user:list",
"permission_name": "查看用户列表",
"module": "system",
"description": "查看和搜索用户列表及详情",
"status": 1,
"sort_order": 10,
"menus": [
{ "menu_id": 2, "menu_name": "用户管理" }
],
"groups": [
{ "group_id": 1, "group_name": "运营组" }
],
"created_at": "2026-03-12T10:00:29Z",
"updated_at": "2026-03-12T10:00:29Z"
}
}
2.3 创建权限
POST /api/v1/permissions
权限:superadmin | 权限 key:system:permission:create
请求体:
{
"permission_key": "order:list",
"permission_name": "查看订单列表",
"module": "order",
"description": "查看和搜索订单列表",
"status": 1,
"sort_order": 100
}
校验规则:
| 字段 | 规则 |
|---|---|
| permission_key | 必填,格式 module:resource:action,1-128 字符,唯一 |
| permission_name | 必填,1-128 字符 |
| module | 必填,1-64 字符 |
| description | 可选,最多 256 字符 |
| status | 可选,默认 1 |
| sort_order | 可选,默认 0 |
响应:
{
"code": 0,
"data": { "perm_id": 25 }
}
2.4 更新权限
PUT /api/v1/permissions/:id
权限:superadmin | 权限 key:system:permission:update
请求体:
{
"permission_name": "查看订单列表(更新)",
"description": "更新描述",
"status": 1,
"sort_order": 101
}
注意:
permission_key创建后不可修改(被中间件引用)module创建后不可修改- 修改
status后,所有拥有该权限的用户auth_version + 1
2.5 删除权限
DELETE /api/v1/permissions/:id
权限:superadmin | 权限 key:system:permission:delete
规则:
- 有菜单或群组引用时不允许删除(10203),需先解绑
- 软删除:
is_deleted = 1 - 删除后相关用户
auth_version + 1
3. 当前用户菜单与权限(T-BE-18)
前端动态路由的核心依赖
3.1 获取当前用户菜单树
GET /api/v1/auth/menus
权限:已登录 | 无需额外权限 key
响应:
{
"code": 0,
"data": [
{
"menu_id": 1,
"parent_id": 0,
"menu_name": "系统管理",
"menu_type": 1,
"path": "/system",
"component": "Layout",
"icon": "setting",
"is_visible": 1,
"is_cache": 0,
"sort_order": 1,
"children": [
{
"menu_id": 2,
"parent_id": 1,
"menu_name": "用户管理",
"menu_type": 2,
"path": "/system/users",
"component": "system/user/index",
"icon": "user",
"is_visible": 1,
"is_cache": 0,
"sort_order": 10,
"children": []
}
]
}
]
}
逻辑:
superadmin:返回所有status=1的菜单- 其他角色:通过
sys_user_group → sys_group_permission → sys_permission → sys_menu链路,返回有权限的菜单 - 只返回
is_deleted=0 AND status=1的记录 - 按钮(menu_type=3) 不在树中返回(前端通过 permissions 列表判断按钮权限)
3.2 获取当前用户权限列表
GET /api/v1/auth/permissions
权限:已登录 | 无需额外权限 key
响应:
{
"code": 0,
"data": {
"role_type": "admin",
"permissions": [
"system:user:list",
"system:user:create",
"system:user:update",
"system:user:delete",
"group:list",
"group:create",
"group:update"
]
}
}
说明:
superadmin返回["*"](前端通配符,代表所有权限)- 其他角色返回实际的 permission_key 列表
- 前端用此列表做按钮级
v-permission指令控制
4. 操作日志(T-BE-19)
4.1 获取操作日志列表
GET /api/v1/logs/operations
权限:admin+ | 权限 key:system:log:list
Query 参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 页码 |
| page_size | int | 否 | 每页条数 |
| operator_id | int | 否 | 按操作人筛选 |
| module | string | 否 | 按模块筛选(user/group/permission/menu) |
| action | string | 否 | 按操作类型筛选(create/update/delete/login/logout) |
| target_type | string | 否 | 按目标类型筛选 |
| start_time | int | 否 | 开始时间(unix 秒) |
| end_time | int | 否 | 结束时间(unix 秒) |
| keyword | string | 否 | 搜索 description / operator_name |
响应:
{
"code": 0,
"data": {
"items": [
{
"log_id": 1178,
"trace_id": "abc123",
"operator_id": 1,
"operator_name": "superadmin",
"target_type": "user",
"target_id": 5,
"action": "update",
"module": "user",
"description": "修改用户 alice 的角色为 admin",
"ip": "192.168.1.100",
"user_agent": "Mozilla/5.0 ...",
"request_method": "PUT",
"request_path": "/api/v1/users/5",
"response_code": 0,
"duration_ms": 45,
"created_at": "2026-03-14T15:30:00Z"
}
],
"total": 1178,
"page": 1,
"page_size": 20
}
}
排序:create_time DESC(最新在前)
4.2 获取操作日志详情
GET /api/v1/logs/operations/:id
权限:admin+ | 权限 key:system:log:list
响应:
{
"code": 0,
"data": {
"log_id": 1178,
"trace_id": "abc123",
"operator_id": 1,
"operator_name": "superadmin",
"target_type": "user",
"target_id": 5,
"action": "update",
"module": "user",
"description": "修改用户 alice 的角色为 admin",
"before_snapshot": {
"role_type": "user",
"status": 1
},
"after_snapshot": {
"role_type": "admin",
"status": 1
},
"ip": "192.168.1.100",
"user_agent": "Mozilla/5.0 ...",
"request_method": "PUT",
"request_path": "/api/v1/users/5",
"request_body": "{\"role_type\":\"admin\"}",
"response_code": 0,
"duration_ms": 45,
"created_at": "2026-03-14T15:30:00Z"
}
}
说明:
before_snapshot/after_snapshot:变更前后的关键字段快照(JSON)request_body:原始请求体(敏感字段如 password 已脱敏)- 操作日志只读,不提供修改和删除接口
5. 前端动态路由方案(T-FE-06)
不涉及后端接口,但需要在文档中明确技术方案
5.1 路由加载流程
用户登录成功
→ 调用 GET /api/v1/auth/menus 获取菜单树
→ 调用 GET /api/v1/auth/permissions 获取权限列表
→ 前端将菜单树转为 vue-router 路由配置
→ router.addRoute() 注册动态路由
→ 将 permissions 列表存入 Pinia
5.2 路由守卫改造
当前 src/router/permissions.ts 需要改造:
constantRoutes只保留:login、403、404、dashboard- 其他所有业务路由(用户管理、群组管理等)从后端菜单树动态生成
setRoutes()方法调用/auth/menus+/auth/permissions- 缓存在 Pinia,刷新页面时重新拉取
5.3 按钮权限指令
<!-- v-permission 指令 -->
<el-button v-permission="'system:user:create'" @click="handleAdd">新增用户</el-button>
<!-- 无权限时按钮不渲染(v-if 语义) -->
实现:
- 注册全局指令
v-permission - 从 Pinia 读取当前用户 permissions 列表
["*"]表示全部权限(superadmin)- 不在列表中的 permission_key →
el.parentNode.removeChild(el)
5.4 Component 映射
后端 sys_menu.component 字段存的是相对路径,前端需要映射:
// 动态导入映射
const modules = import.meta.glob('@/views/**/*.vue')
function resolveComponent(component: string): Component {
if (component === 'Layout') return Layout
const path = `/src/views/${component}.vue`
return modules[path] || (() => import('@/views/404.vue'))
}
6. 接口总览
| # | 方法 | 路径 | 权限 key | 任务 | Phase |
|---|---|---|---|---|---|
| 1 | GET | /api/v1/menus/tree | system:menu:list | T-BE-15 | 1 |
| 2 | GET | /api/v1/menus/:id | system:menu:list | T-BE-15 | 1 |
| 3 | POST | /api/v1/menus | system:menu:create | T-BE-15 | 1 |
| 4 | PUT | /api/v1/menus/:id | system:menu:update | T-BE-15 | 1 |
| 5 | DELETE | /api/v1/menus/:id | system:menu:delete | T-BE-15 | 1 |
| 6 | PUT | /api/v1/menus/sort | system:menu:update | T-BE-15 | 1 |
| 7 | GET | /api/v1/permissions | system:permission:list | T-BE-16 | 1 |
| 8 | GET | /api/v1/permissions/:id | system:permission:list | T-BE-16 | 1 |
| 9 | POST | /api/v1/permissions | system:permission:create | T-BE-16 | 1 |
| 10 | PUT | /api/v1/permissions/:id | system:permission:update | T-BE-16 | 1 |
| 11 | DELETE | /api/v1/permissions/:id | system:permission:delete | T-BE-16 | 1 |
| 12 | GET | /api/v1/auth/menus | (已登录) | T-BE-18 | 2 |
| 13 | GET | /api/v1/auth/permissions | (已登录) | T-BE-18 | 2 |
| 14 | GET | /api/v1/logs/operations | system:log:list | T-BE-19 | 3 |
| 15 | GET | /api/v1/logs/operations/:id | system:log:list | T-BE-19 | 3 |
Sprint 3 新增接口:15 个(不含 Sprint 4 延后的 API 管理模块)
7. 任务分配总览
Phase 1(3/16 - 3/22):后端 CRUD + 菜单管理前端
| 任务 ID | 任务 | 角色 | 优先级 | 接口 |
|---|---|---|---|---|
| T-BE-15 | 菜单 CRUD Controller | 后端侠 | P0 | #1-6 |
| T-BE-16 | 权限 CRUD Controller | 后端侠 | P0 | #7-11 |
| T-FE-04 | 菜单管理页面 | 前端匠 | P0 | 依赖 #1-6 |
Phase 2(3/23 - 3/29):动态路由 + 权限管理前端
| 任务 ID | 任务 | 角色 | 优先级 | 接口 |
|---|---|---|---|---|
| T-BE-18 | 当前用户菜单树 + 权限列表 | 后端侠 | P0 | #12-13 |
| T-FE-05 | 权限管理页面 | 前端匠 | P0 | 依赖 #7-11 |
| T-FE-06 | 动态路由改造 + v-permission | 前端匠 | P0 | 依赖 #12-13 |
Phase 3(3/30 - 4/5):操作日志 + QA
| 任务 ID | 任务 | 角色 | 优先级 | 接口 |
|---|---|---|---|---|
| T-BE-19 | 操作日志查询 Controller | 后端侠 | P1 | #14-15 |
| T-FE-07 | 操作日志页面 | 前端匠 | P1 | 依赖 #14-15 |
| T-QA-08 | 全量测试 | 质检官 | P0 | 全部 |
8. 技术评审待确认
| # | 决策项 | 建议 | 待确认 |
|---|---|---|---|
| 1 | 菜单管理仅 superadmin 可操作 | ✅ 建议 | 是否开放给 admin? |
| 2 | permission_key 创建后不可改 | ✅ 建议 | — |
| 3 | 按钮权限粒度 | v-permission 指令 | 是否需要更细粒度? |
| 4 | 操作日志保留策略 | 只增不删 | 是否需要自动归档/清理? |
| 5 | 前端路由缓存策略 | Pinia + 刷新重拉 | 是否需要 localStorage 持久化? |