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

20 KiB
Raw Permalink Blame History

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 testdevelopment-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:18expect(find.text('北京 · 朝阳区'), findsOneWidget)
    • test/widget_test.dart:19expect(find.text('28°C 晴'), findsOneWidget)
    • 这两个值来自 lib/data/demo_data.dart:11-19locationWeatherOptions 第一项: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.yamlanalysis_options.yaml:10),pubspec.yaml:39-48flutter_testflutter_lints ^6.0.0

1.3 门禁落地情况

  • 两个代码仓库均无任何 CI 配置ls -a 未见 .github.gitlab-ci.yml、Jenkinsfile 等(patbond-api 根目录仅 .git.gitignore.idea、三个模块与 pom.xmlReadme.md)。
  • 无 Maven Wrappermvnw 不存在),与 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)和风险 2development-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)返回的 UserProfilephone 字段(UserService.java:69-71toProfile 传入 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-63requireData 把所有失败折叠为 HttpStatus.BAD_REQUEST。而 user 服务用 ResponseStatusException 返回 409/401/404UserService.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:26CreateUserRequest.java:27,45),无真实凭据。诚实结论:这一项没有问题,不编造。
  • 配置卫生良好:仓库只提交 .yml.samplepatbond-auth/src/main/resources/application.yml.samplepatbond-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 凭据集中在 SQLpatbond-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-apipatbond-flutter/libpatbond-flutter/test 的 java/dart/yml 文件中:0 命中
  • 注释掉的代码块:Java 侧 grep -rnE "^\s*//.*(;|\{)\s*$" 0 命中;Dart 侧同类模式 0 命中(仅 pubspec.yamlanalysis_options.yaml 中的脚手架模板注释,属正常)。
  • 诚实结论:这两类问题当前不存在,不虚报。

2.4 硬编码 URL 与展示型字段(Flutter

  • 22 处硬编码 Unsplash 图片 URL,集中在:lib/data/demo_data.dart4、6、8、128、136、146、151、159、178、191、203、216、229、245、252、259 行)、lib/features/home/home_page.dart:551,556lib/features/pets/pets_page.dart:357-358。属演示数据(文档第 33 行认可其非契约地位),但 DoD 要求「不依赖 Demo 常量」(development-plan.md:340),验收时必须逐页替换。
  • 展示字符串被当数据持久化:lib/data/demo_data.dart:117time: '2小时前')、130、138、147、161 行;distance: '1.2km' 等在 172、185、197、210、223 行。违反第 4.3 节「相对时间、距离由事实字段计算,不持久化展示字符串」(development-plan.md:111),对应风险 4development-plan.md:352)。
  • 无网络层、无登录页、无安全存储:lib/ 下无 http/dio 依赖(pubspec.yaml:30-37cupertino_icons + shared_preferences),AppState 直接持久化宠物/疫苗/帖子/天气到 SharedPreferenceslib/state/app_state.dart:9-12,97-118)。第一迭代需按 4.2 节新建全部网络与鉴权层;届时 token 必须走安全存储而非 SharedPreferencesdevelopment-plan.md:97)。

3. 文档一致性审计

检查项 结论 证据
API Readme 端点 vs 代码 一致 Readme.md:34-44 的 6 个端点与 AuthController.java:24-31UserController.java:27-45 逐一对应
API Readme 端口 vs 配置 一致 Readme.md:33,408081/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.sampleapplication.yml(该步骤只在 development-plan.md:127-138),也无 spring-boot:run 命令。干净检出仅按 README 无法把服务跑起来——文档风险 5(development-plan.md:353)确认属实
Readme 技术栈 RabbitMQ vs 计划 内部一致、与计划冲突 Readme.md:19 列 RabbitMQpatbond-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:34dart format --set-exit-if-changed lib test(缺 --output=none,实际会改写文件);计划门禁是 dart format --output=none --set-exit-if-changed lib testdevelopment-plan.md:317
计划 5.2 节命令中的文件路径 一致 development-plan.md:130-133 引用的两个 .yml.sample 均存在于对应路径
mkdocs.yml 导航 一致 patbond-doc/mkdocs.yml nav 引用的 index.mddevelopment/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-23ConcurrentHashMap 存储)、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/404UserService.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-139spring-boot-starter-amqpspring-cloud-starter-openfeignspring-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-26mvn 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 testflutter analyzeflutter 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 未落入 SharedPreferencesdevelopment-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 -aCI/wrapper 探测)、grep -rnTODO/密钥/URL/展示字符串/测试依赖)、cat/sed(读源码与 SQL)、wc -l(体量)、git status --short / git ls-files / git log --oneline(只读仓库状态)。未修改任何被审计仓库的文件。