Files
juwan-frontend/静态模拟数据残留审计报告.md
T
zetaloop e3a392ae0d docs(audit): add mock-data and unimplemented APIs
Add two audit reports covering static mock data residue and hidden/unimplemented interfaces and logic.
2026-02-26 05:00:20 +08:00

261 lines
20 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.
# 静态模拟数据残留审计报告
## 总体结论
项目当前没有任何真实后端 HTTP 请求。全仓库仅 1 处 `fetch(``lib/api/search.ts:34`),请求的是本地 Next.js Route `/api/search`,而该 Route 本身也以 mock 数据为源。`@tanstack/react-query``QueryClientProvider` 已挂载(`app/providers.tsx`),但全仓库 0 处 `useQuery`/`useMutation` 调用。无 `.env` 文件,无 mock/real 环境切换开关。
---
## 一、静态实体数据源 `lib/mock/*.ts`
15 个文件,定义了全部业务实体的硬编码数组,是整个模拟体系的根:
| 文件 | 导出 | 实体类型 |
| ------------------------------ | --------------------------------------- | ------------ |
| `lib/mock/users.ts:3` | `mockUsers: User[]` | 用户 |
| `lib/mock/users.ts:119` | `currentUser = mockUsers[0]` | 当前登录用户 |
| `lib/mock/games.ts:3` | `mockGames: Game[]` | 游戏 |
| `lib/mock/services.ts:3` | `mockServices: PlayerService[]` | 陪玩服务 |
| `lib/mock/players.ts:5` | `mockPlayers: Player[]` | 打手 |
| `lib/mock/shops.ts:4` | `mockShops: Shop[]` | 店铺 |
| `lib/mock/orders.ts:4` | `mockOrders: Order[]` | 订单 |
| `lib/mock/disputes.ts:3` | `mockDisputes: Dispute[]` | 争议 |
| `lib/mock/reviews.ts:3` | `mockReviews: Review[]` | 评价 |
| `lib/mock/posts.ts:4` | `mockPosts: Post[]` | 帖子 |
| `lib/mock/comments.ts:4` | `mockComments: Comment[]` | 评论 |
| `lib/mock/chat.ts:3` | `mockChatSessions: ChatSession[]` | 聊天会话 |
| `lib/mock/chat.ts:95` | `mockChatMessages: ChatMessage[]` | 聊天消息 |
| `lib/mock/favorites.ts:3` | `mockFavorites: Favorite[]` | 收藏 |
| `lib/mock/notifications.ts:3` | `mockNotifications: Notification[]` | 通知 |
| `lib/mock/transactions.ts:3` | `mockTransactions: WalletTransaction[]` | 交易流水 |
| `lib/mock/transactions.ts:139` | `walletBalance = 275` | 钱包余额 |
聚合出口:`lib/mock/index.ts`
---
## 二、Store 层 — mock 初始化 + 前端本地状态机
12 个 Zustand store 全部以 mock 数据初始化,运行时在前端内存中增删改查并生成 ID/时间戳:
| Store 文件 | 初始化行 | mock 来源 |
| --------------------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
| `store/shops.ts:14` | `shops: mockShops` | `mockShops` |
| `store/players.ts:12` | `players: mockPlayers` | `mockPlayers` |
| `store/services.ts:14` | `services: mockServices` | `mockServices` |
| `store/orders.ts:315` | `orders: mockOrders` | `mockOrders` |
| `store/disputes.ts:203` | `disputes: mockDisputes.map(asRecord)` | `mockDisputes` |
| `store/reviews.ts:51` | `reviews: mockReviews` | `mockReviews` + `mockUsers`:24 |
| `store/posts.ts:25` | `posts: mockPosts` | `mockPosts` |
| `store/comments.ts:13` | `comments: mockComments` | `mockComments` |
| `store/chat.ts:23-24` | `sessions: mockChatSessions, messages: mockChatMessages` | `mockChatSessions` + `mockChatMessages` + `mockUsers`:15 |
| `store/favorites.ts:14` | `favorites: mockFavorites` | `mockFavorites` |
| `store/notifications.ts:21` | `notifications: mockNotifications` | `mockNotifications` |
| `store/wallet.ts:20-21` | `balance: walletBalance, transactions: mockTransactions` | `walletBalance` + `mockTransactions` |
所有 store 还通过 `generateId()` 在前端本地生成实体 ID`store/orders.ts:318,350``store/services.ts:21``store/wallet.ts:29,45,71,102,132``store/disputes.ts:231` 等),切后端后 ID 应由服务端分配。
---
## 三、伪 API 层 `lib/api/*.ts`
15 个模块名义上是 API 层,实际全部是同步读写本地 store 或直接返回 mock
| 文件 | 实际行为 | 风险 |
| ---------------------------- | -------------------------------------------------- | --------------------------- |
| `lib/api/users.ts:1,4,8,12` | 直接 `import { currentUser, mockUsers }` 并 return | 高 — 直接返回 mock |
| `lib/api/games.ts:1,4,8` | 直接 `import { mockGames }` 并 return | 高 — 直接返回 mock |
| `lib/api/orders.ts:11` | `useOrderStore.getState().orders` | 中 — 读本地 store |
| `lib/api/services.ts:4` | `useServiceStore.getState().services` | 中 — 读本地 store |
| `lib/api/players.ts:4` | `usePlayerStore.getState().players` | 中 — 读本地 store |
| `lib/api/shops.ts:4` | `useShopStore.getState().shops` | 中 — 读本地 store |
| `lib/api/posts.ts:7` | `usePostStore.getState().posts` | 中 — 读写本地 store |
| `lib/api/comments.ts:8` | `useCommentStore.getState().comments` | 中 — 读写本地 store |
| `lib/api/chat.ts:6` | `useChatStore.getState().sessions` | 中 — 读写本地 store |
| `lib/api/favorites.ts:4` | `useFavoriteStore.getState().favorites` | 中 — 读本地 store |
| `lib/api/notifications.ts:7` | `useNotificationStore.getState().notifications` | 中 — 读写本地 store |
| `lib/api/transactions.ts:4` | `useWalletStore.getState().transactions` | 中 — 读本地 store |
| `lib/api/reviews.ts:6` | `useReviewStore.getState().reviews` | 中 — 读写本地 store |
| `lib/api/disputes.ts:6` | `useDisputeStore.getState().disputes` | 中 — 读写本地 store |
| `lib/api/search.ts:34` | `fetch('/api/search?...')` | 唯一 fetch,但后端仍是 mock |
| `lib/api/client.ts:9` | `requestWithAuth` 仅包装执行器做未登录拦截 | 无网络请求 |
---
## 四、唯一 HTTP 链路 — 搜索
```
lib/api/search.ts:34 → fetch(`/api/search?${params}`)
app/api/search/route.ts:1 → import { mockPlayers, mockServices, mockShops } from "@/lib/mock"
app/api/search/route.ts:51 → players: mockPlayers, shops: mockShops, services: mockServices
```
搜索是项目里唯一走了 HTTP 请求的链路,但 Next.js Route Handler 的数据源仍然是 mock。
---
## 五、前端状态自动推进(Demo 定时器)
这是切后端时事故概率最高的部分 — 前端 `setTimeout` 自动推进业务状态,真实后端应由服务端事件驱动。
**定时常量:**
```
lib/config/demo-timers.ts:1 ORDER_ACCEPT_TIMEOUT_MS = 30_000
lib/config/demo-timers.ts:2 ORDER_CLOSE_TIMEOUT_MS = 30_000
lib/config/demo-timers.ts:3 ORDER_REVIEW_TIMEOUT_MS = 30_000
lib/config/demo-timers.ts:5 DISPUTE_TO_REVIEWING_MS = 5_000
lib/config/demo-timers.ts:6 DISPUTE_TO_RESOLVED_MS = 10_000
```
**订单自动流转:**
| 位置 | 行为 |
| ------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `store/orders.ts:172-206` | `scheduleOrderTimeout` — 订单进入 pending_accept/pending_close/pending_review 后启动 setTimeout 自动超时流转 |
| `store/orders.ts:405-413` | 自动派单模拟 — 当店铺 `dispatchMode === "auto"` 时,`setTimeout(3000)` 自动调用 `acceptOrder` |
**争议自动推进:**
| 位置 | 行为 |
| --------------------------- | ----------------------------------------------------------------------------------- |
| `store/disputes.ts:142-163` | `setTimeout(DISPUTE_TO_REVIEWING_MS)` — 自动将争议从 open → reviewing |
| `store/disputes.ts:165-197` | `setTimeout(DISPUTE_TO_RESOLVED_MS)` — 自动将争议从 reviewing → resolved 并回写订单 |
---
## 六、认证/表单流程模拟
| 位置 | 行为 |
| ---------------------------------------- | --------------------------------------------------------------------------------------- |
| `app/(auth)/login/page.tsx:35-36` | `await new Promise(r => setTimeout(r, 500))` + `login(getCurrentUserForLogin(), ...)` |
| `app/(auth)/register/page.tsx:50-51` | 同上 |
| `components/login-dialog.tsx:44-45` | 同上 |
| `app/(auth)/forgot-password/page.tsx:27` | `await new Promise(r => setTimeout(r, 500))` + toast |
| `app/(account)/verify/page.tsx:41-52` | `submitWithMockApproval` — 提交认证后 `setTimeout(3000)` 自动调用 `approveVerification` |
`getCurrentUserForLogin()` 最终来自 `lib/api/users.ts:12``lib/mock/users.ts:119``currentUser = mockUsers[0]`,即无论输入什么账号密码,登录的永远是同一个硬编码用户。
---
## 七、硬编码展示值
| 位置 | 内容 |
| --------------------------------------- | -------------------------------------------------------------------------- |
| `app/(account)/wallet/page.tsx:154` | `¥1,280.00`(本月收入,写死) |
| `app/(account)/wallet/page.tsx:158` | `¥320.00`(待结算,写死) |
| `app/(account)/wallet/page.tsx:162` | `¥5,400.00`(已提现,写死) |
| `app/(auth)/layout.tsx:4-8` | `12,000+` 认证打手 / `98.6%` 好评率 / `50,000+` 完成订单(营销数字,写死) |
| `app/(order)/dispute/[id]/page.tsx:377` | UI 文案含"模拟处理结果"字样 |
---
## 八、页面数据源分类(34 个页面)
**Store Only14 个)— 完全不经过 lib/api,直接读写 store**
```
app/(account)/notifications/page.tsx
app/(account)/settings/page.tsx
app/(account)/verify/page.tsx [含 setTimeout 模拟]
app/(account)/wallet/page.tsx
app/(dashboard)/dashboard/services/page.tsx
app/(dashboard)/dashboard/shop/employees/page.tsx
app/(dashboard)/dashboard/shop/orders/page.tsx
app/(dashboard)/dashboard/shop/page.tsx
app/(dashboard)/dashboard/shop/rules/page.tsx
app/(dashboard)/dashboard/shop/templates/page.tsx [含 setTimeout]
app/(main)/post/new/page.tsx
app/(order)/chat/page.tsx
app/(order)/order/[id]/page.tsx
app/(order)/orders/page.tsx
```
**API + Store 混合(9 个)— 经过 lib/api 但 lib/api 本身也是读 store**
```
app/(auth)/login/page.tsx [含 setTimeout 模拟登录]
app/(auth)/register/page.tsx [含 setTimeout 模拟注册]
app/(dashboard)/dashboard/page.tsx
app/(dashboard)/dashboard/services/new/page.tsx
app/(dashboard)/dashboard/shop/income/page.tsx
app/(order)/chat/[id]/page.tsx
app/(order)/dispute/[id]/page.tsx
app/(order)/order/new/page.tsx [含 setTimeout]
app/(order)/review/[id]/page.tsx
```
**API Only7 个)— 仅经过 lib/api,但 lib/api 底层仍是本地数据:**
```
app/(main)/page.tsx
app/(main)/community/page.tsx
app/(main)/player/[id]/page.tsx
app/(main)/post/[id]/page.tsx
app/(main)/search/page.tsx [唯一真正 fetch 的页面]
app/(main)/shop/[id]/page.tsx
app/(main)/user/[id]/page.tsx
```
**无 API/Store(4 个)— 纯静态或纯前端模拟:**
```
app/(auth)/forgot-password/page.tsx [setTimeout 模拟]
app/(main)/help/page.tsx [纯静态内容]
app/(main)/privacy/page.tsx [纯静态内容]
app/(main)/terms/page.tsx [纯静态内容]
```
---
## 九、组件层直接读写业务 Store(绕过 lib/api
`useAuthStore`/`useLoginDialogStore` 外,以下共享组件直接操作业务 store:
| 组件 | 直接引用的 store |
| ------------------------------------- | ----------------------------------------------- |
| `components/order-actions.tsx:14-16` | `useChatStore`, `useOrderStore`, `useShopStore` |
| `components/favorite-button.tsx:6` | `useFavoriteStore` |
| `components/post-like-button.tsx:5` | `usePostStore` |
| `components/post-comments.tsx:5` | `useCommentStore` |
| `components/post-comment-count.tsx:3` | `useCommentStore` |
| `components/header.tsx:19-20` | `useNotificationStore`, `useShopStore` |
---
## 十、已排除项(确认不存在)
-`.env` / `.env.local` / `.env.development` 等环境配置文件
- 无 mock/real 环境切换开关(`NEXT_PUBLIC_*MOCK*``USE_MOCK` 等)
- 无运行时 MSW 拦截代码(`msw` 仅作为 `@vitest/mocker` 的 peer dep 出现在 lockfile
-`axios``axios-mock-adapter``json-server``miragejs` 使用
- 无运行时 `.json` 文件作为数据源导入
-`Promise.resolve` 模拟异步返回
- 无 Next.js `rewrites`/`redirects`/proxy 配置
- `useQuery`/`useMutation`/`useSWR` 全仓库 0 处调用
---
## 十一、隐蔽未实现接口(更像真功能,实际仍在前端自转)
这些条目即使去掉了明显的 mock 字样,也会在切真实后端后造成严重偏差:UI 提示可用或可保存,实际没有任何可被后端接入的请求/数据契约/权限边界。
| 类型 | 现象 | 关键证据(单行) | 切后端后常见失败模式 |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| Client Store 与 Server Component 断层 | 管理页或发布页写入 Zustand 后,跳转到详情/公开页看不到改动 | `app/(main)/post/new/page.tsx:79`createPost 写前端 store / `app/(main)/post/[id]/page.tsx:17`(详情页从 server 侧读 getPostById | 新创建内容在列表可见,详情页 404 或展示旧数据;店铺模板预览不反映保存结果 |
| 图片/文件上传未落到接口 | 多处上传只生成本地 blob URL 或占位图,刷新或换设备即失效 | `app/(account)/settings/page.tsx:75``app/(order)/chat/[id]/page.tsx:152``app/(order)/dispute/[id]/page.tsx:88``app/(main)/post/new/page.tsx:84``app/(account)/verify/page.tsx:170` | 后端接入后出现上传缺字段、图片无法复现、消息图片仅本机可见 |
| 会话列表未按参与者过滤 | 消息列表直接渲染全部会话,只在详情页才做参与者校验 | `app/(order)/chat/page.tsx:12` | 列表出现无关会话,后端接入时需要补齐按用户查询与分页 |
| 店铺规则字段缺少业务约束 | 规则页可保存 allowMultiShop 等字段,员工邀请直接改归属,不存在申请/同意流 | `app/(dashboard)/dashboard/shop/rules/page.tsx:48` / `app/(dashboard)/dashboard/shop/employees/page.tsx:75` / `store/players.ts:13` | 规则与权限不生效,导致运营规则与实际行为脱节 |
| 智能派单与流程推进为固定定时器 | 自动派单固定 3 秒自动接单;待接单/结单/评价自动超时推进;争议自动进入 reviewing/resolved | `store/orders.ts:186``store/orders.ts:408``store/disputes.ts:142` | 与真实状态机/消息队列/人工审核不一致,产生错误状态与资金流偏差 |
| 交易明细与订单关联靠字符串解析 | 收入统计用正则从 transaction.description 里提取 ord id | `app/(dashboard)/dashboard/shop/income/page.tsx:51` | 后端字段结构化后该逻辑失效,统计漏算或误算 |
| 身份切换与实体模型不对齐 | 登录永远是固定用户 u1(consumer),UI 允许切换到 player/owner;导航用 user.id 直接拼 player/shop 路由 | `app/(auth)/login/page.tsx:36` / `lib/mock/users.ts:119` / `components/header.tsx:85` / `lib/mock/players.ts:7` / `lib/mock/shops.ts:7` | 打手/店主页面长期空态或 404;店铺后台永远提示无可管理店铺 |
| 看似走 API,实际仍为 mock | `/search` 会发起 fetch,但 `/api/search` 路由以 mockPlayers/mockShops/mockServices 作为数据源 | `lib/api/search.ts:34` / `app/api/search/route.ts:1` | 接真实后端时容易遗漏替换点,导致线上仍走假数据 |
---
## 复扫命令
后续持续监控可用以下 6 条命令覆盖主要信号:
```bash
rg -n '@/lib/mock' app lib store components
rg -n '\bfetch\(' app lib store components
rg -n 'setTimeout\(' app lib store components
rg -n 'from "@/store/' app components
rg -n 'URL\.createObjectURL' app components
rg -n 'window\.prompt' app
```