Files
junhong_cmp_fiber/openspec/changes/fix-august-iteration-export-and-password-hints/design.md
邹进 1d6c212d04
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s
feat: 第1批 M5 补充 IoT 卡导出追加使用/剩余流量(GB)列
- 新任务在「套餐到期时间」后追加「使用流量(GB)」「剩余流量(GB)」,同行 MB ÷ 1024 两位小数
- 冻结表头按末列识别三版尾列,历史任务行宽不变
- OpenSpec 补充提案、设计、spec 与任务 3.2/5.6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:00:44 +09:00

5.8 KiB
Raw Blame History

Context

M1、M4~M6 原写在 fix-august-iteration-follow-up-gaps,改写该 Change 为第 1 批缺陷时移出;2026-10-11 确定并入第 1 批,单独成本 Change。现状已核对:

  • ChangePasswordRequest(internal/model/dto/auth_dto.go:50)校验为 required 与 min=8,max=32,Handler 校验失败只返回“参数验证失败”,字段与规则被丢弃。
  • 设置登录密码的入口规则不一且无强度要求:本人改密码、后台账号创建/修改/改密码、建店铺初始密码为 8~32;企业客户新增与改密码为 6~20;代理注册为 6~64(internal/domain/distribution/distribution.go:67)。错误码 1016(CodePasswordTooWeak)已定义但未使用。
  • customer_account_dto.go、shop_account_dto.go 中的建账号与改密码 DTO 没有任何调用方,不在本次范围。
  • 订单导出表头有“店铺”无“操作人”(internal/exporter/order_scene.go),订单列表已有冻结的 operator_name。
  • IoT 卡导出无到期列;卡列表最终到期由 packageexpiry.Query.ResolveBatch(internal/query/packageexpiry/query.go:41)推算。
  • 设备导出表头为 ICCIDn使用的流量、“设备合计”(internal/exporter/device_scene.go:378),数值实为 MB,未标单位。
  • 导出任务创建时冻结 ResolvedHeaders,Worker 按冻结表头生成行。

Goals / Non-Goals

Goals: 设置登录密码的入口统一长度与强度规则;修改密码失败消息直接说明字段与规则;三类导出补齐核对字段与单位;历史任务列不错位。

Non-Goals: 不强制存量弱密码修改,不改登录校验;不改超级管理员操作密码与 WiFi 密码;不改订单与支付写入、到期推算口径;不改前端;无迁移。

Decisions

  1. 修改密码复用 internal/handler/validation.Message。 为请求字段补中文 description,Handler 返回“修改密码参数不合法:”加字段级消息;错误码、HTTP 状态和校验顺序不变。与项目已有中文校验提示约定一致,不在 Handler 内硬编码 validator 分支。

  2. 订单“操作人”取冻结的 operator_name,追加在最后一列。 不回查当前账号名称;是否输出以任务 ResolvedHeaders 是否含“操作人”为准。

  3. 卡“套餐到期时间”按分页批量推算,追加在最后一列。 每页调用一次 ResolveBatch,禁止逐卡查询;只有 exact 输出 estimated_final_expires_at,其余留空。 3a. 卡导出 GB 列追加在“套餐到期时间”之后(M5 补充,2026-10-14 确认)。 新任务在最后追加“使用流量(GB)”“剩余流量(GB)”,值为同行 使用流量(MB)、剩余流量(MB) ÷ 1024 保留两位小数,与设备导出 formatMB 口径一致;原 MB 列名与数值不变。冻结表头按末列识别三个版本:末列“剩余流量(GB)”→ 输出到期列与两列 GB;末列“套餐到期时间”→ 只输出到期列;其余 → 两者都不输出(替换现有 iotCardHeadersIncludeFinalExpiry 的单一判断)。

  4. 设备流量数值不变,只改表头并加 GB 列。 新任务表头改为 ICCIDn使用的流量(MB)、“设备合计(MB)”,在最后一列(“关联手机号”之后)加“设备合计(GB)”,值为 MB ÷ 1024 保留两位小数;历史任务按冻结表头输出旧列名与旧列数。

  5. 密码规则集中在一个函数,在服务层哈希前调用。 pkg/validator 新增登录密码规则函数,依次判断:长度 8~12(1001)→ 只含 ASCII 可见字符且无空格(1016)→ 大写、小写、数字、特殊符号四类至少三类(1016)。各入口在 bcrypt.GenerateFromPassword 前调用;DTO 标签同步改为 min=8,max=12 供文档与早期拦截。不只靠 DTO 标签,因为项目存在多个独立 validator.New() 实例,且代理注册在领域层自行校验,集中函数能保证口径一致。

  6. 启动时的系统管理员不校验。 CreateSystemAccount(internal/bootstrap/admin.go:51)按配置创建,校验会让配置弱密码的环境无法启动;该账号上线后改密码走本人改密码入口,受新规则约束。

  7. 长度上限 8~12 位为业务确认值。 现有 8~32 位的密码超过 12 位的可继续登录,只在下次修改时受限。

  8. 新列一律追加在最后(2026-10-11 确认)。 与项目既有做法一致(IoT 卡导出注释:“「关联手机号」固定追加在尾部”)。原因:分片按位置把行截齐到冻结表头(internal/task/export_common.go 的 alignRowsToHeaders),新代码上线前派发、上线后执行的任务只会少最后一列,插在中间则后面各列整体错位。订单与设备导出的卡列是动态的,组数靠表头反推:订单为(总列数 − 固定列数)÷ 2(orderCardGroupCountFromHeaders),设备为按尾部固定列数(现为 6,旧为 5)反推(cardGroupCountFromHeaders)。追加末列后须先按末列表头是否为“操作人”/“设备合计(GB)”识别新旧表头再反推,否则卡列组数算错或为 0。

Risks / Trade-offs

  • [前端提示与后端规则不一致] → 任务中单列前端同步,测试环境联调确认。

  • [12 位上限压低可用强度] → 业务已确认;规则集中在一个函数,后续放宽只改一处。

  • [批量推算增加导出耗时] → 每页一次批量查询,无 N+1。

  • [新列导致历史任务列错位] → 行构造一律以冻结 ResolvedHeaders 判定是否输出新列。

  • [调用方按旧消息“参数验证失败”匹配] → 错误码 1001 不变,调用方应按错误码分支。

Migration Plan

无迁移。测试环境部署 API 与 Worker 后,新建导出任务使用新表头,已有任务沿用冻结表头。

Open Questions

无。