Add two audit reports covering static mock data residue and hidden/unimplemented interfaces and logic.
20 KiB
静态模拟数据残留审计报告
总体结论
项目当前没有任何真实后端 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 条命令覆盖主要信号:
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