# 静态模拟数据残留审计报告 ## 总体结论 项目当前没有任何真实后端 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 Only(14 个)— 完全不经过 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 Only(7 个)— 仅经过 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 ```