Files
patbond-doc/docs/development/feature-checklist.md
T
lixi b26b2af089 docs: CI Runner 手册补充国内网络问题修法并同步清单状态
- 镜像加速、DEFAULT_ACTIONS_URL、runner 重注册清残留等实操要点
- 门禁:mkdocs build --strict 通过
2026-09-04 16:35:08 +08:00

123 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 功能完成清单(第一迭代)
> 目的:直观呈现哪些功能**已完成且有自动化测试**、哪些**部分完成**、哪些**尚未开始**,方便针对性验证与回归。
> 维护约定:每波工单合入后由执行人更新本清单;状态以 `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 |
| 移除 NacosFeign 静态地址(ADR-002 | ✅ | `AuthApplicationTests` | 干净检出可启动、可测试 |
| Flyway V1 baselineplatform/identity/media | ✅ | `UserPersistenceIntegrationTest.flywayBaselineAppliedOnCleanPostgres16` | 干净 postgres:18 全量执行;dev 种子默认不加载 |
| Testcontainers postgres:18ADR-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 约束 + 冲突翻译 |
| 手机号唯一 → 40901E.164 校验(DTO 与 DB CHECK 对齐) | ✅ | `UserControllerTest``databaseRejectsNonE164PhoneEvenIfValidationWereBypassed` | |
| 密码校验(含防账号探测的哑 hash 比对) | ✅ | `UserControllerTest.verifyPassword*` | |
| 登录失败限制(窗口计数→锁定→423/42300) | ✅ | `LoginLockoutIntegrationTest`(3 例)+ E2E | 按用户名维度,5 次/15 分钟锁 15 分钟,全部配置项;成功登录重置窗口 |
| `GET /api/v1/me`BearerRS256 公钥本地验签) | ✅ | `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 tokenJWT RS25615 分钟(配置项) | ✅ | `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 token40101 兜底) |
| 会话记录设备信息(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`75 测试 |
| 跨服务真实 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 载体(自动执行门禁) | 🟡 | `.gitea/workflows/ci.yml` 已提交(`patbond-api@3f6e818`,与本地门禁同命令);**待服务器启用 Gitea Actions 并注册带 Docker 的 act_runner**(前提在文件头注释,需用户操作) |
## 针对性测试速查
```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 <accessToken>"
# 刷新(旧 refreshToken 随即失效;再用旧值应得 40102)
curl -s -X POST http://127.0.0.1:8081/api/v1/auth/refresh \
-H 'Content-Type: application/json' -d '{"refreshToken":"<refreshToken>"}'
# 退出(撤销当前会话)
curl -s -X POST http://127.0.0.1:8081/api/v1/auth/logout \
-H "Authorization: Bearer <accessToken>" \
-H 'Content-Type: application/json' -d '{"refreshToken":"<refreshToken>"}'
# /internal 无凭证应 401
curl -s -i http://127.0.0.1:8082/internal/users/by-username/demo_user | head -1
```