# 低侵入 Mock 架构设计 > 文档创建时间: 2026-03-19 (北京时间) > 适用项目: Crawlful Hub > 目标: 实现"不污染代码、AI上下文安全"的Mock方案 --- ## 📋 目录 1. [核心原则](#核心原则) 2. [架构概览](#架构概览) 3. [前端Mock方案](#前端mock方案) 4. [后端/BFF Mock方案](#后端bff-mock方案) 5. [目录规范](#目录规范) 6. [AI协作规范](#ai协作规范) 7. [实施任务](#实施任务) --- ## 核心原则 ### 1. Mock ≠ 写死数据 ❌ **错误做法**: 在组件中直接写死数据 ```typescript const data = [{ id: 1, name: 'Mock商品' }] ``` ✅ **正确做法**: 通过数据源抽象层获取 ```typescript const data = await productDataSource.list() ``` ### 2. Mock 必须"可插拔" Mock 应该是: - ✅ 可开启 / 可关闭 - ✅ 可替换 / 可隔离 - ✅ 环境变量控制 ### 3. Mock 属于"数据源层",不是"业务层" ``` 业务代码 → DataSource抽象层 → (Mock/真实API) ``` --- ## 架构概览 ### 三层数据架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端 (React + Umi) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ 业务组件 │ → │ DataSource │ → │ API/Mock │ │ │ │ │ │ 抽象层 │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ BFF 层 (服务编排层) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Service │ → │ Mock切换 │ → │ 真实/Mock │ │ │ │ │ │ │ │ 服务 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 后端微服务层 │ └─────────────────────────────────────────────────────────────┘ ``` ### Mock 分布策略 | 层级 | 是否Mock | 说明 | |------|---------|------| | 前端 | ⚠️ 可选 | 推荐用MSW,保持业务代码纯净 | | DataSource | ✅ 可切换 | 控制入口,环境变量驱动 | | BFF | ✅ 主力Mock | 最真实,前端无感知 | | 后端 | ❌ 不建议 | 除非联调测试 | --- ## 前端Mock方案 ### 方案对比 | 方案 | 侵入性 | 推荐度 | 适用场景 | |------|--------|--------|----------| | MSW (Mock Service Worker) | ⭐ 最低 | 🥇 最推荐 | 前端开发、UI测试 | | DataSource抽象 | ⭐⭐ 低 | 🥈 推荐 | 需要自定义逻辑 | | 本地Mock Server | ⭐⭐⭐ 中 | 🥉 可选 | 团队共享Mock | | 硬编码 | ❌ 高 | 🚫 不推荐 | 绝对禁止 | ### 推荐方案: MSW + DataSource抽象 #### 1. 目录结构 ``` /dashboard/src /api ← 真实API封装 product.ts user.ts order.ts certificate.ts /mock ← 所有Mock数据和拦截器 /data ← Mock数据文件 product.mock.ts user.mock.ts certificate.mock.ts msw.ts ← MSW初始化配置 browser.ts ← 浏览器环境启动 server.ts ← Node/测试环境启动 /services ← 数据源抽象层 productDataSource.ts userDataSource.ts certificateDataSource.ts /types ← 类型定义 datasource.ts ← DataSource接口定义 ``` #### 2. DataSource接口定义 ```typescript // /dashboard/src/types/datasource.ts /** * [MOCK-001] DataSource抽象接口 * 所有数据源必须实现此接口 * AI注意: 这是抽象层,不涉及具体实现 */ export interface IDataSource { list(params?: QueryParams): Promise; detail(id: string): Promise; create(data: Partial): Promise; update(id: string, data: Partial): Promise; delete(id: string): Promise; } export interface ProductQueryParams { keyword?: string; category?: string; status?: string; page?: number; pageSize?: number; } export interface CertificateQueryParams { status?: string; type?: string; page?: number; pageSize?: number; } ``` #### 3. DataSource抽象层实现 ```typescript // /dashboard/src/services/certificateDataSource.ts import { Certificate } from '@/types/certificate'; import { IDataSource, CertificateQueryParams } from '@/types/datasource'; /** * [MOCK-002] 证书数据源抽象层 * 通过环境变量自动切换Mock/真实API * AI注意: 这是唯一入口,业务代码必须调用此层 */ // 真实API实现 class ApiCertificateDataSource implements IDataSource { async list(params?: CertificateQueryParams): Promise { const query = new URLSearchParams(params as any).toString(); const response = await fetch(`/api/v1/certificate/certificates?${query}`); const result = await response.json(); return result.data || []; } async detail(id: string): Promise { const response = await fetch(`/api/v1/certificate/certificates/${id}`); const result = await response.json(); return result.data || null; } async create(data: Partial): Promise { const response = await fetch('/api/v1/certificate/certificates', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); const result = await response.json(); return result.data; } async update(id: string, data: Partial): Promise { const response = await fetch(`/api/v1/certificate/certificates/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); const result = await response.json(); return result.data; } async delete(id: string): Promise { await fetch(`/api/v1/certificate/certificates/${id}`, { method: 'DELETE', }); } } // Mock实现 (完全独立文件) class MockCertificateDataSource implements IDataSource { private mockData: Certificate[] = [ { id: '1', name: 'CE认证证书', type: 'PRODUCT_CERT', status: 'APPROVED', fileUrl: '/files/ce-cert.pdf', fileName: 'CE-Certificate-2026.pdf', uploadDate: '2026-03-15', expiryDate: '2027-03-15', approvedBy: 'admin', approvedDate: '2026-03-16', productId: 'P001', productName: '工业温度传感器', notes: '欧盟市场准入认证', }, // ... 更多Mock数据 ]; async list(params?: CertificateQueryParams): Promise { // 模拟网络延迟 await new Promise(resolve => setTimeout(resolve, 300)); let result = [...this.mockData]; if (params?.status) { result = result.filter(item => item.status === params.status); } if (params?.type) { result = result.filter(item => item.type === params.type); } return result; } async detail(id: string): Promise { await new Promise(resolve => setTimeout(resolve, 200)); return this.mockData.find(item => item.id === id) || null; } async create(data: Partial): Promise { await new Promise(resolve => setTimeout(resolve, 500)); const newCert: Certificate = { id: `${Date.now()}`, ...data as Certificate, status: 'PENDING', uploadDate: new Date().toISOString().split('T')[0], }; this.mockData.unshift(newCert); return newCert; } async update(id: string, data: Partial): Promise { await new Promise(resolve => setTimeout(resolve, 300)); const index = this.mockData.findIndex(item => item.id === id); if (index === -1) throw new Error('Certificate not found'); this.mockData[index] = { ...this.mockData[index], ...data }; return this.mockData[index]; } async delete(id: string): Promise { await new Promise(resolve => setTimeout(resolve, 200)); this.mockData = this.mockData.filter(item => item.id !== id); } } // 导出数据源实例 (环境变量控制) const useMock = process.env.REACT_APP_USE_MOCK === 'true'; export const certificateDataSource: IDataSource = useMock ? new MockCertificateDataSource() : new ApiCertificateDataSource(); // 标记Mock状态 (用于调试) export const __MOCK__ = useMock; ``` #### 4. MSW配置 ```typescript // /dashboard/src/mock/msw.ts import { setupWorker, rest } from 'msw'; import { mockCertificates } from './data/certificate.mock'; /** * [MOCK-003] MSW Mock配置 * 拦截HTTP请求,返回Mock数据 * AI注意: 这是网络层拦截,业务代码完全无感知 */ export const worker = setupWorker( // 证书列表 rest.get('/api/v1/certificate/certificates', (req, res, ctx) => { const status = req.url.searchParams.get('status'); const type = req.url.searchParams.get('type'); let data = [...mockCertificates]; if (status) data = data.filter(item => item.status === status); if (type) data = data.filter(item => item.type === type); return res( ctx.delay(300), ctx.json({ success: true, data, total: data.length, }) ); }), // 证书详情 rest.get('/api/v1/certificate/certificates/:id', (req, res, ctx) => { const { id } = req.params; const cert = mockCertificates.find(item => item.id === id); if (!cert) { return res( ctx.status(404), ctx.json({ success: false, error: '证书不存在' }) ); } return res( ctx.delay(200), ctx.json({ success: true, data: cert }) ); }), // 创建证书 rest.post('/api/v1/certificate/certificates', async (req, res, ctx) => { const body = await req.json(); const newCert = { id: `${Date.now()}`, ...body, status: 'PENDING', uploadDate: new Date().toISOString().split('T')[0], }; return res( ctx.delay(500), ctx.json({ success: true, data: { id: newCert.id } }) ); }), // 更新证书状态 rest.put('/api/v1/certificate/certificates/:id/status', async (req, res, ctx) => { const { id } = req.params; const { status, approvedBy } = await req.json(); return res( ctx.delay(300), ctx.json({ success: true }) ); }), // 删除证书 rest.delete('/api/v1/certificate/certificates/:id', (req, res, ctx) => { return res( ctx.delay(200), ctx.json({ success: true }) ); }) ); ``` ```typescript // /dashboard/src/mock/browser.ts import { worker } from './msw'; export { worker }; ``` ```typescript // /dashboard/src/index.tsx (入口文件) import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; /** * [MOCK-004] MSW启动配置 * 仅在REACT_APP_USE_MOCK=true时启用 */ async function enableMocking() { if (process.env.REACT_APP_USE_MOCK !== 'true') { return; } const { worker } = await import('./mock/browser'); return worker.start({ onUnhandledRequest: 'bypass', // 未拦截的请求直接透传 }); } enableMocking().then(() => { const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( ); }); ``` #### 5. 业务组件使用示例 ```typescript // /dashboard/src/pages/Compliance/index.tsx import { certificateDataSource } from '@/services/certificateDataSource'; const ComplianceManagement: React.FC = () => { const fetchData = useCallback(async () => { setLoading(true); try { // 调用DataSource层,完全无感知是Mock还是真实API const data = await certificateDataSource.list({ status: activeStatus, type: activeType, }); setCertificates(data); } catch (error) { message.error('获取证书列表失败'); } finally { setLoading(false); } }, [activeStatus, activeType]); const handleUploadSubmit = async () => { try { const values = await uploadForm.validateFields(); await certificateDataSource.create({ name: values.name, type: values.type, expiryDate: values.expiryDate.format('YYYY-MM-DD'), productId: values.productId, productName: values.productName, notes: values.notes, }); message.success('证书上传成功'); fetchData(); } catch (error) { message.error('上传失败'); } }; // ... 其他逻辑 }; ``` --- ## 后端/BFF Mock方案 ### 架构设计 ``` BFF层 ├─ Service (业务逻辑) ├─ Mock切换器 (环境变量控制) ├─ 真实API客户端 └─ Mock服务 ``` ### 实现示例 ```typescript // /server/src/bff/services/certificateService.ts import { CertificateService as RealCertificateService } from '../../services/CertificateService'; import { MockCertificateService } from '../mock/certificateMock'; /** * [MOCK-005] BFF层证书服务 * 通过环境变量自动切换Mock/真实服务 */ const useMock = process.env.USE_MOCK === 'true'; export const certificateService = useMock ? new MockCertificateService() : new RealCertificateService(); // 类型导出,确保调用方类型安全 export type ICertificateService = typeof certificateService; ``` ```typescript // /server/src/bff/mock/certificateMock.ts import { ICertificateService } from '../services/certificateService'; /** * [MOCK-006] BFF层Mock服务 * 完全独立的Mock实现 * AI注意: 这是Mock实现,不是真实业务逻辑 */ export class MockCertificateService implements ICertificateService { private mockData = [ // ... Mock数据 ]; async query(options: any) { await this.delay(300); return { items: this.mockData, total: this.mockData.length, }; } async getById(id: string, tenantId: string, shopId: string) { await this.delay(200); return this.mockData.find(item => item.id === id) || null; } async create(input: any) { await this.delay(500); const id = `mock-${Date.now()}`; // ... 创建逻辑 return id; } private delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } } ``` --- ## 目录规范 ### 前端目录规范 ``` /dashboard/src /mock ← 所有Mock相关 /data ← Mock数据定义 *.mock.ts msw.ts ← MSW配置 browser.ts ← 浏览器入口 server.ts ← Node/测试入口 README.md ← Mock使用说明 /services ← DataSource抽象层 *DataSource.ts /api ← 真实API封装 *.ts ``` ### 后端目录规范 ``` /server/src /bff ← BFF层 /mock ← Mock服务 *.ts /services ← 服务层 *.ts /api ← API客户端 *.ts /services ← 真实服务 *.ts ``` ### 标记规范 所有Mock文件必须包含以下标记: ```typescript /** * [MOCK-XXX] 功能描述 * AI注意: 这是Mock实现,不是真实业务逻辑 * 仅在USE_MOCK=true时启用 */ // 或者 // [MOCK] 此文件为Mock数据,AI请勿当作真实业务逻辑 ``` --- ## AI协作规范 ### 1. Mock文件识别规则 AI在分析代码时: - ✅ 忽略 `/mock` 目录下所有文件 - ✅ 忽略包含 `[MOCK]` 标记的文件 - ✅ 忽略 `__MOCK__` 变量为真的代码路径 ### 2. 业务代码规范 AI生成业务代码时必须: - ✅ 调用 DataSource 抽象层 - ✅ 不直接调用 API 或 Mock - ✅ 不硬编码任何数据 ### 3. 文档声明 ```markdown ## Mock使用规范 1. 所有Mock数据必须放 `/mock` 目录 2. DataSource层是唯一业务入口 3. `USE_MOCK=true` 时才启用Mock 4. AI不得将Mock数据当作真实业务逻辑 5. Mock不代表真实接口结构,仅用于UI开发 ``` --- ## 实施任务 ### 任务清单 | 任务ID | 任务描述 | 涉及文件 | 优先级 | 状态 | |--------|----------|----------|--------|------| | MOCK-001 | 创建DataSource接口定义 | `/dashboard/src/types/datasource.ts` | P0 | ⏳ 待实现 | | MOCK-002 | 实现证书DataSource | `/dashboard/src/services/certificateDataSource.ts` | P0 | ⏳ 待实现 | | MOCK-003 | 配置MSW拦截器 | `/dashboard/src/mock/msw.ts` | P0 | ⏳ 待实现 | | MOCK-004 | 入口文件MSW启动 | `/dashboard/src/index.tsx` | P0 | ⏳ 待实现 | | MOCK-005 | BFF层Mock切换器 | `/server/src/bff/services/*.ts` | P1 | ⏳ 待实现 | | MOCK-006 | BFF层Mock服务 | `/server/src/bff/mock/*.ts` | P1 | ⏳ 待实现 | | MOCK-007 | 产品模块DataSource | `/dashboard/src/services/productDataSource.ts` | P1 | ⏳ 待实现 | | MOCK-008 | 订单模块DataSource | `/dashboard/src/services/orderDataSource.ts` | P1 | ⏳ 待实现 | | MOCK-009 | 用户模块DataSource | `/dashboard/src/services/userDataSource.ts` | P1 | ⏳ 待实现 | | MOCK-010 | 库存模块DataSource | `/dashboard/src/services/inventoryDataSource.ts` | P1 | ⏳ 待实现 | | MOCK-011 | 环境变量配置 | `.env.development`, `.env.production` | P0 | ⏳ 待实现 | | MOCK-012 | 文档完善 | `docs/01_Architecture/Mock_Architecture.md` | P0 | ✅ 已完成 | | MOCK-013 | 自动选品模块Mock数据 | `/dashboard/src/mock/data/productSelection.mock.ts` | P0 | ✅ 已完成 | | MOCK-014 | 自动选品DataSource | `/dashboard/src/services/productSelectionDataSource.ts` | P0 | ✅ 已完成 | | MOCK-015 | 自动选品页面Mock集成 | `/dashboard/src/pages/AutoProductSelection/index.tsx` | P0 | ✅ 已完成 | ### 环境变量配置 ```bash # .env.development (开发环境) REACT_APP_USE_MOCK=true # 前端启用Mock USE_MOCK=true # 后端启用Mock # .env.production (生产环境) REACT_APP_USE_MOCK=false # 前端禁用Mock USE_MOCK=false # 后端禁用Mock ``` ### 快速启动命令 ```bash # 开发模式 (带Mock) npm run dev:mock # 开发模式 (真实API) npm run dev # 生产构建 npm run build ``` --- ## 优势总结 | 特性 | 描述 | |------|------| | 🎯 零侵入 | 业务层完全不感知Mock | | 🔄 可切换 | 环境变量一键切换Mock/真实API | | 🤖 AI安全 | Mock独立文件 + 文档规范 | | 📦 可扩展 | 支持MSW、BFF Mock、本地Mock Server | | 🧪 可测试 | 统一接口便于单元测试 | | 📝 可维护 | 目录清晰,职责分离 | --- ## 附录 ### 相关文档 - [项目特定规则](../.trae/rules/project-specific-rules.md) - [AI协作协议](../.trae/rules/project-specific-rules.md#7-ai-协作协议) - [Task_Overview](../00_Business/Task_Overview.md) ### 更新记录 | 时间 | 版本 | 更新内容 | |------|------|----------| | 2026-03-19 | v1.0 | 初始版本,创建完整Mock架构设计 | --- *本文档遵循 Crawlful Hub 项目规范,所有Mock实现必须遵守"低侵入、AI安全"原则。*