# 功能完成清单(第一迭代) > 目的:直观呈现哪些功能**已完成且有自动化测试**、哪些**部分完成**、哪些**尚未开始**,方便针对性验证与回归。 > 维护约定:每波工单合入后由执行人更新本清单;状态以 `dev` 分支 + 门禁全绿为准。 > 最后更新:2026-09-04(第三波两端合龙 + 工程化补波:patbond-api `6528a06` 75 测试、patbond-flutter `da25804` 30 测试,均门禁全绿;compose 编排冒烟实测通过) 图例:✅ 已完成且已测试 | 🟡 部分完成/有已知限制 | ⬜ 未开始 ## 1. 后端基础设施 | 功能 | 状态 | 自动化测试 | 说明 | | --- | --- | --- | --- | | Spring Boot 3 / JDK 17 基线(ADR-001) | ✅ | 全量门禁 | Boot 3.5.16 + Spring Cloud 2025.0.3 | | 移除 Nacos,Feign 静态地址(ADR-002) | ✅ | `AuthApplicationTests` | 干净检出可启动、可测试 | | Flyway V1 baseline(platform/identity/media) | ✅ | `UserPersistenceIntegrationTest.flywayBaselineAppliedOnCleanPostgres16` | 干净 postgres:18 全量执行;dev 种子默认不加载 | | Testcontainers postgres:18(ADR-006/008) | ✅ | 所有 user 模块集成测试 + auth E2E | 不依赖本机数据库 | | 统一响应信封 `{code,message,data}` + 稳定错误码 | ✅ | `ApiResponseTest`、各 Controller 测试 | 错误码表见 `docs/api/openapi.yaml` | | 跨服务错误码透传(不折叠) | ✅ | `ApiErrorDecoderTest` + **E2E 真实链路** | 第三波修复两处存量缺陷(ErrorDecoder 未进 Feign 子上下文、JDK HttpURLConnection 读不到 401 错误体),此前真实调用中折叠为 503 | ## 2. 用户与凭证(patbond-user) | 功能 | 状态 | 自动化测试 | 说明 | | --- | --- | --- | --- | | 用户注册落库(UUIDv7、bcrypt、软删不可见) | ✅ | `UserPersistenceIntegrationTest`、`UserControllerTest` | 原生 JDBC 读回验证持久性 | | 用户名唯一(citext 大小写不敏感)→ 40900 | ✅ | `UserControllerTest.duplicateUsernameCheckIsCaseInsensitive` 等 | 依赖 DB 约束 + 冲突翻译 | | 手机号唯一 → 40901;E.164 校验(DTO 与 DB CHECK 对齐) | ✅ | `UserControllerTest`、`databaseRejectsNonE164PhoneEvenIfValidationWereBypassed` | | | 密码校验(含防账号探测的哑 hash 比对) | ✅ | `UserControllerTest.verifyPassword*` | | | 登录失败限制(窗口计数→锁定→423/42300) | ✅ | `LoginLockoutIntegrationTest`(3 例)+ E2E | 按用户名维度,5 次/15 分钟锁 15 分钟,全部配置项;成功登录重置窗口 | | `GET /api/v1/me`(Bearer,RS256 公钥本地验签) | ✅ | `MeEndpointTest`(5 例:正常/缺失/过期/伪造/垃圾) | 响应恰好 `{userId, username, phone, createdAt}` | ## 3. 认证与会话(ADR-003) | 功能 | 状态 | 自动化测试 | 说明 | | --- | --- | --- | --- | | `POST /api/v1/auth/register`(冻结契约 6 字段响应) | ✅ | `AuthControllerTest` + `AuthE2eIntegrationTest.fullAuthVerticalFlow` | 时间字段 ISO 8601 带时区 | | `POST /api/v1/auth/login`(多设备并行会话) | ✅ | 同上 + `logoutOnOneDeviceKeepsOtherDevicesLoggedIn` | | | Access token:JWT RS256,15 分钟(配置项) | ✅ | `JwtSignerTest`(5 例) | 私钥仅 auth,公钥仅 user;密钥环境变量注入,仓库零密钥材料 | | Refresh 会话:SHA-256 摘要落 `auth_sessions` | ✅ | `SessionLifecycleIntegrationTest.createSessionStoresSha256DigestNotPlaintext` | 明文不落库(逐字节断言) | | `POST /api/v1/auth/refresh`:刷新即轮换 + 轮换链 | ✅ | `refreshRotatesTokenAndChainsSessions` + E2E | 旧行 revoked/rotated/replaced_by 三字段断言 | | 旧 refresh 重用 → 40102 + 撤销整个 token family | ✅ | `reuseOfRotatedTokenRevokesWholeFamily` + E2E | 并发轮换同样按重用处理 | | refresh 过期/未知 → 40102 | ✅ | `expiredRefreshTokenIsRejected`、`unknownRefreshTokenIsRejected` | | | `POST /api/v1/auth/logout`:仅撤当前会话,幂等 | ✅ | `SessionLifecycleIntegrationTest`(含跨账号撤销不掉用例)+ E2E | 需有效 access token(40101 兜底) | | 会话记录设备信息(X-Device-Id / UA / IP) | ✅ | `registerForwardsDeviceIdHeaderToTheSessionRecord` + 会话落库断言 | 前端每请求携带 X-Device-Id,为多设备会话列表备数据 | | access 过期/伪造 → 40101 | ✅ | `MeEndpointTest`、`JwtSignerTest`、E2E | | | `/internal/**` 服务间鉴权(X-Internal-Token) | ✅ | `InternalAuthFilterTest`(3 例)+ E2E | 无凭证/错误凭证 401;未配置 fail-closed | | access token 主动吊销(黑名单) | ⬜ | — | 退出后已签发 access 在剩余 ≤15 分钟内仍有效(jti/sid 已入库备用),见报告 16 §9.1 | | auth_sessions 过期行清理任务 | ✅ | `SessionCleanupIntegrationTest` | `patbond-api@6528a06`:@Scheduled 定时删除死亡超过保留期(默认 30d,即重用检测窗口)的行,间隔/保留期均配置项 | ## 4. API 契约与文档 | 功能 | 状态 | 说明 | | --- | --- | --- | | OpenAPI 3 正式契约(5 公开端点+错误码表+会话/锁定策略) | ✅ | `docs/api/openapi.yaml`;与冻结稿字段零偏差;新增 42300 已显著标注 | | 后端三波迭代报告 | ✅ | `docs/development/iterations/iteration-1/`(10、16 等) | | README 运行手册(密钥生成、环境变量表、新端点) | ✅ | `patbond-api/Readme.md` | ## 5. 客户端(patbond-flutter) | 功能 | 状态 | 说明 | | --- | --- | --- | | 珊瑚橙主题迁移 + 认证基础组件(ADR-005) | ✅ | 第二波已交付(dart format / analyze / test 全绿) | | dio API client + 信封解包 + 错误码映射(含 42300) | ✅ | 第三波并行交付(`patbond-flutter@8d890c0`+`da25804`,报告 17),基于 mock 验证 | | Splash/登录/注册页 + secure storage + 登录态恢复 + 真实退出 | ✅ | 同上,登录页四态/注册校验 widget 测试锁定 | | 401 单飞刷新拦截器 | ✅ | `TokenRefresher` 单元测试(刷新单飞、40102 清会话) | | 与真实后端联调(烟囱测试) | 🟡 | 2026-09-04 手动联调通过(compose 后端 + 本地 Flutter,注册/登录链路无报错);自动化 `integration_test` 留第四波 | **跨端核对发现(前端线跟进,后端已按 openapi.yaml 核对全部通过)**: 1. ~~`UserProfile.fromJson` 将 `phone` 按非空 String 强转~~——**已修复**(`patbond-flutter@845e92f`,phone 改为可空,30 测试全绿)。 2. 注册请求携带的 `Idempotency-Key` 后端暂未实现幂等语义(开发计划仅要求帖子/预约类写接口支持);「刷新重放沿用同键」的设计正确,待后端实现后自动受益。 3. 前端每请求携带的 `X-Device-Id` 后端已接入 `auth_sessions.device_id`(`patbond-api@8bdaf53`)。 ## 6. 工程化 | 功能 | 状态 | 说明 | | --- | --- | --- | | 后端集成测试门禁(本地) | ✅ | `JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test`,82 测试(含埋点 +7) | | 跨服务真实 HTTP E2E | ✅ | `AuthE2eIntegrationTest`(同 JVM 双服务 + 真实 postgres:18) | | docker compose 最小编排(postgres:18 + 两无状态服务容器) | ✅ | `patbond-api@ab0265c`:`./deploy/init-secrets.sh` → `mvnw -DskipTests package` → `docker compose up -d --build`;完整冒烟实测通过(register→me→refresh→旧 token 重用 40102→internal 401→logout);用法见 `patbond-api/Readme.md` | | 可执行镜像构建(repackage exec jar、非 root 运行) | ✅ | 同上;顺带修复无 starter-parent 时 package 产物不可执行 | | 信封严格化(`success` 派生字段不再上线) | ✅ | `patbond-api@8a79971`,信封恰为 `{code, message, data}` | | CI 载体(自动执行门禁) | ✅ | 三仓全覆盖:api(mvnw 82 测试,#6 全绿 3m18s)、flutter(format/analyze/test,SDK 走 flutter-io.cn + toolcache 缓存)、doc(mkdocs --strict);全部零 GitHub 依赖 | ## 针对性测试速查 ```bash # 全量门禁 JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test # 只跑会话生命周期 / 锁定 / me 鉴权(user 模块) JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -pl patbond-user -am test \ -Dtest='SessionLifecycleIntegrationTest,LoginLockoutIntegrationTest,MeEndpointTest' \ -Dsurefire.failIfNoSpecifiedTests=false # 只跑跨服务 E2E 纵切(auth 模块;-am 必带,避免 ~/.m2 旧 common 快照) JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -pl patbond-auth -am test \ -Dtest='AuthE2eIntegrationTest' -Dsurefire.failIfNoSpecifiedTests=false ``` 手动冒烟(两服务本地起好后,密钥与内部 token 配置见 `patbond-api/Readme.md`): ```bash # 注册 → 拿令牌对 curl -s -X POST http://127.0.0.1:8081/api/v1/auth/register \ -H 'Content-Type: application/json' \ -d '{"username":"demo_user","phone":"+8613800138000","password":"secret123"}' # me(换成上一步返回的 accessToken) curl -s http://127.0.0.1:8082/api/v1/me -H "Authorization: Bearer " # 刷新(旧 refreshToken 随即失效;再用旧值应得 40102) curl -s -X POST http://127.0.0.1:8081/api/v1/auth/refresh \ -H 'Content-Type: application/json' -d '{"refreshToken":""}' # 退出(撤销当前会话) curl -s -X POST http://127.0.0.1:8081/api/v1/auth/logout \ -H "Authorization: Bearer " \ -H 'Content-Type: application/json' -d '{"refreshToken":""}' # /internal 无凭证应 401 curl -s -i http://127.0.0.1:8082/internal/users/by-username/demo_user | head -1 ```