Files
junhong_cmp_fiber/openspec/changes/enterprise-card-authorization/design.md
huang fdcff33058
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m9s
feat: 实现企业卡授权和授权记录管理功能
主要功能:
- 添加企业卡授权/回收接口 (POST /enterprises/:id/allocate-cards, recall-cards)
- 添加授权记录管理接口 (GET/PUT /authorizations)
- 实现代理用户数据权限过滤(只能查看自己店铺下企业的授权记录)
- 添加 GORM callback 支持授权记录表的数据权限过滤

技术改进:
- 原生 SQL 查询手动添加数据权限过滤(ListWithJoin, GetByIDWithJoin)
- 移除卡授权预检接口(allocate-cards/preview),保留内部方法
- 完善单元测试和集成测试覆盖
2026-01-26 15:07:03 +08:00

166 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
当前系统的企业卡授权功能存在权限控制不当的问题。现有实现使用 `asset_allocation_record` 表记录授权关系,但该表设计用于资产分配而非授权管理。此外,授权逻辑未正确实现单卡授权限制,权限控制不够精细。
**现状问题**
- 授权记录存储位置不当(使用了资产分配表)
- 缺少对已绑定设备的卡的授权限制
- 企业可以看到不应该看到的商业敏感信息
- 权限控制逻辑分散,没有统一的授权管理
**技术约束**
- 必须保持向后兼容,不能影响现有的卡分配功能
- 需要遵循项目的 GORM 数据权限自动过滤机制
- 不使用外键约束,关联通过代码层维护
## Goals / Non-Goals
**Goals:**
- 实现真正的单卡授权,授权不转移所有权
- 建立专用的授权记录表 `enterprise_card_authorization`
- 实现细粒度的权限控制,保护商业敏感数据
- 支持授权的创建、查询、回收全生命周期管理
- 与现有的 GORM 数据权限过滤机制无缝集成
**Non-Goals:**
- 不改变现有的卡分配allocation功能
- 不支持设备级授权(已绑定设备的卡不能单独授权)
- 不支持授权转移(必须先回收再重新授权)
- 不实现授权审批流程(直接授权生效)
## Decisions
### 1. 新建专用授权表
**决策**:创建 `enterprise_card_authorization` 表专门管理授权关系
**理由**
- 授权和分配是两个不同的业务概念,应该分离存储
- 专用表可以更好地记录授权历史(包括回收记录)
- 避免污染现有的 `asset_allocation_record` 表结构
**备选方案**
- 复用 `asset_allocation_record` 表:会混淆授权和分配的概念,且表结构不完全匹配
-`iot_cards` 表添加授权字段:无法记录授权历史,且一张卡可能被多次授权/回收
### 2. 数据权限过滤集成
**决策**:通过修改现有的 IoT 卡查询逻辑,在 Store 层集成授权检查
**实现方式**
```go
// 企业用户查询时的过滤逻辑
if userType == UserTypeEnterprise {
db = db.Where("owner_type = ? AND owner_id = ?", "enterprise", enterpriseID).
Or(db.Where("id IN (?)",
db.Table("enterprise_card_authorization").
Select("card_id").
Where("enterprise_id = ? AND revoked_at IS NULL", enterpriseID)))
}
```
**理由**
- 利用现有的 GORM Callback 机制,自动应用权限过滤
- 保持查询接口不变,上层代码无需修改
- 统一的权限控制点,易于维护
### 3. 敏感信息过滤
**决策**:在 Handler 层对响应数据进行后处理,移除敏感字段
**实现方式**
- Service 层返回完整数据
- Handler 层检查用户类型,如果是企业用户则清空敏感字段
- 敏感字段:`cost_price``distribute_price``supplier`
**理由**
- 保持 Service 层的通用性,不同场景可能需要不同的字段过滤
- Handler 层更接近展示层,适合做展示相关的数据处理
- 便于未来扩展不同用户类型的字段过滤规则
### 4. 批量授权接口设计
**决策**:提供单一的批量授权接口,不单独提供预检接口
**接口结构**
```go
// 请求
POST /api/admin/enterprises/{enterpriseId}/authorize-cards
{
"card_ids": [1, 2, 3]
}
// 响应
{
"success": [
{"card_id": 1, "iccid": "8986..."}
],
"failed": [
{"card_id": 2, "iccid": "8986...", "reason": "卡已绑定设备"},
{"card_id": 3, "iccid": "8986...", "reason": "卡状态不是已分销"}
]
}
```
**理由**
- 减少网络往返,提高性能
- 简化前端实现,一次调用获得所有结果
- 支持部分成功的场景,提高容错性
## Risks / Trade-offs
### 性能风险
**风险**:企业用户查询卡列表时需要 JOIN 授权表,可能影响查询性能
**缓解措施**
-`enterprise_card_authorization` 表的 `enterprise_id``revoked_at` 字段建立联合索引
-`card_id` 字段建立索引支持反向查询
- 考虑未来使用 Redis 缓存授权关系
### 数据一致性
**风险**:授权记录和卡状态可能不一致(如卡被删除但授权记录还在)
**缓解措施**
- 使用软删除,保留历史数据
- 定期运行数据一致性检查任务
- 在查询时过滤已删除的卡
### 权限泄露风险
**风险**:敏感信息过滤不完整可能导致商业数据泄露
**缓解措施**
- 在 Handler 层统一处理,确保所有接口都经过过滤
- 添加单元测试验证敏感字段确实被过滤
- 考虑使用 DTO 模式,为不同用户类型定义不同的响应结构
## Migration Plan
### 部署步骤
1. **数据库迁移**
- 创建 `enterprise_card_authorization`
- 添加必要的索引
2. **代码部署**
- 部署新的授权管理代码
- 保持旧的分配接口正常工作
3. **数据迁移**(如需要)
- 如果有历史授权数据在 `asset_allocation_record` 表,编写迁移脚本
- 迁移完成后可以清理旧数据
### 回滚策略
- 代码支持功能开关,可通过配置禁用新的授权功能
- 数据库表独立,不影响现有功能,可保留表结构
- 如需完全回滚,删除新表并恢复旧代码
## Open Questions
1. **授权有效期**:是否需要支持授权有效期?目前设计是永久授权直到主动回收
2. **授权数量限制**:是否需要限制一个企业可以被授权的卡数量?
3. **通知机制**:授权/回收时是否需要通知企业?
4. **审计日志**:是否需要更详细的授权操作日志?