docs: 迁入第一迭代过程报告并建立进展看板

- 新增 development/iterations/iteration-1/:15 份角色报告 + 进展看板(已完成/未闭环/下一步),作为双人协作的进度事实来源
- 新增 ADR-006:测试与交付容器化策略(Testcontainers / 交付 Docker 包 / 本机库仅个人联调)
- Git 工作流规范补充:敏感信息只进忽略文件或 sample、测试数据不入库、测试代码限标准测试目录
- 门禁:mkdocs build --strict 通过(零警告)
This commit is contained in:
2026-09-04 10:45:05 +08:00
parent 027876ae00
commit 209021e7d2
19 changed files with 3077 additions and 1 deletions
@@ -0,0 +1,206 @@
# 06 证据驱动质量审计(开工前基线)
- 审计人:EvidenceQAEvidence Collector
- 日期:2026-09-03
- 方式:只读审计(ls / grep / 读文件 / git 只读命令)。未运行构建、未启动服务,所有涉及运行时行为的结论均标注「静态分析,需运行验证」。
- 验收依据:`/home/lx/workspace/patbond/patbond-doc/docs/development/development-plan.md` 第 9 节(测试与质量门禁)、第 10 节(Definition of Done)、第 8 节(第一迭代任务)、M1 验收标准(第 212-223 行)。
---
## 1. 测试资产审计
### 1.1 patbond-api:零测试
- 全仓库文件清单中**不存在任何 `src/test` 目录**`find patbond-api -type f` 结果仅含 `src/main`,见仓库文件列表)。
- 三个模块的 pom 中**均无 `spring-boot-starter-test` 依赖**`grep -rn "spring-boot-starter-test" patbond-api --include="pom.xml"` 无任何匹配)——即写测试的前置依赖都还没有。
- 与文档第 9 节要求的差距(`development-plan.md:294-300`):单元测试、集成测试(Testcontainers)、契约测试、安全测试全部缺失,缺口为 100%。
- 后果:CI 最低门禁 `mvn clean test``development-plan.md:313-314`)在当前仓库上是**空转通过**——0 个测试也会绿。这正是文档第 11 节风险 6(`development-plan.md:354`)自认的状态,审计确认属实。
### 1.2 patbond-flutter:仅 1 个冒烟测试
- `test/` 目录只有 1 个文件:`/home/lx/workspace/patbond/patbond-flutter/test/widget_test.dart`,共 21 行。
- 内容:单个 `testWidgets`,断言主导航 5 个 Tab 文案,以及两条**写死的演示数据字符串**:
- `test/widget_test.dart:18``expect(find.text('北京 · 朝阳区'), findsOneWidget)`
- `test/widget_test.dart:19``expect(find.text('28°C 晴'), findsOneWidget)`
- 这两个值来自 `lib/data/demo_data.dart:11-19``locationWeatherOptions` 第一项:city '北京'、district '朝阳区'、temperature 28、conditionText '晴')。演示数据一改,唯一的测试即失败。
- 与文档第 9 节 Flutter 要求的差距(`development-plan.md:302-308`):
- 单元测试(DTO 映射、Repository、缓存、状态转换):0 个。`lib/state/app_state.dart` 有可测的加载/持久化/回退逻辑(约 120 行),无对应测试。
- Widget 测试(登录、宠物编辑、动态互动等):0 个(登录页本身不存在)。
- Golden/响应式测试:0 个。
- Integration/E2E0 个(`integration_test/` 目录不存在)。
- loading/empty/error/retry/离线状态覆盖:0 个。
- 工具链本身可用:`analysis_options.yaml` 引入 `package:flutter_lints/flutter.yaml``analysis_options.yaml:10`),`pubspec.yaml:39-48``flutter_test``flutter_lints ^6.0.0`
### 1.3 门禁落地情况
- **两个代码仓库均无任何 CI 配置**:`ls -a` 未见 `.github``.gitlab-ci.yml`、Jenkinsfile 等(patbond-api 根目录仅 `.git``.gitignore``.idea`、三个模块与 `pom.xml``Readme.md`)。
- **无 Maven Wrapper**`mvnw` 不存在),与 M0 目标(`development-plan.md:118`、204 行前后)尚有差距——这是计划内待办,但意味着「CI 最低门禁」目前只存在于文档里,没有任何机制执行它。
---
## 2. 代码质量风险审计(逐条附证据)
### 2.1 认证与鉴权(后端)
- **token 不可验证**`patbond-api/patbond-auth/src/main/java/com/patbond/patbond/auth/service/AuthService.java:47-56` —— `buildToken` 返回 `UUID.randomUUID().toString().replace("-", "")``expiresAt` 只是响应里的展示字段。无签名、无存储、无校验途径、无 refresh token。与 M1 验收标准(`development-plan.md:223`)和风险 2`development-plan.md:350`)一致,审计确认。
- **`/internal/**` 完全无访问控制**`patbond-api/patbond-user/src/main/java/com/patbond/patbond/user/controller/UserController.java:18` 映射 `/internal/users`,全仓库 grep 无任何 Spring Security、拦截器或过滤器。`GET /internal/users/{id}`UserController.java:37-40)返回的 `UserProfile``phone` 字段(`UserService.java:69-71``toProfile` 传入 `user.getPhone()`)——任何能访问 8082 端口的人可枚举用户资料含手机号。对应风险 3(`development-plan.md:351`)。
- **用户仅存内存**`patbond-api/patbond-user/src/main/java/com/patbond/patbond/user/service/UserService.java:21-23` —— `AtomicLong idGenerator` + 两个 `ConcurrentHashMap`。重启即丢全部用户,直接不满足 M1 验收「注册后重启服务数据不丢失」(`development-plan.md:223`)。
- **错误状态码折叠**(静态分析,需运行验证):`AuthService.java:58-63``requireData` 把所有失败折叠为 `HttpStatus.BAD_REQUEST`。而 user 服务用 `ResponseStatusException` 返回 409/401/404`UserService.java:29,48,56,64`),Feign 客户端(`patbond-auth/.../client/UserClient.java`)收到非 2xx 时会抛 `FeignException` 而非进入 `requireData` 的业务分支——登录密码错误的调用链大概率对客户端表现为 500 或语义丢失的 400,违反契约规范「错误必须同时返回正确 HTTP 状态码和稳定业务错误码」(`development-plan.md:171`)。
### 2.2 硬编码密钥 / 密码 / 敏感信息
- **后端源码中未发现硬编码密钥、密码或密文**。`grep -rniE "password|secret|api[_-]?key|token"``patbond-api` 三个模块 `src` 下的命中全部是 DTO 字段赋值(如 `LoginRequest.java:26``CreateUserRequest.java:27,45`),无真实凭据。诚实结论:这一项没有问题,不编造。
- **配置卫生良好**:仓库只提交 `.yml.sample``patbond-auth/src/main/resources/application.yml.sample``patbond-user/.../application.yml.sample`),真实 `application.yml``.gitignore` 忽略(`patbond-api/.gitignore` 末段:`patbond-*/src/main/resources/application.yml` + `!...yml.sample`)。sample 中仅有 `${NACOS_SERVER_ADDR:127.0.0.1:8848}` 之类的本地默认值,无敏感值。
- **开发 fixture 凭据集中在 SQL**`patbond-doc/docs/database/patbond_postgresql.sql`
- 第 1269 行:注释明文声明 `accounts use the password: Patbond@123`
- 第 1289-1293 行:三个 fixture 账号共用同一 bcrypt 哈希 `$2y$12$UYCn5Zm...`
- 第 1283-1286 行:fixture 手机号 `+8613800000001` 等(明显假号段,风险低);
- 约第 1296-1308 行:`identity.auth_sessions` 插入固定 `refresh_token_hash = decode(repeat('ab', 32), 'hex')``access_token_jti = 'fixture-access-token-jti'`
- 文件自身已注明「Remove this section from the production Flyway baseline」(第 1266-1269 行),且这是开发种子数据而非泄露的生产密钥。风险在于:bootstrap 是**单文件**,结构与种子数据未拆分(第 4.3 节第 105 行的拆分要求未完成),误导入生产即产生三个已知密码账号和一个可预测 refresh token。
### 2.3 TODO / FIXME / 注释掉的代码
- `grep -rn "TODO|FIXME|HACK|XXX"``patbond-api``patbond-flutter/lib``patbond-flutter/test` 的 java/dart/yml 文件中:**0 命中**。
- 注释掉的代码块:Java 侧 `grep -rnE "^\s*//.*(;|\{)\s*$"` 0 命中;Dart 侧同类模式 0 命中(仅 `pubspec.yaml``analysis_options.yaml` 中的脚手架模板注释,属正常)。
- 诚实结论:这两类问题当前不存在,不虚报。
### 2.4 硬编码 URL 与展示型字段(Flutter
- 22 处硬编码 Unsplash 图片 URL,集中在:`lib/data/demo_data.dart`4、6、8、128、136、146、151、159、178、191、203、216、229、245、252、259 行)、`lib/features/home/home_page.dart:551,556``lib/features/pets/pets_page.dart:357-358`。属演示数据(文档第 33 行认可其非契约地位),但 DoD 要求「不依赖 Demo 常量」(`development-plan.md:340`),验收时必须逐页替换。
- 展示字符串被当数据持久化:`lib/data/demo_data.dart:117``time: '2小时前'`)、130、138、147、161 行;`distance: '1.2km'` 等在 172、185、197、210、223 行。违反第 4.3 节「相对时间、距离由事实字段计算,不持久化展示字符串」(`development-plan.md:111`),对应风险 4`development-plan.md:352`)。
- 无网络层、无登录页、无安全存储:`lib/` 下无 `http`/`dio` 依赖(`pubspec.yaml:30-37``cupertino_icons` + `shared_preferences`),`AppState` 直接持久化宠物/疫苗/帖子/天气到 `SharedPreferences``lib/state/app_state.dart:9-12,97-118`)。第一迭代需按 4.2 节新建全部网络与鉴权层;届时 token 必须走安全存储而非 `SharedPreferences``development-plan.md:97`)。
---
## 3. 文档一致性审计
| 检查项 | 结论 | 证据 |
| --- | --- | --- |
| API Readme 端点 vs 代码 | 一致 | `Readme.md:34-44` 的 6 个端点与 `AuthController.java:24-31``UserController.java:27-45` 逐一对应 |
| API Readme 端口 vs 配置 | 一致 | `Readme.md:33,40`8081/8082= 两个 `application.yml.sample:2` |
| API Readme 的 NACOS_SERVER_ADDR 说明 | 一致 | `Readme.md:48-53` vs sample 第 12 行 `${NACOS_SERVER_ADDR:127.0.0.1:8848}` |
| **API Readme 启动步骤完整性** | **不一致** | `Readme.md:22-26` 只有 `mvn compile`**未提及**必须先复制 `application.yml.sample``application.yml`(该步骤只在 `development-plan.md:127-138`),也无 `spring-boot:run` 命令。干净检出仅按 README 无法把服务跑起来——文档风险 5(`development-plan.md:353`)确认属实 |
| Readme 技术栈 RabbitMQ vs 计划 | 内部一致、与计划冲突 | `Readme.md:19` 列 RabbitMQ`patbond-common/pom.xml:120-123` 确实引入 `spring-boot-starter-amqp`;但计划 4.1 节明确 common「不应让所有服务被动引入 RabbitMQ、Feign 等依赖」(`development-plan.md:79` |
| Flutter README 平台声明 | 一致 | `README.md:5` 声明的 Android/iOS/Web/桌面对应仓库 `android/ ios/ web/ linux/ macos/ windows/` 目录均存在 |
| Flutter README shared_preferences 声明 | 一致 | `README.md:13` vs `lib/state/app_state.dart:9-12` 四个持久化 key |
| **Flutter README 校验命令 vs 计划 CI 门禁** | **不一致** | `README.md:34``dart format --set-exit-if-changed lib test`(缺 `--output=none`,实际会**改写文件**);计划门禁是 `dart format --output=none --set-exit-if-changed lib test``development-plan.md:317` |
| 计划 5.2 节命令中的文件路径 | 一致 | `development-plan.md:130-133` 引用的两个 `.yml.sample` 均存在于对应路径 |
| mkdocs.yml 导航 | 一致 | `patbond-doc/mkdocs.yml` nav 引用的 `index.md``development/development-plan.md` 均存在;`docs/index.md:14` 链接的 `database/patbond_postgresql.sql` 存在 |
| 其他观察 | — | `patbond-flutter` 工作区有未提交改动(`git status`` M README.md`);`patbond-api/.idea/` 在磁盘上但未被 git 跟踪(`git ls-files` 无 .idea 条目),无泄露 |
`mkdocs build --strict` 门禁未实际执行(审计约束禁止构建),仅完成静态引用核对。
---
## 4. 问题清单(按严重程度排序)
### Blocker
**B1 后端自动化测试为零,CI 门禁空转**
证据:`patbond-api` 无任何 `src/test` 目录;三个 pom 均无 `spring-boot-starter-test`
影响:第 9 节全部后端测试类别缺口 100%;`mvn clean test` 0 测试也通过,门禁无意义。第一迭代任务 5「注册、登录、刷新、退出和鉴权集成测试」(`development-plan.md:285`)从零开始。
**B2 认证纵切三件套全部缺位:内存用户 + 不可验证 token + `/internal` 裸奔**
证据:`UserService.java:21-23`ConcurrentHashMap 存储)、`AuthService.java:47-56`(随机 UUID 当 token)、`UserController.java:18``/internal/users` 无鉴权且经 `UserService.java:69-71` 返回手机号)。
影响:M1 全部四条验收标准(`development-plan.md:223`)当前均不满足;未鉴权的 `/internal` 是当前唯一对外可见的真实安全暴露面。
### Major
**M1 错误状态码在 auth→user 调用链上丢失(静态分析,需运行验证)**
证据:`AuthService.java:58-63` 将一切失败折叠为 400;user 侧用 `ResponseStatusException` 抛 409/401/404`UserService.java:29,48,56,64`),Feign 非 2xx 会抛异常绕过该分支。
影响:违反契约规范 `development-plan.md:171`;客户端无法区分「用户名已存在」「密码错误」。第一迭代任务 3 的统一异常响应必须覆盖此链路,验收时需用真实 curl 记录证明。
**M2 patbond-common 强制全体服务引入 AMQP/Feign/LoadBalancer/Nacos**
证据:`patbond-common/pom.xml:120-139``spring-boot-starter-amqp``spring-cloud-starter-openfeign``spring-cloud-starter-loadbalancer`、两个 nacos starter 全部为 compile 依赖)。
影响:与架构原则 `development-plan.md:79` 直接冲突;后续每个新业务模块都会被动携带消息队列与服务发现依赖。
**M3 两个代码仓库均无 CI 配置和 Maven Wrapper**
证据:`ls -a``.github`/`.gitlab-ci.yml`/`mvnw`
影响:第 9 节 CI 最低门禁(`development-plan.md:310-323`)没有执行载体,DoD 中「代码通过 CI」(`development-plan.md:345`)无法核查。
**M4 API Readme 启动步骤不完整,干净检出无法照做启动**
证据:`patbond-api/Readme.md:22-26``mvn compile`;配置复制步骤只存在于 `development-plan.md:127-138``.gitignore` 忽略 `application.yml` 且仓库只有 `.sample`
影响:违反 M0 验收「新机器仅依据仓库文档即可启动」(`development-plan.md:210`)。
**M5 数据库 bootstrap 单文件混装结构与开发凭据**
证据:`patbond_postgresql.sql:1266-1269`(明文声明 fixture 密码 `Patbond@123`)、1289-1293(三账号同 bcrypt 哈希)、约 1296-1308(固定 `refresh_token_hash` 与 JTI 的预置会话)。
影响:结构/种子未拆分(`development-plan.md:105` 要求拆开),一旦整文件被当生产 baseline 导入,即产生已知密码账号与可预测会话。风险 7(`development-plan.md:355`)确认属实。
### Minor
**m1 Flutter 唯一测试断言写死演示数据**
证据:`test/widget_test.dart:18-19` 断言 `'北京 · 朝阳区'``'28°C 晴'`,值来自 `lib/data/demo_data.dart:11-19`
影响:演示数据或默认城市一改,唯一的测试即挂;该测试对回归防护价值趋近于零。
**m2 Flutter README 校验命令与 CI 门禁不一致**
证据:`patbond-flutter/README.md:34``--output=none`,会改写文件;门禁版本在 `development-plan.md:317`
影响:开发者本机「校验」实际是格式化,CI(若建立)与本机行为不一致。
**m3 展示字符串与硬编码图片 URL 持久化在演示数据中**
证据:`lib/data/demo_data.dart:117,130,138,147,161`'2小时前' 等)、172,185,197,210,223'1.2km' 等)、22 处 Unsplash URL(见 2.4 节行号清单)。
影响:与 `development-plan.md:111` 及 DoD「不依赖 Demo 常量」冲突;属已知风险 4,逐页替换时必须清除,验收时应 grep 证明。
**m4 patbond-flutter 工作区有未提交改动**
证据:`git status --short`` M README.md`
影响:基线不干净,审计快照与远端不一致;开工前应提交或还原。
---
## 5. 第一迭代「登录纵切」验收证据清单
依据:第 8 节任务 1-8、M1 验收标准(`development-plan.md:223`)、第 9 节门禁、第 10 节 DoD。**没有下列证据即不通过验收,任何「已完成」的口头声明不作数。**
### 5.1 自动化测试输出(原始终端输出,不接受转述)
1. `mvn clean test` 完整输出:显示测试总数 > 0,且包含注册/登录/刷新/退出/鉴权的集成测试类名与用例数;Testcontainers 启动 PostgreSQL 16 的日志行可见。
2. 每个接口至少覆盖:成功、参数错误、资源不存在、无权限、并发冲突、幂等重试(`development-plan.md:300`)——以测试报告中的用例名逐条对应。
3. `dart format --output=none --set-exit-if-changed lib test``flutter analyze``flutter test` 三条命令的退出码为 0 的完整输出;`flutter test` 中包含登录页 widget 测试(loading/error/成功三态)。
4. `mkdocs build --strict` 成功输出(文档更新后)。
5. CI 运行链接或日志:以上门禁在 CI 中执行并全绿(M3 问题修复的证明)。
### 5.2 接口调用记录(curl/httpie 全文:请求 + 响应头 + 响应体)
按顺序一份完整 transcript
1. `POST /api/v1/auth/register` → 201/200,响应体含 UUID 格式 userId 与 `{code, message, data}` 包裹。
2. 重复用户名注册 → HTTP 409 + 稳定业务错误码(验证 M1 问题修复:不再折叠为 400/500)。
3. 错误密码登录 → HTTP 401 + 业务错误码。
4. `POST /api/v1/auth/login` 成功 → 含 access + refresh token。
5. `GET /api/v1/me` 带 token → 200;不带/伪造 token → 401(证明 token 可验证)。
6. `POST /api/v1/auth/refresh` → 新 token 对;随后用**旧 refresh token 重放** → 401(轮换生效)。
7. `POST /api/v1/auth/logout` → 成功;再用已撤销 refresh token → 401M1 验收「退出后 refresh token 不可再次使用」)。
8. 未携带服务间凭据直接调用 `/internal/users/{id}` → 被拒绝(401/403),对照当前裸奔状态(B2)。
### 5.3 数据库查询结果(psql 原始输出)
1. 注册后:`SELECT id, username, status FROM identity.users WHERE username='...'` 显示 UUID 主键行。
2. `SELECT hash_algorithm, left(password_hash, 7) FROM identity.user_credentials ...` 显示 bcrypt/argon2id 前缀——同时证明**非明文**。
3. 重启持久化证据:注册 → 服务重启(附带重启时间戳的服务日志)→ 登录成功 + 上述查询仍有该行(M1 验收「重启数据不丢失」,直接针对 B2 内存存储)。
4. refresh 轮换后:`SELECT ... FROM identity.auth_sessions` 显示旧会话 revoked/新会话 active。
5. `SELECT version, description, success FROM flyway_schema_history` 显示 identity/media baseline 迁移(任务 1),且在全新 PostgreSQL 16 实例执行过一次(`development-plan.md:325`)。
6. 生产 baseline 不含 fixture:对迁移产物 `grep -c "Patbond@123"` 为 0(针对 M5)。
### 5.4 界面截图(真机或模拟器,标注设备与时间)
1. 登录页:初始态、提交中 loading 态、错误态(错误密码后的可读提示)、成功跳转后首页。
2. 注册页同三态。
3. 登录态恢复:登录 → 完全杀掉 App → 重新打开直接进入已登录态(M1 验收),两张前后截图 + 中间的杀进程操作说明。
4. 退出登录后回到未登录态的截图。
5. 安全存储证据:代码评审指向 token 写入 secure storage 的调用点(文件+行号),并 `grep -rn "SharedPreferences" lib` 输出证明 token 未落入 `SharedPreferences``development-plan.md:97`)。
### 5.5 附加核查项(DoD
1. 日志片段 + `grep -inE "password|token" <日志文件>` 输出:证明日志不含密码与 token 全文(`development-plan.md:175,344`)。
2. OpenAPI 文件路径 + 契约测试输出(任务 6)。
3. 端到端用例(注册 → 登录 → 获取当前用户 → 退出,任务 8)的单次完整执行记录,与 5.2 的 transcript 可为同一份。
### 验收纪律
- 每条证据必须可复现:附命令、路径、时间。截图必须来自本次交付的构建,不接受历史截图。
- 声明「零问题」「production ready」而不附上述证据的交付,直接按 FAILED 处理并退回。
- 本报告第 4 节的 B1、B2 未关闭前,第一迭代不具备进入验收的资格。
---
## 附:本次审计执行的命令类别
`find`(文件清单)、`ls -a`CI/wrapper 探测)、`grep -rn`TODO/密钥/URL/展示字符串/测试依赖)、`cat`/`sed`(读源码与 SQL)、`wc -l`(体量)、`git status --short` / `git ls-files` / `git log --oneline`(只读仓库状态)。未修改任何被审计仓库的文件。