diff --git a/T-266.-.md b/T-266.-.md new file mode 100644 index 0000000..bff66e7 --- /dev/null +++ b/T-266.-.md @@ -0,0 +1,87 @@ + +> 同步来源:[`docs/tasks/T-266.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-266.md) · commit `afc651f75a3a` + +--- +id: T-266 +title: 将设备 Bearer token 固定有效期延长为30天 +phase: 2 +deps: + - T-204 + - T-206 +status: DONE +created: 2026-07-31 +context_ref: 200e263 +work_branch: null +write_paths: + - docs/tasks/T-266.md + - docs/current-state.md + - docs/02-requirements.md + - docs/03-tech-stack.md + - docs/04-architecture.md + - docs/api.md + - backend-api/internal/usecase/** + - backend-api/internal/transport/httpapi/** + - android-buyer/app/src/test/java/com/roubao/autopilot/procurement/ProcurementApiClientTest.kt +--- + +## 问题 / 背景 + +内部采购手机当前每次登录取得固定 1 小时 BUYER 设备 Bearer token。采购设备是预授权 +设备,token 由 Android Keystore-backed 安全存储保存;1 小时到期会增加频繁重新登录 +和无人值守任务中断成本。需求将有效期延长为 1 个月。 + +Go `time.Duration` 不表达自然月,本任务把“1个月”固定定义为 **30 天,即 720 小时、 +2,592,000 秒**,避免不同月份导致 API 与设备本地到期时间不确定。 + +## 行为契约 + +- 新签发 BUYER 设备 access token 的绝对有效期固定为 `30 * 24 * time.Hour`。 +- `POST /api/v1/auth/token` 成功响应的 `expires_in` 固定为 `2592000`。 +- 已签发 token 保留数据库中的原 `expires_at`,不批量延长;部署后设备需重新登录一次 + 才能取得30天 token。 +- token 不因 heartbeat、claim、start、任务执行或 API 请求滚动续期;仍不增加 refresh + token。 +- 用户禁用、设备禁用、token 撤销、设备绑定不匹配和角色错误仍在下一次请求立即失败, + 不等待30天到期。 +- 数据库仍只保存 token SHA-256;原 token 不写日志、不写 execution event。 + +## 不变边界 + +- ADMIN 服务端 session 仍为固定8小时。 +- 设备预共享 secret、任务 claim token、claim 租约、execution 授权和幂等 key 的 + 生命周期均不变。 +- 管理 Cookie 与 BUYER Bearer middleware 继续隔离。 +- 不增加可配置环境变量,避免生产节点出现不一致的鉴权期限。 + +## 方案 + +1. 将 `AccessTokenLifetime` 改为 `30 * 24 * time.Hour`。 +2. 更新 HTTP 契约测试,断言 `expires_in=2592000`;usecase 测试继续断言数据库到期 + 时间严格等于统一常量。 +3. 同步需求、技术栈、架构、API 示例和当前状态文档。 +4. 运行 Go 全量测试、Android 契约消费回归和根 `init.ps1`。 + +## 验收要点 + +- [x] 新登录产生的 `expires_at - created_at` 严格等于30天。 +- [x] 登录响应 `expires_in` 为 `2592000`。 +- [x] 过期、撤销、用户禁用和设备禁用测试继续通过。 +- [x] 已有 token 不被迁移或静默延期。 +- [x] Admin session、claim/execution 生命周期无改动。 +- [x] Go 全量测试和根 `init.ps1` 通过。 + +## 执行记录 + +- 2026-07-31:将 `AccessTokenLifetime` 从 `time.Hour` 改为 + `30 * 24 * time.Hour`;登录响应继续从该统一常量计算 `expires_in`,现在固定返回 + `2592000`。ADMIN session 常量仍为8小时。 +- 2026-07-31:更新 HTTP handler 测试精确断言 `2592000`;既有 usecase 测试继续 + 断言 `expires_at - created_at == AccessTokenLifetime`,SQLite 鉴权的过期、撤销、 + 用户禁用、设备禁用与绑定检查测试全部通过。 +- 2026-07-31:Android 登录 mock 改为30天并断言本地计算的 + `expiresAtEpochMillis` 落在登录时刻加30天的窗口内,证明跨端秒/毫秒换算无溢出。 +- 2026-07-31:同步需求、技术栈、架构、API 示例和当前状态。没有新增或修改数据库 + migration,因此已有 token 的 `expires_at` 不变;后端重启后设备必须重新登录一次 + 才取得30天 token。 +- 2026-07-31:Go 全量测试通过,Android 定向消费测试通过,根 `init.ps1` 通过 + Debug/Release 单测、Debug APK、共享契约、导入工具和全部 Go 后端测试。