# 权限模块设计说明
## 1. 权限架构总览
本系统采用**双层次权限控制架构**:
| 层级 | 控制点 | 实现方式 | 范围 |
|------|--------|----------|------|
| 第一层 - 前端 | 路由访问、按钮显示 | 路由守卫(`meta.roles`)+ `Auth` 组件 / `v-auth` 指令 | 菜单可见性、按钮/操作可见性 |
| 第二层 - 后端 | API 接口 | `@RequirePermission` 注解 + AOP 切面 | 接口访问权限校验 |
两个层级独立校验,前端控制用户体验(隐藏无权限的元素),后端控制数据安全(阻止越权请求)。
---
## 2. 后端权限实现
### 2.1 核心注解
#### `@RequirePermission`
```java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
String value(); // 权限编码,格式: module:operation
String message() default "无操作权限"; // 无权限时的提示信息
}
```
**使用位置**:Controller 方法上,标注该接口所需的权限编码。
#### `@SuperAdmin`
```java
public @interface SuperAdmin {
String message() default "仅超级管理员可访问";
}
```
**使用位置**:Controller 方法上,标注仅超级管理员可访问的接口。
### 2.2 权限校验原理(PermissionAspect)
使用 Spring AOP `@Around` 切面拦截所有带有 `@RequirePermission` 的方法,执行流程:
1. 通过 Sa-Token `StpUtil.getLoginId()` 获取当前登录用户
2. 从 Session 中获取用户的角色 ID 列表
3. **超级管理员豁免**:如果用户角色包含 roleId=1(超级管理员),直接放行
4. 通过 `DataPermissionService.getPermissionCodesByRoleIds()` 查询用户拥有的所有权限编码
5. 检查用户权限列表中是否包含接口所需的权限编码
6. 包含则放行,不包含则返回 `Result.error(403, "无操作权限")`
### 2.3 数据模型
#### t_data_permission(权限定义表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | BIGINT | 主键(雪花算法) |
| permission_code | VARCHAR | 权限编码,如 `product:create` |
| permission_name | VARCHAR | 权限名称,如 `商品创建` |
| module_code | VARCHAR | 模块编码,如 `product` |
| module_name | VARCHAR | 模块名称,如 `商品管理` |
| operation_type | VARCHAR | 操作类型: create/read/update/delete/execute |
| description | VARCHAR | 权限描述 |
| sort_order | INT | 排序 |
| status | INT | 状态: 1-启用, 0-禁用 |
| create_time | DATETIME | 创建时间 |
| update_time | DATETIME | 更新时间 |
#### t_role(角色表)
角色通过 `role_id` 与 `t_role_permission` 关联,实现角色-权限多对多关系。
#### t_role_permission(角色权限关联表)
关联角色与权限的多对多关系。
### 2.4 权限编码规则
格式:`{module_code}:{operation_type}`
| 操作类型 | 适用场景 | 说明 |
|----------|----------|------|
| `read` | 查询类操作 | 列表、详情、统计、导出 |
| `create` | 创建类操作 | 新增记录 |
| `update` | 更新类操作 | 编辑、修改状态、发布/下线、审核 |
| `delete` | 删除类操作 | 删除、批量删除 |
| `export` | 导出操作 | 导出数据 |
| `import` | 导入操作 | 导入数据 |
| `execute` | 执行操作 | 同步、手动执行 |
| `distribute` | 发放操作 | 发放优惠券 |
| `review` | 审核操作 | 审核提现 |
| `generate` | 生成操作 | 生成报表 |
### 2.5 权限编码清单
#### 系统管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| admin:read | 管理员查询 | 管理员管理 |
| admin:create | 管理员新增 | 管理员管理 |
| admin:update | 管理员编辑 | 管理员管理 |
| admin:delete | 管理员删除 | 管理员管理 |
| role:read | 角色查询 | 角色管理 |
| role:create | 角色新增 | 角色管理 |
| role:update | 角色编辑 | 角色管理 |
| role:delete | 角色删除 | 角色管理 |
| dict:read | 字典查询 | 字典管理 |
| dict:create | 字典新增 | 字典管理 |
| dict:update | 字典编辑 | 字典管理 |
| dict:delete | 字典删除 | 字典管理 |
| dict:export | 字典导出 | 字典管理 |
| dict:import | 字典导入 | 字典管理 |
#### 运营监控
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| operation-log:read | 操作日志查询 | 操作日志 |
| operation-log:delete | 操作日志删除 | 操作日志 |
| sync:read | 同步记录查询 | 数据同步 |
| sync:execute | 执行数据同步 | 数据同步 |
#### 公告管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| announcement:read | 公告查询 | 公告管理 |
| announcement:create | 公告新增 | 公告管理 |
| announcement:update | 公告编辑 | 公告管理 |
| announcement:delete | 公告删除 | 公告管理 |
#### 订单管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| order:read | 订单查询 | 订单管理 |
| order:update | 订单编辑/退款审核 | 订单管理 |
#### 商品管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| product:read | 商品查询 | 商品管理 |
| product:create | 商品新增 | 商品管理 |
| product:update | 商品编辑/同步 | 商品管理 |
| product:delete | 商品删除 | 商品管理 |
| product-apply:read | 新品申请查询 | 新品申请 |
| product-apply:create | 新品申请提交 | 新品申请 |
| product-apply:update | 新品申请更新 | 新品申请 |
| product-apply:delete | 新品申请删除 | 新品申请 |
#### 门店管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| shop:read | 门店查询 | 门店管理 |
| shop:create | 门店新增 | 门店管理 |
| shop:update | 门店编辑/设备关联/补货员管理 | 门店管理 |
| shop:delete | 门店删除 | 门店管理 |
#### 设备管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| device:read | 设备查询/详情/统计 | 设备管理 |
| device:update | 设备远程控制/配置 | 设备管理 |
#### 设备告警
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| device-alert:read | 告警记录查询/统计 | 设备告警 |
#### 库存管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| inventory:read | 库存查询/日志/统计 | 库存管理 |
| inventory:create | 创建上货记录 | 库存管理 |
| inventory:update | 库存调整/上货完成 | 库存管理 |
#### 客户管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| user:read | 客户查询/详情/统计 | 客户管理 |
| user:update | 客户状态/信用分更新 | 客户管理 |
#### 营销中心
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| dashboard:read | 数据概览 | 数据概览 |
| marketing:activity:read | 活动查询 | 营销活动 |
| marketing:activity:create | 活动创建 | 营销活动 |
| marketing:activity:edit | 活动编辑/暂停/恢复 | 营销活动 |
| marketing:activity:publish | 活动发布 | 营销活动 |
| marketing:activity:delete | 活动删除 | 营销活动 |
| marketing:activity:list | 统一活动列表 | 统一活动 |
| marketing:coupon:read | 优惠券查询 | 优惠券管理 |
| marketing:coupon:create | 优惠券创建 | 优惠券管理 |
| marketing:coupon:edit | 优惠券编辑 | 优惠券管理 |
| marketing:coupon:delete | 优惠券删除 | 优惠券管理 |
| marketing:coupon:distribute | 优惠券发放 | 优惠券管理 |
| marketing:invite:read | 邀请活动查询 | 邀请活动 |
| marketing:invite:create | 邀请活动创建 | 邀请活动 |
| marketing:invite:edit | 邀请活动编辑 | 邀请活动 |
| timed-discount:read | 定时折扣查询 | 定时折扣 |
| timed-discount:create | 定时折扣创建 | 定时折扣 |
| timed-discount:edit | 定时折扣编辑 | 定时折扣 |
| timed-discount:delete | 定时折扣删除 | 定时折扣 |
| timed-discount:execute | 定时折扣执行 | 定时折扣 |
#### 分销管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| distribution:distributor:create | 分销员创建 | 分销管理 |
| distribution:distributor:read | 分销员查询 | 分销管理 |
| distribution:distributor:update | 分销员编辑/激活/停用 | 分销管理 |
| distribution:distributor:delete | 分销员删除 | 分销管理 |
| distribution:referral:read | 推荐记录查询 | 分销管理 |
| distribution:commission:read | 佣金记录查询 | 分销管理 |
| distribution:withdrawal:read | 提现记录查询 | 分销管理 |
| distribution:withdrawal:review | 提现审核/确认到账 | 分销管理 |
| distribution:report:read | 月度报表查询 | 分销管理 |
| distribution:report:generate | 生成月度报表 | 分销管理 |
| distribution:report:export | 导出月度报表 | 分销管理 |
| distribution:dashboard:read | 分销概览 | 分销管理 |
| distribution:config:read | 分销配置查询 | 分销管理 |
| distribution:config:update | 分销配置更新 | 分销管理 |
#### 统计报表
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| statistics:read | 统计概览 | 统计报表 |
| statistics:category | 品类统计 | 统计报表 |
| statistics:product | 商品统计 | 统计报表 |
| statistics:device | 设备统计 | 统计报表 |
| statistics:shop | 门店统计 | 统计报表 |
| statistics:profit | 利润统计 | 统计报表 |
| statistics:repurchase | 复购统计 | 统计报表 |
| statistics:export | 统计导出 | 统计报表 |
#### 设备层模版
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| layer-template:read | 层模版查询 | 层模版管理 |
| layer-template:create | 层模版创建 | 层模版管理 |
| layer-template:update | 层模版编辑/同步 | 层模版管理 |
| layer-template:delete | 层模版删除 | 层模版管理 |
#### 签到管理
| 权限编码 | 权限名称 | 所属模块 |
|----------|----------|----------|
| checkin:read | 签到记录查询 | 签到管理 |
| checkin:create | 签到提交/同步 | 签到管理 |
### 2.6 业务异常抛出
```java
// 使用 ResponseEnum 枚举
throw new BusinessException(ResponseEnum.ORDER_NOT_FOUND);
// 自定义消息
throw new BusinessException(404, "订单不存在");
// 仅消息(默认500)
throw new BusinessException("操作失败");
```
### 2.7 操作日志注解 `@Log`
写操作(增/删/改)必须添加操作日志注解:
```java
@Log(module = "模块名", operation = OperationType.XXX, summary = "操作描述")
```
| OperationType | 说明 |
|---------------|------|
| INSERT | 新增操作 |
| UPDATE | 更新操作 |
| DELETE | 删除操作 |
| SYNC | 同步操作 |
| EXPORT | 导出操作 |
| IMPORT | 导入操作 |
| LOGIN | 登录操作 |
| LOGOUT | 登出操作 |
| OTHER | 其他操作 |
---
## 3. 前端权限实现
### 3.1 路由级权限控制
**路由配置**:在 `src/router/modules/` 下的路由配置文件中,通过 `meta.roles` 指定允许访问的角色:
```ts
meta: {
title: "管理员管理",
roles: ["admin"] // 仅 admin 角色可访问
}
```
**路由守卫**:`src/router/utils.ts` 中的 `filterTree` 和 `initRouter` 函数根据用户角色动态过滤路由。
### 3.2 按钮级权限控制
提供两种方式:
#### `v-auth` 指令(基于路由 meta 中的 auth 配置)
```vue
新增
```
在路由配置中定义按钮级权限码:
```ts
meta: {
auths: ["btn.add", "btn.edit", "btn.delete"]
}
```
#### `v-perms` 指令(基于用户 permissions 列表)
```vue
新增商品
```
用户的 `permissions` 在登录时从后端获取并存储到 `userStore` 中。如果用户拥有 `*:*:*`(超级管理员权限),则所有操作均可执行。
### 3.3 登录与权限加载流程
1. 调用 `/login` API 登录
2. 后端返回 `accessToken`、`refreshToken`、用户信息(包含角色和权限列表)
3. 前端存储 Token 到 Cookie(`authorized-token`)和 localStorage(`user-info`)
4. `useUserStoreHook().SET_ROLES/SET_PERMS` 存储角色和权限到 Pinia store
5. 路由守卫 `initRouter()` 根据用户角色动态过滤可访问的路由
6. 401 响应自动触发登出流程
### 3.4 Token 管理
- Token 存储在 Cookie 和 localStorage 双保险
- 请求头格式:`Authorization: Bearer {token}`
- 无感刷新机制:Token 过期时自动调用刷新接口
- 401 响应自动清除 Token 并跳转登录页
---
## 4. 完整权限校验流程图
```
用户请求
|
v
[前端路由守卫] ── 检查 meta.roles
| 无权限 → 跳转 403 页面
| 有权限 → 加载页面
v
[前端按钮级 v-perms/v-auth]
| 无权限 → DOM 元素被移除
| 有权限 → 显示按钮
v
[前端发起 API 请求] ── 携带 Bearer Token
|
v
[Sa-Token 拦截器] ── 验证 Token 有效性
| 无效 → 返回 401
| 有效 → 继续
v
[AOP @RequirePermission 切面]
| 无注解 → 放行
| 有注解:
| 获取用户角色列表
| 是超级管理员(roleId=1) → 放行
| 检查用户权限列表是否包含所需权限
| 不包含 → 返回 403
| 包含 → 放行
v
[Controller 执行业务逻辑]
```
---
## 5. 注意事项与最佳实践
### 5.1 权限编码命名规范
- 格式:`{module_code}:{operation_type}`
- `module_code` 使用小写字母,多词用中划线连接(如 `timed-discount`、`operation-log`)
- `operation_type` 使用小写字母
### 5.2 新增功能权限纳入规范
开发新功能时,必须按以下流程确保权限控制完整:
1. Controller 中所有接口必须添加 `@RequirePermission` 注解
2. 写操作接口(增/删/改)必须添加 `@Log` 注解
3. 在 `t_data_permission` 表中添加对应的权限记录
4. 前端菜单配置中添加 `meta.roles` 和按钮级权限码
5. 本文档中补充新增的权限编码
### 5.3 常见错误
- **UserController 缺少权限注解**(已修复)—— 客户管理模块完全缺失 `@RequirePermission`
- **DeviceAlertController 缺少权限注解**(已修复)—— 设备告警模块完全缺失 `@RequirePermission`
- **LayerTemplateController 临时移除权限**(已修复)—— 三个接口的 `@RequirePermission` 被注释并标注 TODO
- **辅助查询接口缺少权限**(已修复)—— 部分下拉选择接口未加权限注解
### 5.4 接口分类与权限策略
| 接口类型 | 权限策略 | 示例 |
|----------|----------|------|
| 查询列表 | `module:read` | `@RequirePermission("order:read")` |
| 查询详情 | `module:read` | `@RequirePermission("order:read")` |
| 新增 | `module:create` | `@RequirePermission("product:create")` |
| 编辑 | `module:update` | `@RequirePermission("product:update")` |
| 删除 | `module:delete` | `@RequirePermission("product:delete")` |
| 状态变更 | `module:update` | `@RequirePermission("shop:update")` |
| 审核 | `module:review` | `@RequirePermission("distribution:withdrawal:review")` |
| 同步 | `module:update` | `@RequirePermission("layer-template:update")` |
| 导出 | `module:export` | `@RequirePermission("statistics:export")` |
| 登录 | 无需权限(`@SaIgnore`) | `AdminLoginController.login()` |
---
## 6. 前端菜单与后端权限映射
| 前端菜单 | 后端 Controller | 权限前缀 |
|----------|----------------|----------|
| 数据概览 | DashboardController | dashboard:read |
| 订单管理 | OrderController | order:read/update |
| 退款管理 | RefundApplicationController | order:read/update |
| 门店管理 | ShopController | shop:read/create/update/delete |
| 设备管理 | DeviceController | device:read/update |
| 门店分布地图 | (ShopController 复用) | shop:read |
| 商品列表 | ProductController | product:read/create/update/delete |
| 新品申请 | NewProductApplyController | product-apply:read/create/update/delete |
| 库存管理 | InventoryController | inventory:read/create/update |
| 客户管理 | UserController | user:read/update |
| 营销活动 | MarketingActivityController / UnifiedActivityController | marketing:activity:* |
| 优惠券管理 | CouponController | marketing:coupon:* |
| 定时折扣 | TimedDiscountController | timed-discount:* |
| 邀请活动 | InviteActivityAdminController | marketing:invite:* |
| 分销管理 | DistributorAdminController | distribution:* |
| 统计报表 | StatisticsController | statistics:* |
| 管理员管理 | AdminController | admin:read/create/update/delete |
| 角色管理 | RoleController | role:read/create/update/delete |
| 权限管理 | DataPermissionController | role:read/update |
| 公告管理 | AnnouncementController | announcement:* |
| 操作日志 | OperationLogController | operation-log:read/delete |
| 数据同步 | DataSyncController | sync:read/execute |
| 字典管理 | DictController | dict:read/create/update/delete/export/import |
| 签到管理 | CheckinController | checkin:read/create |
| 设备告警 | DeviceAlertController | device-alert:read |
| 层模版管理 | LayerTemplateController | layer-template:* |
---
## 7. 权限修复记录
### 2026-04-29 修复清单
| 文件 | 问题 | 修复内容 |
|------|------|----------|
| UserController.java | 5个接口完全缺失@RequirePermission | 新增 user:read(列表/详情/统计), user:update(状态/信用分) |
| DeviceAlertController.java | 2个接口完全缺失@RequirePermission | 新增 device-alert:read(列表/统计) |
| LayerTemplateController.java | 3个接口TEMP注释移除权限 | 恢复 layer-template:read(list), layer-template:update(sync/batchSync) |
| DictController.java | getDictDataList缺少@RequirePermission | 新增 dict:read |
| DictController.java | refreshCache缺少@RequirePermission和@Log | 新增 dict:update + 操作日志 |
| RolePermissionMapper.xml | 自定义batchInsert未包含id列,雪花ID不生效 | 改为实体insert方式,利用@TableId自动生成ID |
| haha-admin, haha-miniapp application.yml | 全局缺少db-config.id-type兜底配置 | 新增 db-config.id-type: ASSIGN_ID |
| MarketingActivityController.java | getOngoingActivities缺少@RequirePermission | 新增 marketing:activity:read |
| TimedDiscountController.java | getEnabledActivities缺少@RequirePermission | 新增 timed-discount:read |
| CouponController.java | getAvailableTemplates缺少@RequirePermission | 新增 marketing:coupon:read |
| ShopController.java | getEnabledShops缺少@RequirePermission | 新增 shop:read |