Files
plp/docs/superpowers/plans/2026-08-03-drift-bottle-web-mvp.md
T
2026-09-14 08:50:50 +08:00

554 lines
22 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.
# 漂流瓶 Web MVP 实现计划
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
**目标:** 在独立仓库中构建并部署一个包含安全登录、扔/捞瓶、审核、实时聊天、举报与拉黑闭环的移动端 Web/PWA。
**架构:** pnpm TypeScript Monorepo 包含 Vue 3 Web、NestJS API/Socket.IO、NestJS Worker 与共享 contracts。PostgreSQL 保存权威事实,Redis 保存验证码和快速限流状态,Transactional Outbox 驱动审核与通知异步任务,Docker Compose 提供可复现运行环境。
**技术栈:** TypeScript、pnpm、Vue 3、Vite、Pinia、Vue Router、NestJS、Socket.IO、Prisma、PostgreSQL 16、Redis 7、Vitest、Supertest、Playwright、Docker Compose、Nginx。
---
## 文件结构
- `package.json`、`pnpm-workspace.yaml`、`tsconfig.base.json`:Monorepo 命令与共享编译配置。
- `.env.example`:不含秘密的完整环境变量契约。
- `packages/contracts/src/*`:REST/Socket DTO、错误码、分页和共享实体类型。
- `apps/api/src/main.ts`:NestJS HTTP/Socket 启动入口与全局安全配置。
- `apps/api/src/common/*`:统一响应、异常、请求 ID、鉴权守卫和当前用户。
- `apps/api/src/auth/*`:演示验证码、JWT、Refresh Token 轮换、设备会话。
- `apps/api/src/profile/*`:匿名资料。
- `apps/api/src/bottle/*`:投瓶、状态机与用户瓶子列表。
- `apps/api/src/match/*`:候选排除、租约、退回和配额。
- `apps/api/src/conversation/*`:首次回复、会话、消息历史和 Socket Gateway。
- `apps/api/src/safety/*`:审核规则、举报、拉黑、处罚和后台接口。
- `apps/api/src/notification/*`:站内通知与未读数。
- `apps/api/src/outbox/*`:事务事件写入和消费租约。
- `apps/worker/src/*`:审核、租约回收、通知与重试 Worker。
- `apps/web/src/api/*`:鉴权刷新串行化与 REST 客户端。
- `apps/web/src/stores/*`:登录态、瓶子、会话和通知状态。
- `apps/web/src/router/*`:页面路由与登录守卫。
- `apps/web/src/views/*`:登录、首页、投瓶、捞瓶、会话、聊天、我的、设置和审核台。
- `apps/web/src/components/*`:底部导航、瓶子卡片、模态框、消息气泡和状态组件。
- `apps/web/src/styles/*`:设计 Token、深海主题、响应式与无障碍样式。
- `apps/web/public/*`:PWA Manifest 和图标。
- `prisma/schema.prisma`、`prisma/migrations/*`、`prisma/seed.ts`:数据库模型、迁移和演示数据。
- `tests/integration/*`:跨模块事务、幂等、Socket 和安全测试。
- `tests/e2e/*`:双用户浏览器完整业务链路。
- `infra/compose.yaml`、`infra/nginx.conf`、`apps/*/Dockerfile`:生产式本机部署。
- `README.md`:启动、测试、演示账号、部署和故障排查。
### 任务 1:初始化 Monorepo 与质量门禁
**文件:**
- 创建:`package.json`
- 创建:`pnpm-workspace.yaml`
- 创建:`tsconfig.base.json`
- 创建:`.gitignore`
- 创建:`.env.example`
- 创建:`packages/contracts/package.json`
- 创建:`packages/contracts/src/index.ts`
- 测试:`packages/contracts/src/index.test.ts`
- [ ] **步骤 1:编写失败的 contracts 测试**
```ts
import { describe, expect, it } from 'vitest';
import { ErrorCode, ok } from './index';
describe('contracts', () => {
it('creates the uniform response envelope', () => {
expect(ok({ id: '1' }, 'req-1')).toEqual({ code: ErrorCode.OK, message: 'success', data: { id: '1' }, requestId: 'req-1' });
});
});
```
- [ ] **步骤 2:运行测试并确认因包和实现不存在而失败**
运行:`corepack pnpm install && corepack pnpm --filter @drift/contracts test`
预期:FAIL,无法解析 `./index` 导出。
- [ ] **步骤 3:建立 workspace、严格 TypeScript、ESLint/Prettier/Vitest 命令并实现 contracts**
```ts
export enum ErrorCode { OK = 'OK', AUTH_TOKEN_EXPIRED = 'AUTH_TOKEN_EXPIRED', AUTH_REFRESH_REUSED = 'AUTH_REFRESH_REUSED', BOTTLE_DAILY_LIMIT = 'BOTTLE_DAILY_LIMIT', BOTTLE_POOL_EMPTY = 'BOTTLE_POOL_EMPTY', BOTTLE_LEASE_EXPIRED = 'BOTTLE_LEASE_EXPIRED', CONTENT_REJECTED = 'CONTENT_REJECTED', USER_BLOCKED = 'USER_BLOCKED', CONVERSATION_FORBIDDEN = 'CONVERSATION_FORBIDDEN', MESSAGE_DUPLICATE = 'MESSAGE_DUPLICATE' }
export const ok = <T>(data: T, requestId: string) => ({ code: ErrorCode.OK, message: 'success', data, requestId });
```
- [ ] **步骤 4:运行 contracts 测试、类型检查和格式检查**
运行:`corepack pnpm --filter @drift/contracts test && corepack pnpm typecheck && corepack pnpm lint`
预期:全部退出码为 0。
- [ ] **步骤 5:Commit**
```bash
git add package.json pnpm-workspace.yaml tsconfig.base.json .gitignore .env.example packages
git commit -m "chore: 初始化 TypeScript monorepo"
```
### 任务 2:建立数据库模型、迁移与测试基础设施
**文件:**
- 创建:`prisma/schema.prisma`
- 创建:`prisma/migrations/0001_init/migration.sql`
- 创建:`prisma/seed.ts`
- 创建:`apps/api/src/database/prisma.service.ts`
- 创建:`tests/integration/database.spec.ts`
- 创建:`infra/compose.test.yaml`
- [ ] **步骤 1:编写数据库约束失败测试**
```ts
it('rejects a second conversation for the same bottle', async () => {
await prisma.conversation.create({ data: firstConversation });
await expect(prisma.conversation.create({ data: secondConversation })).rejects.toMatchObject({ code: 'P2002' });
});
```
同时测试 `BottlePickHistory(bottleId,pickerId)`、`Block(blockerId,blockedId)` 与 `Message(conversationId,clientMsgId)` 组合唯一。
- [ ] **步骤 2:启动测试 PostgreSQL/Redis 并确认迁移或模型缺失导致失败**
运行:`docker compose -f infra/compose.test.yaml up -d && corepack pnpm test:integration -- database.spec.ts`
预期:FAIL,Prisma Schema 或表不存在。
- [ ] **步骤 3:实现规格中的全部核心模型、枚举、索引、唯一约束和 UTC 时间字段**
关键约束:
```prisma
model Conversation {
id String @id @default(uuid()) @db.Uuid
sourceBottleId String @unique @db.Uuid
status ConversationStatus @default(ACTIVE)
createdAt DateTime @default(now())
lastMessageAt DateTime @default(now())
}
model Message {
id String @id @default(uuid()) @db.Uuid
conversationId String @db.Uuid
clientMsgId String
seq BigInt
@@unique([conversationId, clientMsgId])
@@unique([conversationId, seq])
}
```
- [ ] **步骤 4:应用迁移、生成客户端、运行约束测试并 Seed 两个用户及池中瓶子**
运行:`corepack pnpm prisma generate && corepack pnpm prisma migrate deploy && corepack pnpm prisma db seed && corepack pnpm test:integration -- database.spec.ts`
预期:迁移成功,所有唯一约束测试 PASS。
- [ ] **步骤 5:Commit**
```bash
git add prisma apps/api/src/database tests/integration/database.spec.ts infra/compose.test.yaml
git commit -m "feat: 建立漂流瓶权威数据模型"
```
### 任务 3:实现 API 骨架、统一协议与健康检查
**文件:**
- 创建:`apps/api/package.json`
- 创建:`apps/api/src/main.ts`
- 创建:`apps/api/src/app.module.ts`
- 创建:`apps/api/src/common/http-response.interceptor.ts`
- 创建:`apps/api/src/common/domain.exception.ts`
- 创建:`apps/api/src/common/request-id.middleware.ts`
- 创建:`apps/api/src/health/health.controller.ts`
- 测试:`apps/api/src/health/health.e2e-spec.ts`
- [ ] **步骤 1:编写健康检查与统一错误响应测试**
```ts
await request(app.getHttpServer()).get('/api/v1/health').expect(200).expect(({ body }) => {
expect(body.code).toBe('OK');
expect(body.data.status).toBe('ok');
expect(body.requestId).toBeTruthy();
});
```
- [ ] **步骤 2:运行并确认 Nest 应用尚不存在**
运行:`corepack pnpm --filter @drift/api test -- health.e2e-spec.ts`
预期:FAIL,无法加载 `AppModule`。
- [ ] **步骤 3:实现 `/api/v1` 前缀、DTO 白名单、Helmet、同源 CORS、请求 ID、统一响应和异常映射**
启动时必须开启 `ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })`,并在 `/api/v1/health` 同时检查数据库与 Redis。
- [ ] **步骤 4:运行 API 测试**
运行:`corepack pnpm --filter @drift/api test`
预期:PASS。
- [ ] **步骤 5:Commit**
```bash
git add apps/api
git commit -m "feat: 添加 NestJS API 基础设施"
```
### 任务 4:实现演示短信登录与安全 Token 生命周期
**文件:**
- 创建:`apps/api/src/auth/auth.module.ts`
- 创建:`apps/api/src/auth/auth.controller.ts`
- 创建:`apps/api/src/auth/auth.service.ts`
- 创建:`apps/api/src/auth/token.service.ts`
- 创建:`apps/api/src/auth/auth.guard.ts`
- 创建:`apps/api/src/auth/dto/*.ts`
- 测试:`apps/api/src/auth/auth.e2e-spec.ts`
- [ ] **步骤 1:编写验证码、登录、串行轮换、退出和 Refresh 重放测试**
```ts
const sent = await request(server).post('/api/v1/auth/sms/send').send({ phone: '13800000001', deviceId: 'web-a' }).expect(201);
const login = await request(server).post('/api/v1/auth/sms/login').send({ phone: '13800000001', code: sent.body.data.debugCode, deviceId: 'web-a' }).expect(201);
expect(login.headers['set-cookie'][0]).toContain('refresh_token=');
```
重用已轮换 Cookie 必须返回 `AUTH_REFRESH_REUSED`,且当前设备会话被撤销。
- [ ] **步骤 2:运行测试确认路由不存在**
运行:`corepack pnpm --filter @drift/api test -- auth.e2e-spec.ts`
预期:FAIL,接口返回 404。
- [ ] **步骤 3:实现手机号规范化/HMAC、验证码 Redis 哈希与 TTL、限流、账号创建、JWT、Refresh 哈希/轮换和注销**
开发模式返回 `debugCode`;`NODE_ENV=production` 响应中必须无该字段。Cookie 设置 `HttpOnly; SameSite=Strict; Path=/api/v1/auth`,生产模式追加 `Secure`。
- [ ] **步骤 4:运行登录安全测试与日志敏感数据扫描**
运行:`corepack pnpm --filter @drift/api test -- auth.e2e-spec.ts && corepack pnpm test:security`
预期:全部 PASS,测试日志中不存在完整手机号、验证码、JWT 或 Cookie。
- [ ] **步骤 5:Commit**
```bash
git add apps/api/src/auth tests
git commit -m "feat: 实现演示验证码和令牌轮换"
```
### 任务 5:实现匿名资料、投瓶状态机与异步审核
**文件:**
- 创建:`apps/api/src/profile/*`
- 创建:`apps/api/src/bottle/*`
- 创建:`apps/api/src/outbox/*`
- 创建:`apps/worker/package.json`
- 创建:`apps/worker/src/main.ts`
- 创建:`apps/worker/src/moderation.processor.ts`
- 测试:`tests/integration/bottle-moderation.spec.ts`
- [ ] **步骤 1:编写投瓶配额、拒绝词、复审词、普通文本入池与 Outbox 幂等测试**
```ts
expect(await throwBottle('祝你今天开心')).toMatchObject({ reviewStatus: 'REVIEWING' });
await worker.runOnce();
expect(await getBottle()).toMatchObject({ reviewStatus: 'APPROVED', poolStatus: 'IN_POOL' });
```
循环第 11 次投瓶必须返回 `BOTTLE_DAILY_LIMIT`。
- [ ] **步骤 2:运行测试确认模块不存在**
运行:`corepack pnpm test:integration -- bottle-moderation.spec.ts`
预期:FAIL,无法解析 Bottle 服务。
- [ ] **步骤 3:实现匿名 Profile、Bottle 状态机、UTC+8 日期键、事务配额、审核任务与 Outbox Worker**
规则配置固定为可测试集合:拒绝词进入 `REJECTED`,复审词进入 `MANUAL_REVIEW`,其余进入 `IN_POOL`。Worker 使用事件 ID 去重、`FOR UPDATE SKIP LOCKED` 领取和指数退避字段。
- [ ] **步骤 4:运行相关单元/集成测试并验证重复消费不改变结果**
运行:`corepack pnpm test:unit && corepack pnpm test:integration -- bottle-moderation.spec.ts`
预期:PASS。
- [ ] **步骤 5:Commit**
```bash
git add apps/api/src/profile apps/api/src/bottle apps/api/src/outbox apps/worker tests/integration/bottle-moderation.spec.ts
git commit -m "feat: 实现投瓶和异步内容审核"
```
### 任务 6:实现随机捞取、排除、租约和并发一致性
**文件:**
- 创建:`apps/api/src/match/match.module.ts`
- 创建:`apps/api/src/match/match.controller.ts`
- 创建:`apps/api/src/match/match.service.ts`
- 创建:`apps/api/src/match/candidate.repository.ts`
- 创建:`apps/worker/src/lease-reaper.processor.ts`
- 测试:`tests/integration/match-concurrency.spec.ts`
- [ ] **步骤 1:编写本人/已捞/双向拉黑排除、21 次配额、并发租约和超时回池测试**
```ts
const results = await Promise.allSettled([pick(userA), pick(userB)]);
expect(results.filter(x => x.status === 'fulfilled')).toHaveLength(1);
expect(await activeLeaseCount(bottle.id)).toBe(1);
```
- [ ] **步骤 2:运行测试确认失败**
运行:`corepack pnpm test:integration -- match-concurrency.spec.ts`
预期:FAIL,MatchService 不存在。
- [ ] **步骤 3:实现小批随机候选、事务行锁、CAS、租约历史、展示即计次和有限重试**
查询必须排除作者、历史、双向 block、非 `IN_POOL` 和活跃租约;事务以数据库为最终裁决,候选为空返回 `BOTTLE_POOL_EMPTY`。
- [ ] **步骤 4:连续运行并发测试 20 次以排除偶发竞态**
运行:`for i in $(seq 1 20); do corepack pnpm test:integration -- match-concurrency.spec.ts || exit 1; done`
预期:20 次全部 PASS。
- [ ] **步骤 5:Commit**
```bash
git add apps/api/src/match apps/worker/src/lease-reaper.processor.ts tests/integration/match-concurrency.spec.ts
git commit -m "feat: 实现公平捞瓶和领取租约"
```
### 任务 7:实现首次回复、会话与 Socket.IO 消息
**文件:**
- 创建:`apps/api/src/conversation/conversation.module.ts`
- 创建:`apps/api/src/conversation/conversation.controller.ts`
- 创建:`apps/api/src/conversation/conversation.service.ts`
- 创建:`apps/api/src/conversation/chat.gateway.ts`
- 创建:`apps/api/src/conversation/socket-auth.adapter.ts`
- 测试:`tests/integration/conversation-concurrency.spec.ts`
- 测试:`tests/integration/socket-chat.spec.ts`
- [ ] **步骤 1:编写并发首次回复、clientMsgId 幂等、非成员拒绝、顺序消息和断线补齐测试**
```ts
const [a, b] = await Promise.all([
reply({ bottleId, leaseId, clientMsgId: 'first-1', text: '你好' }),
reply({ bottleId, leaseId, clientMsgId: 'first-1', text: '你好' }),
]);
expect(a.conversationId).toBe(b.conversationId);
expect(await conversationCount(bottleId)).toBe(1);
```
- [ ] **步骤 2:运行测试确认失败**
运行:`corepack pnpm test:integration -- conversation-concurrency.spec.ts socket-chat.spec.ts`
预期:FAIL,会话模块和 Socket namespace 不存在。
- [ ] **步骤 3:实现事务建会话、会话内 seq、成员授权、消息审核、持久化后 ACK、房间广播和游标历史接口**
Socket namespace 使用 `/chat`,事件使用 `message:send`、`message:ack`、`message:new`、`conversation:read`。握手和每条命令均校验 Access Token 与成员状态。
- [ ] **步骤 4:运行会话/Socket 测试并模拟断开后重连**
运行:`corepack pnpm test:integration -- conversation-concurrency.spec.ts socket-chat.spec.ts`
预期:PASS,重连同步无缺失、无重复且 seq 递增。
- [ ] **步骤 5:Commit**
```bash
git add apps/api/src/conversation tests/integration/conversation-concurrency.spec.ts tests/integration/socket-chat.spec.ts
git commit -m "feat: 实现实时匿名会话"
```
### 任务 8:实现举报、拉黑、处罚、通知与审核后台 API
**文件:**
- 创建:`apps/api/src/safety/*`
- 创建:`apps/api/src/notification/*`
- 创建:`apps/api/src/admin/*`
- 创建:`apps/worker/src/notification.processor.ts`
- 测试:`tests/integration/safety-admin.spec.ts`
- [ ] **步骤 1:编写拉黑即时禁止消息/匹配、举报快照、管理员处置和审计测试**
```ts
await block(userA, userB);
await expect(sendMessage(userB, conversationId, 'still there?')).rejects.toMatchObject({ code: 'USER_BLOCKED' });
expect(await auditLogsFor(report.id)).toContainEqual(expect.objectContaining({ action: 'REPORT_RESOLVED' }));
```
- [ ] **步骤 2:运行测试确认失败**
运行:`corepack pnpm test:integration -- safety-admin.spec.ts`
预期:FAIL,Safety/Admin 模块不存在。
- [ ] **步骤 3:实现举报、双向业务隔离、处罚检查、最小管理员角色、审核/举报处置和站内通知**
管理员接口置于 `/api/v1/admin`,普通 Token 必须返回 403;审计元数据不得包含完整手机号或消息正文。
- [ ] **步骤 4:运行治理测试和 IDOR 测试**
运行:`corepack pnpm test:integration -- safety-admin.spec.ts && corepack pnpm test:security`
预期:PASS。
- [ ] **步骤 5:Commit**
```bash
git add apps/api/src/safety apps/api/src/notification apps/api/src/admin apps/worker/src/notification.processor.ts tests/integration/safety-admin.spec.ts
git commit -m "feat: 完成举报拉黑和审核处置闭环"
```
### 任务 9:实现移动端 Vue 3 PWA
**文件:**
- 创建:`apps/web/package.json`
- 创建:`apps/web/vite.config.ts`
- 创建:`apps/web/src/main.ts`
- 创建:`apps/web/src/App.vue`
- 创建:`apps/web/src/api/client.ts`
- 创建:`apps/web/src/api/socket.ts`
- 创建:`apps/web/src/stores/*.ts`
- 创建:`apps/web/src/router/index.ts`
- 创建:`apps/web/src/views/*.vue`
- 创建:`apps/web/src/components/*.vue`
- 创建:`apps/web/src/styles/*.css`
- 创建:`apps/web/public/manifest.webmanifest`
- 测试:`apps/web/src/**/*.spec.ts`
- [ ] **步骤 1:编写登录刷新串行化、路由守卫、消息去重和核心组件测试**
```ts
it('coalesces concurrent 401 refreshes', async () => {
await Promise.all([client.get('/me'), client.get('/conversations')]);
expect(refreshSpy).toHaveBeenCalledTimes(1);
});
```
- [ ] **步骤 2:运行 Web 测试确认应用不存在**
运行:`corepack pnpm --filter @drift/web test`
预期:FAIL,Vite/Vue 入口不存在。
- [ ] **步骤 3:实现完整页面、深海设计系统、无障碍状态、Pinia 数据流、Socket 重连与 PWA**
必须具备 375px 移动视口、桌面居中布局、44px 最小触控区域、键盘焦点样式、加载/空/错误/离线状态;Access Token 只在内存中,刷新由单 Promise 锁合并。
- [ ] **步骤 4:运行组件测试、类型检查、生产构建及 PWA Manifest 检查**
运行:`corepack pnpm --filter @drift/web test && corepack pnpm --filter @drift/web typecheck && corepack pnpm --filter @drift/web build`
预期:全部 PASS,生成 `apps/web/dist/manifest.webmanifest`。
- [ ] **步骤 5:Commit**
```bash
git add apps/web
git commit -m "feat: 构建漂流瓶移动端 PWA"
```
### 任务 10:实现双用户完整 E2E
**文件:**
- 创建:`playwright.config.ts`
- 创建:`tests/e2e/auth.spec.ts`
- 创建:`tests/e2e/bottle-chat.spec.ts`
- 创建:`tests/e2e/safety.spec.ts`
- 创建:`tests/e2e/admin.spec.ts`
- [ ] **步骤 1:编写两个独立浏览器上下文的失败 E2E**
```ts
const alice = await browser.newContext();
const bob = await browser.newContext();
// Alice 登录并投瓶;Bob 登录、捞取、首次回复;双方实时互发;Bob 断线后补齐;Alice 举报并拉黑。
```
测试必须通过页面交互完成,不直接调用领域服务绕开 UI。
- [ ] **步骤 2:运行 E2E 并记录首个真实失败点**
运行:`corepack pnpm test:e2e`
预期:初次 FAIL,并指向尚未正确接线的页面或接口。
- [ ] **步骤 3:只修复 E2E 暴露的接线、选择器、等待条件与真实缺陷**
使用 `data-testid` 稳定选择器;禁止固定 sleep,等待 API 响应、Socket ACK 或可见状态。
- [ ] **步骤 4:运行全部 E2E,包含登录、完整社交闭环、治理和后台审计**
运行:`corepack pnpm test:e2e`
预期:全部 PASS,并保留失败时 trace/screenshot/video。
- [ ] **步骤 5:Commit**
```bash
git add playwright.config.ts tests/e2e apps
git commit -m "test: 覆盖双用户漂流瓶完整流程"
```
### 任务 11:容器化部署与局域网预览
**文件:**
- 创建:`apps/api/Dockerfile`
- 创建:`apps/worker/Dockerfile`
- 创建:`apps/web/Dockerfile`
- 创建:`infra/compose.yaml`
- 创建:`infra/nginx.conf`
- 创建:`infra/healthcheck.sh`
- 创建:`README.md`
- [ ] **步骤 1:编写部署冒烟脚本并确认服务未启动时失败**
```bash
curl -fsS http://127.0.0.1:${APP_PORT}/api/v1/health | jq -e '.code == "OK" and .data.status == "ok"'
curl -fsS http://127.0.0.1:${APP_PORT}/ | grep -q '<div id="app">'
```
- [ ] **步骤 2:运行冒烟脚本确认连接失败**
运行:`bash infra/healthcheck.sh`
预期:FAIL,目标端口未监听。
- [ ] **步骤 3:实现多阶段镜像、同源 Nginx 代理、健康检查、迁移 Job、持久卷和最小权限容器**
Compose 服务为 `postgres`、`redis`、`migrate`、`api`、`worker`、`web`;API/Worker 等待迁移成功,Web 仅暴露配置端口并代理 `/api` 与 `/socket.io`。
- [ ] **步骤 4:真实构建、启动、迁移、Seed、健康检查并从局域网地址请求**
运行:`docker compose -f infra/compose.yaml up -d --build && bash infra/healthcheck.sh`
预期:所有服务 healthy,首页和 API 健康检查均成功。
- [ ] **步骤 5:Commit**
```bash
git add apps/*/Dockerfile infra README.md
git commit -m "ops: 添加可复现本机部署"
```
### 任务 12:最终质量、安全和验收验证
**文件:**
- 修改:`README.md`
- 创建:`docs/verification/2026-08-03-mvp-verification.md`
- [ ] **步骤 1:运行完整静态、单元、集成、E2E 和生产构建门禁**
运行:`corepack pnpm lint && corepack pnpm typecheck && corepack pnpm test:unit && corepack pnpm test:integration && corepack pnpm test:e2e && corepack pnpm build`
预期:全部退出码 0。
- [ ] **步骤 2:运行并发、幂等、权限和敏感日志专项测试**
运行:`corepack pnpm test:security && corepack pnpm test:concurrency`
预期:全部 PASS。
- [ ] **步骤 3:验证 Compose 状态、健康端点、监听端口和局域网 IP**
运行:`docker compose -f infra/compose.yaml ps && bash infra/healthcheck.sh && hostname -I`
预期:所有必要容器 healthy,输出至少一个可访问 IP。
- [ ] **步骤 4:将真实命令、退出码、测试统计、容器状态和预览 URL 写入验证报告**
验证报告不得填写推测结果;只记录命令实际输出。README 写明演示验证码流程、管理员 Seed 登录方式、启动/停止/重置命令和数据清理风险。
- [ ] **步骤 5:最终 Commit**
```bash
git add README.md docs/verification/2026-08-03-mvp-verification.md
git commit -m "docs: 记录 MVP 验收结果"
```