Files
lixi 209021e7d2 docs: 迁入第一迭代过程报告并建立进展看板
- 新增 development/iterations/iteration-1/:15 份角色报告 + 进展看板(已完成/未闭环/下一步),作为双人协作的进度事实来源
- 新增 ADR-006:测试与交付容器化策略(Testcontainers / 交付 Docker 包 / 本机库仅个人联调)
- Git 工作流规范补充:敏感信息只进忽略文件或 sample、测试数据不入库、测试代码限标准测试目录
- 门禁:mkdocs build --strict 通过(零警告)
2026-09-04 10:45:05 +08:00

207 lines
20 KiB
Markdown
Raw Permalink 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.
# 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`(只读仓库状态)。未修改任何被审计仓库的文件。