- 新任务在「套餐到期时间」后追加「使用流量(GB)」「剩余流量(GB)」,同行 MB ÷ 1024 两位小数 - 冻结表头按末列识别三版尾列,历史任务行宽不变 - OpenSpec 补充提案、设计、spec 与任务 3.2/5.6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.8 KiB
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
-
修改密码复用
internal/handler/validation.Message。 为请求字段补中文description,Handler 返回“修改密码参数不合法:”加字段级消息;错误码、HTTP 状态和校验顺序不变。与项目已有中文校验提示约定一致,不在 Handler 内硬编码 validator 分支。 -
订单“操作人”取冻结的
operator_name,追加在最后一列。 不回查当前账号名称;是否输出以任务ResolvedHeaders是否含“操作人”为准。 -
卡“套餐到期时间”按分页批量推算,追加在最后一列。 每页调用一次
ResolveBatch,禁止逐卡查询;只有exact输出estimated_final_expires_at,其余留空。 3a. 卡导出 GB 列追加在“套餐到期时间”之后(M5 补充,2026-10-14 确认)。 新任务在最后追加“使用流量(GB)”“剩余流量(GB)”,值为同行使用流量(MB)、剩余流量(MB)÷ 1024 保留两位小数,与设备导出formatMB口径一致;原 MB 列名与数值不变。冻结表头按末列识别三个版本:末列“剩余流量(GB)”→ 输出到期列与两列 GB;末列“套餐到期时间”→ 只输出到期列;其余 → 两者都不输出(替换现有iotCardHeadersIncludeFinalExpiry的单一判断)。 -
设备流量数值不变,只改表头并加 GB 列。 新任务表头改为
ICCIDn使用的流量(MB)、“设备合计(MB)”,在最后一列(“关联手机号”之后)加“设备合计(GB)”,值为 MB ÷ 1024 保留两位小数;历史任务按冻结表头输出旧列名与旧列数。 -
密码规则集中在一个函数,在服务层哈希前调用。
pkg/validator新增登录密码规则函数,依次判断:长度 8~12(1001)→ 只含 ASCII 可见字符且无空格(1016)→ 大写、小写、数字、特殊符号四类至少三类(1016)。各入口在bcrypt.GenerateFromPassword前调用;DTO 标签同步改为min=8,max=12供文档与早期拦截。不只靠 DTO 标签,因为项目存在多个独立validator.New()实例,且代理注册在领域层自行校验,集中函数能保证口径一致。 -
启动时的系统管理员不校验。
CreateSystemAccount(internal/bootstrap/admin.go:51)按配置创建,校验会让配置弱密码的环境无法启动;该账号上线后改密码走本人改密码入口,受新规则约束。 -
长度上限 8~12 位为业务确认值。 现有 8~32 位的密码超过 12 位的可继续登录,只在下次修改时受限。
-
新列一律追加在最后(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
无。