学习目标:掌握 Mock 数据配置方案、mockWhiteList 实现、端口动态配置、部分接口 Mock 部分接口真实请求的实现。
Mock 数据配置需求场景:
│
├── 开发阶段
│ ├── 后端接口未开发完成
│ ├── 前端需要模拟数据进行开发
│ ├── 前后端并行开发
│ └── 提高开发效率
│
├── 接口迁移阶段
│ ├── 部分接口已开发完成
│ ├── 部分接口还在开发中
│ ├── 需要部分 Mock 部分真实
│ └── 渐进式迁移
│
└── 环境切换
├── 开发环境使用 Mock
├── 测试环境使用真实接口
└── 生产环境使用真实接口Mock 配置核心问题:
│
├── 问题一:全量 Mock vs 部分 Mock
│ ├── 全量 Mock:所有接口都使用 Mock 数据
│ ├── 部分 Mock:部分接口 Mock,部分接口真实
│ └── 需求:灵活控制哪些接口走 Mock
│
├── 问题二:环境切换
│ ├── 开发环境:可能需要 Mock
│ ├── 测试环境:不需要 Mock
│ └── 需求:通过环境变量控制
│
└── 问题三:接口白名单
├── 哪些接口走 Mock
├── 哪些接口走真实后端
└── 需求:配置化管理| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 方案一:全局开关 | 实现简单 | 不够灵活,只能全量 Mock 或全量真实 | 接口全部未开发或全部开发完成 |
| 方案二:接口白名单 | 灵活可控 | 需要配置白名单 | 部分接口 Mock 部分接口真实 |
| 方案三:按环境切换 | 自动化 | 需要多个环境配置文件 | 多环境部署 |
方案一实现思路:
│
├── 第一步:添加环境变量
│ └── .env.local 中添加 MOCK=true
│
├── 第二步:修改 middleware
│ └── 判断 process.env.MOCK === 'true'
│
├── 第三步:控制 Mock 逻辑
│ ├── true:走 Mock 逻辑
│ └── false:走真实接口
│
└── 第四步:测试验证
├── MOCK=true:返回 Mock 数据
└── MOCK=false:返回真实数据环境变量配置:
# .env.local
MOCK=true
BASE_URL=http://localhost:3000
PORT=3010Middleware 实现:
// middleware.ts
export default defineNuxtConfig({
srcDir: 'src/',
nitro: {
externals: {
inline: ['protobufjs'],
},
},
hooks: {
'render:setupMiddleware'(app) {
app.use(async (req, res, next) => {
const url = req.url || ''
// 判断是否开启 Mock
if (process.env.MOCK === 'true') {
// 所有接口都走 Mock 逻辑
if (url.startsWith('/api')) {
// Mock 处理逻辑
const mockData = getMockData(url)
res.end(JSON.stringify(mockData))
return
}
}
// 否则走真实接口
next()
})
},
},
})方案一优缺点分析:
│
├── 优点
│ ├── 实现简单
│ ├── 配置简单
│ └── 全局控制
│
└── 缺点
├── 不够灵活
├── 只能全量 Mock 或全量真实
└── 无法部分接口 Mock方案二实现思路:
│
├── 第一步:配置白名单
│ └── .env.local 中添加 MOCK_WHITE_LIST
│
├── 第二步:解析白名单
│ └── middleware 中读取并解析为数组
│
├── 第三步:判断接口是否在白名单
│ ├── 在白名单:走真实接口
│ └── 不在白名单:走 Mock 逻辑
│
└── 第四步:测试验证
├── 白名单接口:返回真实数据
└── 非白名单接口:返回 Mock 数据第一步:配置环境变量
# .env.local
# Mock 白名单:这些接口不走 Mock,走真实接口
MOCK_WHITE_LIST=/api/courses,/api/users
# 后端接口地址
BASE_URL=http://localhost:3000
# 本地服务端口
PORT=3010第二步:Middleware 实现白名单逻辑
// middleware.ts
export default defineNuxtConfig({
srcDir: 'src/',
nitro: {
externals: {
inline: ['protobufjs'],
},
},
hooks: {
'render:setupMiddleware'(app) {
app.use(async (req, res, next) => {
const url = req.url || ''
// 只处理 API 请求
if (!url.startsWith('/api')) {
next()
return
}
// 获取白名单
const whiteList = (process.env.MOCK_WHITE_LIST || '').split(',')
// 提取 URL 路径(去除 Query 参数)
const urlPath = url.split('?')[0]
// 判断是否在白名单中
if (whiteList.indexOf(urlPath) !== -1) {
// 在白名单中,走真实接口
next()
} else {
// 不在白名单中,走 Mock 逻辑
const mockData = getMockData(urlPath)
res.end(JSON.stringify(mockData))
}
})
},
},
})第三步:端口动态配置
# .env.local
PORT=3010
BASE_URL=http://localhost:3000
MOCK_WHITE_LIST=/api/courses// nuxt.config.ts
export default defineNuxtConfig({
devServer: {
// 动态读取端口,默认 3000
port: parseInt(process.env.PORT || '3000', 10),
},
})第四步:Mock 数据请求到本地端口
// middleware.ts(完整版)
import { getMockData } from './mock'
export default defineNuxtConfig({
srcDir: 'src/',
nitro: {
externals: {
inline: ['protobufjs'],
},
},
hooks: {
'render:setupMiddleware'(app) {
app.use(async (req, res, next) => {
const url = req.url || ''
// 只处理 API 请求
if (!url.startsWith('/api')) {
next()
return
}
// 获取白名单
const whiteList = (process.env.MOCK_WHITE_LIST || '').split(',')
// 提取 URL 路径(去除 Query 参数)
const urlPath = url.split('?')[0]
// 判断是否在白名单中
if (whiteList.indexOf(urlPath) !== -1) {
// 在白名单中,走真实接口
next()
} else {
// 不在白名单中,走 Mock 逻辑
// Mock 数据请求到本地端口
const port = process.env.PORT || '3000'
const mockUrl = `http://localhost:${port}${url}`
try {
const response = await fetch(mockUrl)
const data = await response.json()
res.end(JSON.stringify(data))
} catch (error) {
res.statusCode = 500
res.end(JSON.stringify({ error: 'Mock data fetch failed' }))
}
}
})
},
},
})接口白名单工作流程:
│
├── 第一步:前端发起请求
│ └── GET /api/courses
│
├── 第二步:Middleware 拦截
│ └── 提取 URL 路径:/api/courses
│
├── 第三步:读取白名单
│ └── whiteList = ['/api/courses', '/api/users']
│
├── 第四步:判断是否在白名单
│ │
│ ├── 在白名单(/api/courses)
│ │ ├── 走真实接口
│ │ ├── 请求:http://localhost:3000/api/v1/courses
│ │ └── 返回真实数据
│ │
│ └── 不在白名单(/api/home)
│ ├── 走 Mock 逻辑
│ ├── 请求:http://localhost:3010/api/home
│ └── 返回 Mock 数据
│
└── 第五步:响应数据给前端
└── 前端收到数据并渲染配置说明:
# .env.local
# Mock 白名单配置
# 格式:接口路径,多个用逗号分隔
# 这些接口将走真实后端,不走 Mock
# 示例一:单个接口
MOCK_WHITE_LIST=/api/courses
# 示例二:多个接口
MOCK_WHITE_LIST=/api/courses,/api/users,/api/posts
# 示例三:空字符串(所有接口都走 Mock)
MOCK_WHITE_LIST=
# 示例四:不配置(默认所有接口走 Mock)
# 不添加 MOCK_WHITE_LIST 配置项配置解析逻辑:
// 正确的解析方式
const whiteList = (process.env.MOCK_WHITE_LIST || '').split(',')
// 结果:
// MOCK_WHITE_LIST='/api/courses' → ['/api/courses']
// MOCK_WHITE_LIST='/api/courses,/api/users' → ['/api/courses', '/api/users']
// MOCK_WHITE_LIST='' → ['']
// 不配置 → ['']
// 判断逻辑
if (whiteList.indexOf(urlPath) !== -1) {
// 在白名单中
}端口动态配置需求:
│
├── 问题:硬编码端口不灵活
│ ├── nuxt.config.ts 中端口写死为 3010
│ ├── 不同开发者可能使用不同端口
│ └── 不同环境可能使用不同端口
│
├── 解决:从环境变量读取
│ ├── .env.local 中配置 PORT
│ ├── nuxt.config.ts 动态读取
│ └── 提供默认值
│
└── 好处:
├── 灵活配置
├── 不同环境不同端口
└── 避免端口冲突环境变量配置:
# .env.local
# 本地服务端口
PORT=3010
# 后端接口地址
BASE_URL=http://localhost:3000
# Mock 白名单
MOCK_WHITE_LIST=/api/coursesNuxt 配置:
// nuxt.config.ts
export default defineNuxtConfig({
devServer: {
// 方式一:直接读取,提供默认值
port: parseInt(process.env.PORT || '3000', 10),
// 方式二:更安全的写法
// port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
},
})parseInt 详解:
// parseInt(string, radix)
// radix:进制,通常使用 10(十进制)
// 示例
parseInt('3010', 10) // 3010
parseInt('3010') // 3010(不推荐,可能产生意外结果)
parseInt('abc', 10) // NaN
// 安全写法
const port = parseInt(process.env.PORT || '3000', 10)
// 更安全的写法(处理 NaN 情况)
const port = parseInt(process.env.PORT || '3000', 10) || 3000端口配置最佳实践:
│
├── 1. 使用环境变量
│ ├── .env.local 配置 PORT
│ ├── nuxt.config.ts 读取 PORT
│ └── 提供默认值
│
├── 2. 端口范围选择
│ ├── 开发环境:3000-9999
│ ├── 避免系统保留端口:0-1023
│ └── 避免常用服务端口:80, 443, 3306, 5432
│
├── 3. 常见端口约定
│ ├── 前端:3000, 3001, 3010, 8080
│ ├── 后端:3000, 4000, 5000, 8000
│ └── 数据库:3306 (MySQL), 5432 (PostgreSQL)
│
└── 4. 端口冲突解决
├── 查看端口占用:lsof -i :3000
├── 杀死进程:kill -9 <PID>
└── 更换端口:修改 PORT 环境变量前端项目/
├── middleware.ts # Middleware 配置
├── nuxt.config.ts # Nuxt 配置(端口配置)
├── .env # 默认环境变量
├── .env.local # 本地环境变量
│ ├── PORT=3010
│ ├── BASE_URL=http://localhost:3000
│ └── MOCK_WHITE_LIST=/api/courses
├── server/
│ └── api/
│ ├── courses/
│ │ └── get.ts # 课程接口(走真实后端)
│ └── home/
│ └── get.ts # 首页接口(走 Mock)
└── mock/
└── index.ts # Mock 数据1. 环境变量配置
# .env.local
# ========================================
# 本地开发环境配置
# ========================================
# 本地服务端口
PORT=3010
# 后端接口地址(NestJS)
BASE_URL=http://localhost:3000
# Mock 白名单(这些接口不走 Mock,走真实后端)
# 格式:接口路径,多个用逗号分隔
# 示例:/api/courses,/api/users,/api/posts
MOCK_WHITE_LIST=/api/courses2. Nuxt 配置
// nuxt.config.ts
export default defineNuxtConfig({
// 开发服务器配置
devServer: {
// 动态读取端口,默认 3000
port: parseInt(process.env.PORT || '3000', 10),
},
})3. Middleware 完整实现
// middleware.ts
import { getMockData } from './mock'
export default defineNuxtConfig({
srcDir: 'src/',
nitro: {
externals: {
inline: ['protobufjs'],
},
},
hooks: {
'render:setupMiddleware'(app) {
app.use(async (req, res, next) => {
const url = req.url || ''
// 只处理 API 请求
if (!url.startsWith('/api')) {
next()
return
}
// 提取 URL 路径(去除 Query 参数)
const urlPath = url.split('?')[0]
// 获取白名单
const whiteList = (process.env.MOCK_WHITE_LIST || '').split(',')
// 判断是否在白名单中
if (whiteList.indexOf(urlPath) !== -1) {
// 在白名单中,走真实接口
console.log(`[真实接口] ${urlPath} -> 走 NestJS 后端`)
next()
} else {
// 不在白名单中,走 Mock 逻辑
console.log(`[Mock 接口] ${urlPath} -> 走 Mock 数据`)
// Mock 数据请求到本地端口
const port = process.env.PORT || '3000'
const mockUrl = `http://localhost:${port}${url}`
try {
const response = await fetch(mockUrl)
const data = await response.json()
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(data))
} catch (error) {
console.error('Mock data fetch failed:', error)
res.statusCode = 500
res.end(JSON.stringify({ error: 'Mock data fetch failed' }))
}
}
})
},
},
})4. Server API 实现(真实接口)
// server/api/courses/get.ts
export default defineEventHandler(async (event) => {
// 获取 Query 参数
const query = getQuery(event)
// 发起请求到后端(NestJS)
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'GET',
query,
})
return data
})5. Mock 数据实现
// mock/index.ts
export const getMockData = (url: string) => {
// 根据不同的 URL 返回不同的 Mock 数据
if (url === '/api/home') {
return {
code: 200,
data: {
banner: [
{ id: 1, title: 'Banner 1', image: '/banner1.jpg' },
{ id: 2, title: 'Banner 2', image: '/banner2.jpg' },
],
recommend: [
{ id: 1, title: '推荐课程 1' },
{ id: 2, title: '推荐课程 2' },
],
},
}
}
// 默认返回空数据
return {
code: 404,
message: 'Mock data not found',
}
}测试场景一:白名单接口(走真实后端)
# 请求
GET http://localhost:3010/api/courses
# 日志输出
[真实接口] /api/courses -> 走 NestJS 后端
# 响应数据(来自 NestJS)
{
"code": 200,
"data": [
{ "id": 11, "name": "课程 1" },
{ "id": 12, "name": "课程 2" }
]
}测试场景二:非白名单接口(走 Mock)
# 请求
GET http://localhost:3010/api/home
# 日志输出
[Mock 接口] /api/home -> 走 Mock 数据
# 响应数据(来自 Mock)
{
"code": 200,
"data": {
"banner": [...],
"recommend": [...]
}
}测试场景三:修改白名单配置
# 修改 .env.local
MOCK_WHITE_LIST=/api/courses,/api/users
# 重启项目
npm run dev
# 测试 /api/courses
GET http://localhost:3010/api/courses
# 结果:走真实后端
# 测试 /api/users
GET http://localhost:3010/api/users
# 结果:走真实后端
# 测试 /api/home
GET http://localhost:3010/api/home
# 结果:走 Mock问题描述:配置了 MOCK_WHITE_LIST,但白名单不生效,所有接口都走 Mock。
原因分析:
// 错误写法:split 前没有处理空值
const whiteList = process.env.MOCK_WHITE_LIST.split(',')
// 如果 MOCK_WHITE_LIST 未配置,会报错
// 正确写法:提供默认值
const whiteList = (process.env.MOCK_WHITE_LIST || '').split(',')解决方案:
// 方案一:提供默认值
const whiteList = (process.env.MOCK_WHITE_LIST || '').split(',')
// 方案二:判断是否存在
const whiteList = process.env.MOCK_WHITE_LIST
? process.env.MOCK_WHITE_LIST.split(',')
: []
// 方案三:过滤空字符串
const whiteList = (process.env.MOCK_WHITE_LIST || '')
.split(',')
.filter(item => item.trim() !== '')问题描述:修改了 .env.local 中的 PORT,但端口没有变化。
原因分析:
端口配置不生效原因:
│
├── 原因一:未重启项目
│ └── 环境变量修改后需要重启项目
│
├── 原因二:parseInt 转换错误
│ └── parseInt(process.env.PORT) 可能返回 NaN
│
└── 原因三:环境变量未加载
└── 未使用 --dotenv 指定环境变量文件解决方案:
# 方案一:重启项目
# 修改 .env.local 后,重启项目
npm run dev
# 方案二:指定环境变量文件
# package.json
{
"scripts": {
"dev": "nuxt dev --dotenv .env.local"
}
}
# 方案三:安全的端口配置
# nuxt.config.ts
export default defineNuxtConfig({
devServer: {
port: parseInt(process.env.PORT || '3000', 10) || 3000,
},
})问题描述:URL 中包含 Query 参数,导致白名单匹配失败。
原因分析:
// 问题:URL 包含 Query 参数
const url = '/api/courses?page=1&size=10'
const whiteList = ['/api/courses']
whiteList.indexOf(url) // -1(不匹配)解决方案:
// 方案一:提取 URL 路径(去除 Query 参数)
const urlPath = url.split('?')[0]
whiteList.indexOf(urlPath) // 0(匹配)
// 方案二:使用 includes 判断
whiteList.some(path => urlPath.includes(path))
// 方案三:使用正则匹配
const regex = new RegExp(whiteList.join('|'))
regex.test(urlPath)问题描述:非白名单接口返回 404 错误。
原因分析:
Mock 数据 404 原因:
│
├── 原因一:Mock 文件未创建
│ └── 需要在 mock/index.ts 中添加对应的 Mock 数据
│
├── 原因二:Mock 路径配置错误
│ └── mockUrl 配置错误
│
└── 原因三:Mock 服务未启动
└── Mock 数据服务未正常运行解决方案:
// 方案一:完善 Mock 数据
// mock/index.ts
export const getMockData = (url: string) => {
const mockDataMap = {
'/api/home': {
code: 200,
data: { banner: [], recommend: [] }
},
'/api/users': {
code: 200,
data: []
},
}
return mockDataMap[url] || { code: 404, message: 'Mock data not found' }
}
// 方案二:正确配置 Mock URL
const port = process.env.PORT || '3000'
const mockUrl = `http://localhost:${port}${url}`
// 方案三:添加错误处理
try {
const response = await fetch(mockUrl)
if (!response.ok) {
throw new Error('Mock data fetch failed')
}
const data = await response.json()
res.end(JSON.stringify(data))
} catch (error) {
res.statusCode = 500
res.end(JSON.stringify({ error: 'Mock data fetch failed' }))
}Mock 配置最佳实践:
│
├── 1. 使用接口白名单
│ ├── 配置 MOCK_WHITE_LIST
│ ├── 灵活控制哪些接口走 Mock
│ └── 便于渐进式迁移
│
├── 2. 动态端口配置
│ ├── 使用环境变量 PORT
│ ├── 提供默认值
│ └── 避免端口冲突
│
├── 3. 环境变量管理
│ ├── 使用 .env.local 配置本地环境
│ ├── 使用 .env.production 配置生产环境
│ └── 不要将 .env.local 提交到 Git
│
├── 4. Mock 数据管理
│ ├── 统一放在 mock 目录
│ ├── 根据 URL 返回不同 Mock 数据
│ └── Mock 数据要符合真实接口格式
│
└── 5. 日志调试
├── 打印接口类型(Mock 或真实)
├── 打印请求 URL
└── 便于调试和问题定位环境变量配置最佳实践:
│
├── 1. 文件命名规范
│ ├── .env:默认环境变量(所有环境共享)
│ ├── .env.local:本地环境变量(优先级最高)
│ ├── .env.development:开发环境变量
│ └── .env.production:生产环境变量
│
├── 2. 环境变量命名规范
│ ├── 使用大写字母和下划线
│ ├── 语义化命名
│ └── 添加注释说明
│
├── 3. 环境变量优先级
│ ├── .env.local > .env.[environment] > .env
│ ├── 系统环境变量 > 文件环境变量
│ └── 后加载的会覆盖先加载的
│
└── 4. 安全性
├── 不要在 .env 中存储敏感信息
├── 使用 .env.example 作为模板
└── 将 .env.local 添加到 .gitignore接口迁移最佳实践:
│
├── 1. 渐进式迁移
│ ├── 先迁移简单接口
│ ├── 逐步迁移复杂接口
│ └── 最后迁移核心接口
│
├── 2. 白名单管理
│ ├── 迁移完成的接口加入白名单
│ ├── 未迁移的接口保持 Mock
│ └── 定期更新白名单
│
├── 3. 测试验证
│ ├── 每迁移一个接口都要测试
│ ├── 对比 Mock 数据和真实数据
│ └── 确保数据格式一致
│
└── 4. 文档更新
├── 记录哪些接口已迁移
├── 记录迁移过程中的问题
└── 更新接口文档| 命令 | 说明 |
|---|---|
npm run dev | 启动开发服务器(加载 .env.development) |
npm run dev:local | 启动本地测试服务器(加载 .env.local) |
npm run build | 构建生产版本(加载 .env.production) |
npm run start | 运行生产版本 |
| 命令 | 说明 |
|---|---|
lsof -i :3000 | 查看 3000 端口占用情况 |
kill -9 <PID> | 杀死占用端口的进程 |
netstat -an | grep 3000 | 查看 3000 端口状态 |
npx kill-port 3000 | 使用工具杀死端口 |
| 方法 | 说明 | 示例 |
|---|---|---|
process.env.VAR_NAME | 读取环境变量 | process.env.PORT |
process.env.VAR_NAME || 'default' | 提供默认值 | process.env.PORT || '3000' |
parseInt(str, 10) | 转换为整数 | parseInt('3010', 10) |
NestJS Mock 数据配置核心要点:
│
├── 1. Mock 配置方案
│ ├── 方案一:全局开关(简单但不灵活)
│ ├── 方案二:接口白名单(灵活可控)
│ └── 选择:推荐使用接口白名单方案
│
├── 2. 接口白名单实现
│ ├── 配置 MOCK_WHITE_LIST
│ ├── 解析为数组
│ ├── 判断 URL 是否在白名单
│ └── 分别处理 Mock 和真实接口
│
├── 3. 端口动态配置
│ ├── 使用环境变量 PORT
│ ├── parseInt 转换
│ └── 提供默认值
│
├── 4. 环境变量管理
│ ├── .env.local 本地配置
│ ├── .env.production 生产配置
│ └── 不同环境不同配置
│
├── 5. Mock 数据管理
│ ├── 统一放在 mock 目录
│ ├── 根据 URL 返回不同数据
│ └── 格式符合真实接口
│
└── 6. 接口迁移流程
├── 配置白名单
├── 实现真实接口
├── 测试验证
└── 更新白名单| 知识点 | 重要程度 | 说明 |
|---|---|---|
| 接口白名单 | 必须掌握 | Mock 配置核心 |
| 端口动态配置 | 必须掌握 | 避免端口冲突 |
| 环境变量管理 | 必须掌握 | 不同环境配置 |
| Middleware 实现 | 必须掌握 | 请求拦截逻辑 |
| Mock 数据管理 | 重要 | Mock 数据组织 |
学习路径规划:
│
├── 第一阶段:理解概念(1 天)
│ ├── 理解 Mock 配置的两种方案
│ ├── 理解接口白名单的作用
│ └── 理解端口动态配置的意义
│
├── 第二阶段:实践操作(2-3 天)
│ ├── 配置环境变量
│ ├── 实现 Middleware
│ ├── 测试接口白名单
│ └── 测试 Mock 和真实接口切换
│
└── 第三阶段:深入应用(持续)
├── 复杂业务场景
├── 性能优化
└── 团队协作规范# .env.local
# ========================================
# 本地开发环境配置
# ========================================
# 本地服务端口
PORT=3010
# 后端接口地址(NestJS)
BASE_URL=http://localhost:3000
# Mock 白名单(这些接口不走 Mock,走真实后端)
# 格式:接口路径,多个用逗号分隔
# 示例:/api/courses,/api/users,/api/posts
MOCK_WHITE_LIST=/api/courses# .env.production
# ========================================
# 生产环境配置
# ========================================
# 生产环境端口
PORT=3000
# 生产环境后端接口地址
BASE_URL=https://api.production.com
# 生产环境不使用 Mock
MOCK_WHITE_LIST=// nuxt.config.ts
export default defineNuxtConfig({
// 开发服务器配置
devServer: {
// 动态读取端口,默认 3000
port: parseInt(process.env.PORT || '3000', 10) || 3000,
},
})// middleware.ts
import { getMockData } from './mock'
export default defineNuxtConfig({
srcDir: 'src/',
nitro: {
externals: {
inline: ['protobufjs'],
},
},
hooks: {
'render:setupMiddleware'(app) {
app.use(async (req, res, next) => {
const url = req.url || ''
// 只处理 API 请求
if (!url.startsWith('/api')) {
next()
return
}
// 提取 URL 路径(去除 Query 参数)
const urlPath = url.split('?')[0]
// 获取白名单(过滤空字符串)
const whiteList = (process.env.MOCK_WHITE_LIST || '')
.split(',')
.filter(item => item.trim() !== '')
// 判断是否在白名单中
if (whiteList.indexOf(urlPath) !== -1) {
// 在白名单中,走真实接口
console.log(`[真实接口] ${urlPath} -> 走 NestJS 后端`)
next()
} else {
// 不在白名单中,走 Mock 逻辑
console.log(`[Mock 接口] ${urlPath} -> 走 Mock 数据`)
// Mock 数据请求到本地端口
const port = process.env.PORT || '3000'
const mockUrl = `http://localhost:${port}${url}`
try {
const response = await fetch(mockUrl)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(data))
} catch (error) {
console.error('Mock data fetch failed:', error)
res.statusCode = 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({
code: 500,
message: 'Mock data fetch failed',
error: String(error)
}))
}
}
})
},
},
})// mock/index.ts
interface MockData {
code: number
message?: string
data?: any
}
/**
* 根据 URL 返回 Mock 数据
* @param url 请求 URL
* @returns Mock 数据
*/
export const getMockData = (url: string): MockData => {
// Mock 数据映射表
const mockDataMap: Record<string, MockData> = {
// 首页数据
'/api/home': {
code: 200,
data: {
banner: [
{ id: 1, title: '精品微课', image: '/banner1.jpg', link: '/courses' },
{ id: 2, title: '学习计划', image: '/banner2.jpg', link: '/plans' },
],
recommend: [
{ id: 1, title: '推荐课程 1', cover: '/course1.jpg', price: 99 },
{ id: 2, title: '推荐课程 2', cover: '/course2.jpg', price: 199 },
],
categories: [
{ id: 11, name: '前端开发', count: 120 },
{ id: 12, name: '后端开发', count: 80 },
],
},
},
// 用户数据
'/api/users': {
code: 200,
data: [
{ id: 1, username: 'user1', email: 'user1@example.com' },
{ id: 2, username: 'user2', email: 'user2@example.com' },
],
},
// 文章数据
'/api/posts': {
code: 200,
data: [
{ id: 1, title: '文章 1', content: '内容 1', author: '作者 1' },
{ id: 2, title: '文章 2', content: '内容 2', author: '作者 2' },
],
},
}
// 返回对应的 Mock 数据,如果不存在则返回 404
return mockDataMap[url] || {
code: 404,
message: 'Mock data not found',
data: null,
}
}// server/api/courses/get.ts
export default defineEventHandler(async (event) => {
// 获取 Query 参数
const query = getQuery(event)
// 发起请求到后端(NestJS)
const data = await $fetch(`${process.env.BASE_URL}/api/v1/courses`, {
method: 'GET',
query,
})
return data
})// server/api/users/get.ts
export default defineEventHandler(async (event) => {
// 获取 Query 参数
const query = getQuery(event)
// 发起请求到后端(NestJS)
const data = await $fetch(`${process.env.BASE_URL}/api/v1/users`, {
method: 'GET',
query,
})
return data
})使用 Mock.js 生成 Mock 数据:
// mock/index.ts
import Mock from 'mockjs'
export const getMockData = (url: string) => {
if (url === '/api/users') {
// 使用 Mock.js 生成随机数据
return Mock.mock({
code: 200,
'data|10': [{
'id|+1': 1,
'username': '@cname',
'email': '@email',
'avatar': '@image("100x100")',
'createTime': '@datetime',
}]
})
}
// 其他 Mock 数据...
}根据接口文档自动生成 Mock 数据:
// mock/auto.ts
import { OpenAPI } from 'openapi-types'
/**
* 根据 OpenAPI 文档自动生成 Mock 数据
*/
export const generateMockFromOpenAPI = (doc: OpenAPI.Document) => {
const mockData = {}
// 遍历所有接口
for (const [path, methods] of Object.entries(doc.paths)) {
for (const [method, spec] of Object.entries(methods)) {
const key = `${method.toUpperCase()} ${path}`
mockData[key] = generateMockFromSchema(spec.responses['200'])
}
}
return mockData
}团队协作 Mock 规范:
│
├── 1. Mock 数据文件组织
│ ├── mock/
│ │ ├── index.ts # 入口文件
│ │ ├── home.ts # 首页相关 Mock
│ │ ├── courses.ts # 课程相关 Mock
│ │ └── users.ts # 用户相关 Mock
│ └── 统一管理,便于维护
│
├── 2. Mock 数据命名规范
│ ├── 文件名:小写,用连字符分隔
│ ├── 接口名:RESTful 风格
│ └── 数据字段:驼峰命名
│
├── 3. Mock 数据更新流程
│ ├── 接口文档先行
│ ├── Mock 数据符合接口文档
│ ├── 前端基于 Mock 开发
│ └── 后端接口完成后迁移
│
└── 4. Mock 数据版本控制
├── 使用 Git 管理 Mock 数据
├── 接口变更时同步更新 Mock
└── 保留历史版本便于回滚重要提示:Mock 数据配置是前后端并行开发的重要工具,掌握接口白名单、端口动态配置、环境变量管理,对实际项目开发非常重要!推荐使用接口白名单方案,灵活可控,便于渐进式迁移!
写过很多 Module、Service、Controller,但这些都要服务跑起来之后在浏览器里访问对应的 url,通过 get 或者 post 的方式传参来测试。
这个还是挺麻烦的,能不能像 node 的 repl 那样,直接在控制台测试呢?
repl 是 read-eval-print-loop,也就是这个:
Nest 能不能这样来测试呢?
可以的,Nest 支持 repl 模式。
创建个 Nest 项目:
nest new repl-test然后创建两个模块:
把服务跑起来:
npm run start:dev浏览器访问下:
前面都是这么测试接口的。
其实还可以用 repl 模式。
在 src 下创建个 repl.ts,写入如下内容:
import { repl } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
await repl(AppModule);
}
bootstrap();然后把服务停掉,通过这种方式跑:
npm run start:dev -- --entryFile repl这里的 --entryFile 是指定入口文件是 repl.ts
前面带了个 -- 是指后面的参数不是传给 npm run start:dev 的,要原封不动保留。
也就是会传给 nest start
当然,你直接执行 nest start 也可以:
nest start --watch --entryFile repl跑起来后,执行 debug(),会打印所有的 module 和 module 下的 controllers 和 providers。
而且,你可以 get() 来取对应的 providers 或者 controllers 调用:
get、post 方法都可以调用。
有的同学说,你这个 post 方法没有参数啊。
那加一些:
然后添加 ValidationPipe:
安装校验相关的包:
npm install class-validator class-transformer在 dto 添加约束:
import { IsEmail, IsNotEmpty } from "class-validator";
export class CreateAaaDto {
@IsNotEmpty()
aaa: string;
@IsEmail()
bbb: string;
}先正常跑下服务:
npm run start:dev然后 postman 里测试下:
可以看到,ValidationPipe 生效了。
那 repl 里是不是一样呢?
再跑下 repl 模式:
npm run start:dev -- --entryFile repl可以看到,并没有触发 pipe:
也就是说,它只是单纯的传参调用这个函数,不会解析装饰器。
所以测试 controller 的话,repl 的方式是有一些限制的。
但是测试 service 很不错:
比如测试某个项目的 UserService 的 login 方法:
就很方便。
大概知道 repl 模式是做啥的之后,过一下常用的 api:
debug() 可以查看全部的 module 或者某个 module 下的 controllers、providers:
methods() 可以查看某个 controller 或者 provider 的方法:
get() 或者 $() 可以拿到某个 controller 或者 provider 调用它的方法:
常用的 api 就这些。
此外,按住上下键可以在历史命令中导航:
但有个问题。
当你重新跑之后,这些命令历史就消失了,再按上下键也没有历史。
可以改一下 repl.ts:
import { repl } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const replServer = await repl(AppModule);
replServer.setupHistory(".nestjs_repl_history", (err) => {
if (err) {
console.error(err);
}
});
}
bootstrap();
再跑的时候也是有历史的:
其实就是 nest 会把历史命令写入文件里,下一次跑就可以用它恢复历史了:
你还可以把这个命令配到 npm scripts 里:
然后直接 npm run repl:dev 来跑。
案例代码上传了。
这节学了 nest 的 repl 模式。
repl 模式下可以直接调用 controller 或者 provider 的方法,但是它们并不会触发 pipe、interceptor 等,只是传参测试函数。
可以使用 debug() 拿到 module、controller、provider 的信息,methods() 拿到方法,然后 get() 或者 $() 拿到 controller、provider 然后调用。
repl 模式对于测试 service 或者 controller 的功能还是很有用的。
如果你的网站要支持多种语言访问,那就要做国际化。
也就是中文用户访问返回中文界面,英文用户访问返回英文界面。
如果你在外企,那可能经常要做这些,比如我在韩企的时候,要支持韩文、英文,在日企的时候,要支持日文、英文。
不只是前端要做国际化,后端也要做,不然英文用户用着英文的界面登录的时候,突然返回一个“用户不存在”的错误,是不是一脸懵逼?
本文就来学一下 Nest 如何实现国际化。
Nest 里做国际化用 nestjs-i18n 这个包:
来试一下:
nest new i18n-test安装 nestjs-i18n:
npm install --save nestjs-i18n在 AppModule 引入 I18nModule:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { I18nModule, QueryResolver } from 'nestjs-i18n';
import * as path from 'path';
@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
path: path.join(__dirname, '/i18n/'),
watch: true,
},
resolvers: [
new QueryResolver(["lang", "l"]),
]
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}默认语言是 en,然后资源包在 i18n 目录下。
resolver 也就是从哪里读取当前语言信息,这里是从 query 中读取,比如 ?lang=en、?l=zh
添加一下资源包:
i18n/en/test.json
{
"hello": "Hello World"
}i18n/zh/test.json
{
"hello": "你好世界"
}这里的国际化资源包要在 nest-cli.json 里配置下自动复制:
"assets": [
{ "include": "i18n/**/*", "watchAssets": true }
]然后改下 AppService:
import { Inject, Injectable } from '@nestjs/common';
import { I18nContext, I18nService } from 'nestjs-i18n';
@Injectable()
export class AppService {
@Inject()
i18n: I18nService;
getHello(): string {
return this.i18n.t('test.hello', { lang: I18nContext.current().lang })
}
}注入 I18nService,从资源包中取 test.hello 的值,也就是对应 test.json 里的 hello 的值,用当前的语言。
把服务跑起来:
npm run start:dev浏览器访问下:
可以看到,文案根据语言环境做了国际化。
还有其他 resolver,比如根据自定义 header、cookie、accept-language 的 header 等。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AcceptLanguageResolver, CookieResolver, HeaderResolver, I18nModule, QueryResolver } from 'nestjs-i18n';
import * as path from 'path';
@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
path: path.join(__dirname, '/i18n/'),
watch: true,
},
resolvers: [
new QueryResolver(["lang", "l"]),
new HeaderResolver(["x-custom-lang"]),
new CookieResolver(['lang']),
AcceptLanguageResolver,
]
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}试一下 cookie:
在 postman 里访问,添加一个 cookie:
再访问就变成了中文的:
有的同学可能问了,现在是用 I18nService 做的翻译,那不在 IoC 容器里的类,怎么翻译呢?
比如 dto:
它并不在 IoC 容器里,没法注入 I18nService,怎么翻译这些文案呢?
这时候可以用专门的 Pipe。
加一个模块:
nest g resource user安装 dto 验证用的包:
npm install --save class-validator class-transformer改一下 CreateUserDto:
import { IsNotEmpty, MinLength } from "class-validator";
export class CreateUserDto {
@IsNotEmpty({
message: "用户名不能为空"
})
username: string;
@IsNotEmpty({
message: '密码不能为空'
})
@MinLength(6, {
message: '密码不能少于 6 位'
})
password: string;
}校验 body 的错误需要全局启用 ValidationPipe:
app.useGlobalPipes(new ValidationPipe());访问下:
如果是英文网站,需要返回英文的错误信息,但是 dto 不在 IoC 容器里,不能注入 I18nService,怎么办呢?
这时候可以用 nestjs-i18n 提供的 I18nValidationPipe 来替换 ValidationPipe。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { I18nValidationExceptionFilter, I18nValidationPipe } from 'nestjs-i18n';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new I18nValidationPipe());
app.useGlobalFilters(new I18nValidationExceptionFilter({
detailedErrors: false
}));
await app.listen(3000);
}
bootstrap();然后把 message 改为资源的 key:
访问下:
可以看到,key 被替换成了具体的文案。
把 cookie 里的 lang 改为 en:
文案也换成了英文。
那接下来只要添加对应的资源包就可以了。
添加 i18n/zh/validate.json
{
"usernameNotEmpty": "用户名不能为空",
"passwordNotEmpty": "密码不能为空",
"passwordNotLessThan6": "密码不能少于 6 位"
}i18n/en/validate.json
{
"usernameNotEmpty": "The username cannot be empty",
"passwordNotEmpty": "Password cannot be empty",
"passwordNotLessThan6": "The password cannot be less than 6 characters"
}然后改下 dto 里的 message,换成资源的 key:
import { IsNotEmpty, MinLength } from "class-validator";
export class CreateUserDto {
@IsNotEmpty({
message: "validate.usernameNotEmpty"
})
username: string;
@IsNotEmpty({
message: 'validate.passwordNotEmpty'
})
@MinLength(6, {
message: 'validate.passwordNotLessThan6'
})
password: string;
}再次访问下:
中文环境返回中文文案、英文环境返回英文文案,这样就实现了国际化。
那如果这个密码位数不一定是 6 位呢?
文案里可以填占位符:
然后用的时候传入参数:
@MinLength(6, {
message: i18nValidationMessage("validate.passwordNotLessThan6", {
num: 88
})
})试一下:
I18nService 的 api 同样支持这个:
加一下占位符:
然后用的时候传入 args:
import { Inject, Injectable } from '@nestjs/common';
import { I18nContext, I18nService } from 'nestjs-i18n';
@Injectable()
export class AppService {
@Inject()
i18n: I18nService;
getHello(): string {
return this.i18n.t('test.hello', {
lang: I18nContext.current().lang,
args: {
name: 'guang'
}
})
}
}案例代码上传了
当你的应用需要支持多种语言环境的用户访问时,就要做国际化。
前端要做界面的国际化,后端也同样要做返回的信息的国际化。
nest 里用 nestjs-i18n 这个包,在 AppModule 里引入 I18nModule,指定资源包的路径,resolver(取 lang 配置的方式)。
然后就可以注入 I18nService,用它的 t 方法来取资源包中的文案了。
dto 的国际化需要全局启用 I18nValidationPipe 和 I18nValidationExceptionFilter,然后把 message 换成资源包的 key 就好了。
文案支持占位符,可以在资源包里写 {xxx} 然后用的时候传入 xxx 的值。
如果你做一个面向多种语言用户的网站,那么国际化功能是必不可少的。