Files
huang ce0783f96e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m30s
feat: 实现设备管理和设备导入功能,修复测试问题
主要变更:
- 实现设备管理模块(创建、查询、列表、更新状态、删除)
- 实现设备批量导入功能(CSV 解析、ICCID 绑定、异步任务处理)
- 添加设备-SIM 卡绑定约束(部分唯一索引防止并发问题)
- 修复 fee_rate 数据库字段类型(numeric -> bigint)
- 修复测试数据隔离问题(基于增量断言)
- 修复集成测试中间件顺序问题
- 清理无用测试文件(PersonalCustomer、Email 相关)
- 归档 enterprise-card-authorization 变更
2026-01-26 18:05:12 +08:00

5.5 KiB
Raw Permalink Blame History

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 层集成授权检查

实现方式

// 企业用户查询时的过滤逻辑
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_pricedistribute_pricesupplier

理由

  • 保持 Service 层的通用性,不同场景可能需要不同的字段过滤
  • Handler 层更接近展示层,适合做展示相关的数据处理
  • 便于未来扩展不同用户类型的字段过滤规则

4. 批量授权接口设计

决策:提供单一的批量授权接口,不单独提供预检接口

接口结构

// 请求
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_idrevoked_at 字段建立联合索引
  • card_id 字段建立索引支持反向查询
  • 考虑未来使用 Redis 缓存授权关系

数据一致性

风险:授权记录和卡状态可能不一致(如卡被删除但授权记录还在)

缓解措施

  • 使用软删除,保留历史数据
  • 定期运行数据一致性检查任务
  • 在查询时过滤已删除的卡

权限泄露风险

风险:敏感信息过滤不完整可能导致商业数据泄露

缓解措施

  • 在 Handler 层统一处理,确保所有接口都经过过滤
  • 添加单元测试验证敏感字段确实被过滤
  • 考虑使用 DTO 模式,为不同用户类型定义不同的响应结构

Migration Plan

部署步骤

  1. 数据库迁移

    • 创建 enterprise_card_authorization
    • 添加必要的索引
  2. 代码部署

    • 部署新的授权管理代码
    • 保持旧的分配接口正常工作
  3. 数据迁移(如需要)

    • 如果有历史授权数据在 asset_allocation_record 表,编写迁移脚本
    • 迁移完成后可以清理旧数据

回滚策略

  • 代码支持功能开关,可通过配置禁用新的授权功能
  • 数据库表独立,不影响现有功能,可保留表结构
  • 如需完全回滚,删除新表并恢复旧代码

Open Questions

  1. 授权有效期:是否需要支持授权有效期?目前设计是永久授权直到主动回收
  2. 授权数量限制:是否需要限制一个企业可以被授权的卡数量?
  3. 通知机制:授权/回收时是否需要通知企业?
  4. 审计日志:是否需要更详细的授权操作日志?