feat: 实现企业卡授权和授权记录管理功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s
主要功能: - 添加企业卡授权/回收接口 (POST /enterprises/:id/allocate-cards, recall-cards) - 添加授权记录管理接口 (GET/PUT /authorizations) - 实现代理用户数据权限过滤(只能查看自己店铺下企业的授权记录) - 添加 GORM callback 支持授权记录表的数据权限过滤 技术改进: - 原生 SQL 查询手动添加数据权限过滤(ListWithJoin, GetByIDWithJoin) - 移除卡授权预检接口(allocate-cards/preview),保留内部方法 - 完善单元测试和集成测试覆盖
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-26
|
||||
@@ -0,0 +1,123 @@
|
||||
# 授权记录管理 - 技术设计
|
||||
|
||||
## Context
|
||||
|
||||
### 背景
|
||||
|
||||
企业卡授权功能已实现授权/回收操作,数据存储在 `tb_enterprise_card_authorization` 表中。但目前缺少对授权记录本身的管理视角,无法审计授权历史。
|
||||
|
||||
### 现有架构
|
||||
|
||||
```
|
||||
tb_enterprise_card_authorization
|
||||
├── id, created_at, updated_at, deleted_at
|
||||
├── enterprise_id (被授权企业ID)
|
||||
├── card_id (被授权卡ID)
|
||||
├── authorized_by (授权人账号ID)
|
||||
├── authorized_at (授权时间)
|
||||
├── authorizer_type (授权人类型:2=平台,3=代理)
|
||||
├── revoked_by (回收人账号ID)
|
||||
├── revoked_at (回收时间)
|
||||
└── remark (授权备注)
|
||||
```
|
||||
|
||||
### 约束
|
||||
|
||||
1. 表中没有 `shop_id` 字段,需要通过 `enterprise_id` 关联 `tb_enterprise.owner_shop_id` 来判断归属
|
||||
2. 代理用户只能看到自己店铺的数据,不包含下级店铺
|
||||
3. 现有 GORM Callback 对无 `shop_id` 字段的表直接跳过过滤,存在权限漏洞
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 提供授权记录的列表、详情、备注修改接口
|
||||
- 修复数据权限过滤,确保代理只能看到自己店铺下企业的授权记录
|
||||
- 列表接口支持多条件筛选和分页
|
||||
|
||||
**Non-Goals:**
|
||||
- 不提供删除授权记录的能力(只能通过回收操作标记)
|
||||
- 不提供统计接口(后续按需添加)
|
||||
- 不提供导出功能
|
||||
|
||||
## Decisions
|
||||
|
||||
### 决策 1:数据权限过滤方式
|
||||
|
||||
**选择**:在 GORM Callback 中为授权记录表添加特殊处理,使用子查询过滤
|
||||
|
||||
**方案对比**:
|
||||
|
||||
| 方案 | 优点 | 缺点 |
|
||||
|------|------|------|
|
||||
| A. Callback 子查询 ✓ | 自动应用,无需手动过滤 | 子查询可能影响性能 |
|
||||
| B. 添加冗余 shop_id 字段 | 查询简单高效 | 需要迁移,数据一致性风险 |
|
||||
| C. Service 层手动过滤 | 灵活可控 | 容易遗漏,不符合现有模式 |
|
||||
|
||||
**理由**:方案 A 与现有架构一致,子查询性能可接受(授权记录量不大),且自动应用避免遗漏。
|
||||
|
||||
**实现**:
|
||||
```go
|
||||
// pkg/gorm/callback.go
|
||||
if tableName == "tb_enterprise_card_authorization" {
|
||||
// 代理用户:只能看自己店铺下企业的授权记录
|
||||
tx.Where("enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = ?)", shopID)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 决策 2:列表接口关联查询
|
||||
|
||||
**选择**:在 Store 层使用 JOIN 一次性获取关联数据
|
||||
|
||||
**方案对比**:
|
||||
|
||||
| 方案 | 优点 | 缺点 |
|
||||
|------|------|------|
|
||||
| A. Store 层 JOIN ✓ | 单次查询,性能好 | SQL 稍复杂 |
|
||||
| B. Service 层多次查询 | 逻辑清晰 | N+1 问题 |
|
||||
| C. 使用 GORM Preload | 代码简洁 | 需要定义关联关系(项目禁止) |
|
||||
|
||||
**理由**:项目禁止使用 GORM 关联关系,JOIN 是最佳选择。
|
||||
|
||||
**实现**:
|
||||
```sql
|
||||
SELECT
|
||||
a.*,
|
||||
e.enterprise_name,
|
||||
c.iccid, c.msisdn,
|
||||
acc1.username as authorizer_name,
|
||||
acc2.username as revoker_name
|
||||
FROM tb_enterprise_card_authorization a
|
||||
LEFT JOIN tb_enterprise e ON a.enterprise_id = e.id
|
||||
LEFT JOIN tb_iot_card c ON a.card_id = c.id
|
||||
LEFT JOIN tb_account acc1 ON a.authorized_by = acc1.id
|
||||
LEFT JOIN tb_account acc2 ON a.revoked_by = acc2.id
|
||||
WHERE ...
|
||||
```
|
||||
|
||||
### 决策 3:备注修改权限
|
||||
|
||||
**选择**:平台用户可改任意记录,代理用户只能修改可见范围内的记录
|
||||
|
||||
**理由**:
|
||||
- 平台需要管理能力,可以修正任何备注
|
||||
- 代理只能操作自己店铺的数据,符合数据隔离原则
|
||||
- 不限制"只能修改自己创建的",便于同店铺协作
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| 子查询性能 | 大数据量时可能变慢 | 授权记录量可控;必要时添加索引 |
|
||||
| JOIN 查询复杂 | 维护成本增加 | 封装在 Store 层,对外透明 |
|
||||
| 权限逻辑特殊 | 与其他表不一致 | 在 Callback 中添加清晰注释 |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 先发布 Callback 修复(修复权限漏洞)
|
||||
2. 再发布新接口(列表、详情、备注修改)
|
||||
3. 无需数据迁移,无破坏性变更
|
||||
|
||||
## Open Questions
|
||||
|
||||
无
|
||||
@@ -0,0 +1,57 @@
|
||||
# 授权记录管理
|
||||
|
||||
## Why
|
||||
|
||||
现有的企业卡授权功能已完成授权/回收操作,但缺少对授权记录本身的管理视角。平台和代理无法审计"谁在什么时候授权了哪张卡给哪个企业",也无法查看授权的完整生命周期(包括已回收的记录)。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **新增授权记录列表接口**:支持分页、多条件筛选(企业、ICCID、授权人类型、状态、时间范围)
|
||||
- **新增授权记录详情接口**:查看单条授权记录的完整信息(含关联的企业名、卡信息、授权人名)
|
||||
- **新增修改授权备注接口**:支持平台和授权创建者修改授权备注
|
||||
- **修复数据权限过滤**:`tb_enterprise_card_authorization` 表当前未正确应用数据权限过滤,需要添加特殊处理逻辑
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `authorization-record`: 授权记录管理功能,包含列表查询、详情查看、备注修改
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `data-permission`: 数据权限过滤需要为授权记录表添加特殊处理规则(按 `enterprise.owner_shop_id` 过滤,且代理用户只能看自己店铺的数据,不含下级)
|
||||
|
||||
## Impact
|
||||
|
||||
### 代码变更
|
||||
|
||||
| 层级 | 文件 | 变更内容 |
|
||||
|------|------|----------|
|
||||
| Model/DTO | `internal/model/dto/authorization_dto.go` | 新增列表/详情/更新备注的请求响应结构 |
|
||||
| Handler | `internal/handler/admin/authorization.go` | 新增授权记录管理 Handler |
|
||||
| Service | `internal/service/enterprise_card/authorization_service.go` | 扩展授权记录查询和更新方法 |
|
||||
| Store | `internal/store/postgres/enterprise_card_authorization_store.go` | 新增关联查询方法 |
|
||||
| Routes | `internal/routes/authorization.go` | 新增授权记录路由组 |
|
||||
| Callback | `pkg/gorm/callback.go` | 为授权记录表添加特殊的数据权限过滤规则 |
|
||||
|
||||
### API 变更
|
||||
|
||||
| HTTP | 路径 | 功能 |
|
||||
|------|------|------|
|
||||
| `GET` | `/api/admin/authorizations` | 授权记录列表(分页、筛选) |
|
||||
| `GET` | `/api/admin/authorizations/:id` | 授权记录详情 |
|
||||
| `PUT` | `/api/admin/authorizations/:id/remark` | 修改授权备注 |
|
||||
|
||||
### 权限规则
|
||||
|
||||
| 用户类型 | 列表/详情查看 | 修改备注 |
|
||||
|----------|---------------|----------|
|
||||
| 平台用户 | 所有记录 | 可修改任意记录 |
|
||||
| 代理用户 | 仅自己店铺下企业的授权记录(不含下级店铺) | 仅可见范围内的记录 |
|
||||
|
||||
### 数据权限过滤
|
||||
|
||||
授权记录表 `tb_enterprise_card_authorization` 需要特殊处理:
|
||||
- 表中有 `enterprise_id` 字段,但这是被授权的企业,不是操作者归属
|
||||
- 需要通过 `enterprise.owner_shop_id` 关联判断企业归属
|
||||
- 代理用户过滤条件:`WHERE enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = 当前店铺ID)`
|
||||
@@ -0,0 +1,114 @@
|
||||
# authorization-record Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
授权记录管理功能,提供对企业卡授权记录的查询、详情查看和备注修改能力。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 授权记录列表查询
|
||||
|
||||
系统 SHALL 提供授权记录列表接口,支持分页和多条件筛选。
|
||||
|
||||
#### Scenario: 平台用户查询所有授权记录
|
||||
- **WHEN** 平台用户请求 `GET /api/admin/authorizations`
|
||||
- **THEN** 系统返回所有授权记录(包含有效和已回收)
|
||||
- **AND** 每条记录包含企业名称、卡信息(ICCID/MSISDN)、授权人名称
|
||||
|
||||
#### Scenario: 代理用户查询授权记录
|
||||
- **WHEN** 代理用户请求 `GET /api/admin/authorizations`
|
||||
- **THEN** 系统只返回该代理店铺下企业的授权记录
|
||||
- **AND** 不包含下级店铺的授权记录
|
||||
|
||||
#### Scenario: 按企业筛选
|
||||
- **WHEN** 请求包含 `enterprise_id` 参数
|
||||
- **THEN** 系统只返回该企业的授权记录
|
||||
|
||||
#### Scenario: 按ICCID模糊查询
|
||||
- **WHEN** 请求包含 `iccid` 参数
|
||||
- **THEN** 系统返回 ICCID 包含该值的授权记录
|
||||
|
||||
#### Scenario: 按授权人类型筛选
|
||||
- **WHEN** 请求包含 `authorizer_type` 参数(2=平台,3=代理)
|
||||
- **THEN** 系统只返回该类型授权人创建的记录
|
||||
|
||||
#### Scenario: 按状态筛选
|
||||
- **WHEN** 请求包含 `status` 参数
|
||||
- **AND** `status=1` 表示有效,`status=0` 表示已回收
|
||||
- **THEN** 系统只返回对应状态的授权记录
|
||||
|
||||
#### Scenario: 按授权时间范围筛选
|
||||
- **WHEN** 请求包含 `start_time` 和/或 `end_time` 参数
|
||||
- **THEN** 系统只返回授权时间在该范围内的记录
|
||||
|
||||
#### Scenario: 分页查询
|
||||
- **WHEN** 请求包含 `page` 和 `page_size` 参数
|
||||
- **THEN** 系统返回对应页的数据
|
||||
- **AND** 响应包含 `total` 总记录数
|
||||
|
||||
### Requirement: 授权记录详情查询
|
||||
|
||||
系统 SHALL 提供授权记录详情接口,返回单条记录的完整信息。
|
||||
|
||||
#### Scenario: 查询存在的授权记录
|
||||
- **WHEN** 请求 `GET /api/admin/authorizations/:id`
|
||||
- **AND** 记录存在且用户有权限查看
|
||||
- **THEN** 系统返回该授权记录的完整信息
|
||||
- **AND** 包含关联的企业名称、卡信息、授权人名称、回收人名称
|
||||
|
||||
#### Scenario: 查询不存在的授权记录
|
||||
- **WHEN** 请求 `GET /api/admin/authorizations/:id`
|
||||
- **AND** 记录不存在
|
||||
- **THEN** 系统返回 404 错误
|
||||
|
||||
#### Scenario: 查询无权限的授权记录
|
||||
- **WHEN** 代理用户请求 `GET /api/admin/authorizations/:id`
|
||||
- **AND** 该记录不属于代理的店铺
|
||||
- **THEN** 系统返回 404 错误(不暴露记录存在)
|
||||
|
||||
### Requirement: 修改授权备注
|
||||
|
||||
系统 SHALL 提供修改授权备注的接口。
|
||||
|
||||
#### Scenario: 平台用户修改任意备注
|
||||
- **WHEN** 平台用户请求 `PUT /api/admin/authorizations/:id/remark`
|
||||
- **AND** 提供新的备注内容
|
||||
- **THEN** 系统更新该授权记录的备注
|
||||
- **AND** 返回更新后的记录
|
||||
|
||||
#### Scenario: 代理用户修改备注
|
||||
- **WHEN** 代理用户请求 `PUT /api/admin/authorizations/:id/remark`
|
||||
- **AND** 该记录属于代理的店铺
|
||||
- **THEN** 系统更新该授权记录的备注
|
||||
|
||||
#### Scenario: 代理用户修改无权限的备注
|
||||
- **WHEN** 代理用户请求 `PUT /api/admin/authorizations/:id/remark`
|
||||
- **AND** 该记录不属于代理的店铺
|
||||
- **THEN** 系统返回 404 错误
|
||||
|
||||
#### Scenario: 备注长度限制
|
||||
- **WHEN** 请求的备注内容超过 500 字符
|
||||
- **THEN** 系统返回 400 错误,提示备注过长
|
||||
|
||||
### Requirement: 授权记录响应格式
|
||||
|
||||
系统 SHALL 使用统一的响应格式返回授权记录。
|
||||
|
||||
#### Scenario: 列表响应格式
|
||||
- **WHEN** 返回授权记录列表
|
||||
- **THEN** 每条记录包含以下字段:
|
||||
- `id`: 记录ID
|
||||
- `enterprise_id`: 企业ID
|
||||
- `enterprise_name`: 企业名称
|
||||
- `card_id`: 卡ID
|
||||
- `iccid`: ICCID
|
||||
- `msisdn`: 手机号
|
||||
- `authorized_by`: 授权人ID
|
||||
- `authorizer_name`: 授权人名称
|
||||
- `authorizer_type`: 授权人类型
|
||||
- `authorized_at`: 授权时间
|
||||
- `revoked_by`: 回收人ID(可空)
|
||||
- `revoker_name`: 回收人名称(可空)
|
||||
- `revoked_at`: 回收时间(可空)
|
||||
- `status`: 状态(1=有效,0=已回收)
|
||||
- `remark`: 备注
|
||||
@@ -0,0 +1,34 @@
|
||||
# data-permission Specification Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: GORM Callback Data Permission
|
||||
|
||||
系统 SHALL 使用 GORM Callback 机制自动为所有查询添加数据权限过滤。
|
||||
|
||||
#### Scenario: 自动应用权限过滤
|
||||
- **WHEN** 执行 GORM 查询
|
||||
- **AND** Context 包含用户信息
|
||||
- **AND** 表包含 owner_id 字段
|
||||
- **THEN** 自动添加 WHERE owner_id IN (subordinateIDs) 条件
|
||||
|
||||
#### Scenario: Root 用户跳过过滤
|
||||
- **WHEN** 当前用户是 Root 用户
|
||||
- **THEN** 不添加任何数据权限过滤条件
|
||||
- **AND** 可查询所有数据
|
||||
|
||||
#### Scenario: 无 owner_id 字段的表
|
||||
- **WHEN** 表不包含 owner_id 字段
|
||||
- **THEN** 不添加数据权限过滤条件
|
||||
|
||||
#### Scenario: 授权记录表特殊处理
|
||||
- **WHEN** 查询 `tb_enterprise_card_authorization` 表
|
||||
- **AND** 当前用户是代理用户
|
||||
- **THEN** 自动添加 WHERE enterprise_id IN (SELECT id FROM tb_enterprise WHERE owner_shop_id = 当前店铺ID) 条件
|
||||
- **AND** 不包含下级店铺的数据
|
||||
|
||||
#### Scenario: 平台用户查询授权记录
|
||||
- **WHEN** 查询 `tb_enterprise_card_authorization` 表
|
||||
- **AND** 当前用户是平台用户或超级管理员
|
||||
- **THEN** 不添加数据权限过滤条件
|
||||
- **AND** 可查询所有授权记录
|
||||
@@ -0,0 +1,53 @@
|
||||
# 授权记录管理 - 实现任务
|
||||
|
||||
## 1. 数据权限修复
|
||||
|
||||
- [x] 1.1 修改 `pkg/gorm/callback.go`,为 `tb_enterprise_card_authorization` 表添加特殊处理逻辑
|
||||
- [x] 1.2 添加单元测试验证授权记录表的数据权限过滤(`pkg/gorm/callback_test.go`)
|
||||
|
||||
## 2. DTO 定义
|
||||
|
||||
- [x] 2.1 创建 `internal/model/dto/authorization_dto.go`,定义列表请求/响应结构
|
||||
- [x] 2.2 添加详情响应结构 `AuthorizationDetailResp`
|
||||
- [x] 2.3 添加更新备注请求结构 `UpdateAuthorizationRemarkReq`
|
||||
|
||||
## 3. Store 层实现
|
||||
|
||||
- [x] 3.1 在 `EnterpriseCardAuthorizationStore` 中添加 `ListWithJoin` 方法(关联查询企业名、卡信息、授权人名)
|
||||
- [x] 3.2 添加 `GetByIDWithJoin` 方法(详情查询)
|
||||
- [x] 3.3 添加 `UpdateRemark` 方法
|
||||
- [x] 3.4 在 `ListWithJoin` 和 `GetByIDWithJoin` 中手动添加数据权限过滤(原生 SQL 绕过 GORM callback)
|
||||
|
||||
## 4. Service 层实现
|
||||
|
||||
- [x] 4.1 在 `AuthorizationService` 中添加 `List` 方法(列表查询,支持筛选条件)
|
||||
- [x] 4.2 添加 `GetDetail` 方法(详情查询)
|
||||
- [x] 4.3 添加 `UpdateRemark` 方法(更新备注)
|
||||
|
||||
## 5. Handler 层实现
|
||||
|
||||
- [x] 5.1 创建 `internal/handler/admin/authorization.go`
|
||||
- [x] 5.2 实现 `List` handler(GET /authorizations)
|
||||
- [x] 5.3 实现 `GetDetail` handler(GET /authorizations/:id)
|
||||
- [x] 5.4 实现 `UpdateRemark` handler(PUT /authorizations/:id/remark)
|
||||
|
||||
## 6. 路由注册
|
||||
|
||||
- [x] 6.1 创建 `internal/routes/authorization.go`,注册路由组
|
||||
- [x] 6.2 在 `internal/routes/routes.go` 中调用路由注册
|
||||
- [x] 6.3 更新 `internal/bootstrap/handlers.go`,添加 AuthorizationHandler
|
||||
- [x] 6.4 更新 `internal/bootstrap/types.go`,添加 Handler 类型
|
||||
- [x] 6.5 更新 `cmd/api/docs.go` 和 `cmd/gendocs/main.go`,添加文档生成
|
||||
|
||||
## 7. 测试
|
||||
|
||||
- [x] 7.1 编写 Store 层单元测试(`tests/unit/enterprise_card_authorization_store_test.go`)
|
||||
- [x] 7.2 编写 Service 层单元测试(`tests/unit/enterprise_card_authorization_permission_test.go`)
|
||||
- [x] 7.3 编写集成测试(`tests/integration/authorization_test.go`)
|
||||
|
||||
## 8. 验证
|
||||
|
||||
- [x] 8.1 运行 `go build ./...` 确保编译通过
|
||||
- [x] 8.2 运行 `go test ./...` 确保测试通过
|
||||
- [x] 8.3 集成测试 API 端到端验证(列表、详情、更新备注、认证)
|
||||
- [x] 8.4 验证数据权限:代理用户只能看到自己店铺的数据(集成测试验证通过)
|
||||
Reference in New Issue
Block a user