接口联调与调试
前端与后端联调时的常见操作:请求封装、跨域处理、Mock 数据、接口调试。
Axios 封装
为什么要封装 Axios?
如果你不封装,每个组件发请求都要写一遍 axios.get()、try/catch、错误处理、token 注入...代码会非常重复。
封装的好处:
- 统一 baseURL:开发环境用
localhost:3000,生产环境用api.example.com,一处配置全局生效 - 自动携带 token:登录后每次请求自动带上 token,不用手动写
- 统一错误处理:401 自动跳登录页、500 统一提示,不用每个请求都写一遍
- 统一响应格式:后端返回
{ code, data, message },封装后直接返回data,组件里不用再解构
基础封装
js
// src/utils/request.js
import axios from 'axios' // 引入 axios 库
import { getToken, removeToken } from './auth' // 引入 token 管理函数
import router from '@/router' // 引入路由实例,用于跳转登录页
// 创建 axios 实例(axios.create 会返回一个独立的 axios 实例,不影响全局配置)
const service = axios.create({
baseURL: import.meta.env.VITE_API_URL, // 从环境变量读取 API 基础地址
timeout: 15000, // 请求超时时间:15 秒
headers: {
'Content-Type': 'application/json' // 默认请求体格式为 JSON
}
})
// 请求拦截器:在请求发送前自动执行(拦截器是 axios 的中间件机制)
service.interceptors.request.use(
config => {
const token = getToken() // 从本地存储获取 token
if (token) {
config.headers.Authorization = `Bearer ${token}` // 在请求头中携带 token
}
return config // 必须返回 config,否则请求会被阻塞
},
error => Promise.reject(error) // 请求配置出错时,直接抛出错误
)
// 响应拦截器:在收到响应后自动执行
service.interceptors.response.use(
response => {
// ⚠️ 假设后端返回 { code, data, message } 统一格式
// 如果后端直接返回数据(无 code 包装),可改为:return response.data
const { code, data, message } = response.data // 解构后端返回的统一格式
if (code === 0) return data // 业务成功(code=0),直接返回 data
// ⚠️ 成功码因后端而异,常见值:0、200、'success'、'000000' 等
// 需要与后端约定,按实际接口调整上面的判断条件
// 业务错误(code 非 0)
console.error(message || '请求失败') // 打印错误信息
return Promise.reject(new Error(message)) // 抛出错误,让调用方捕获
},
error => {
if (error.response) { // 有响应说明服务器返回了状态码
switch (error.response.status) {
case 401: // 未登录或 token 过期
removeToken() // 清除本地 token
router.push('/login') // 跳转到登录页
break
case 403: // 无权限访问
console.error('没有操作权限')
break
case 500: // 服务器内部错误
console.error('服务器异常')
break
}
}
return Promise.reject(error) // 继续抛出错误
}
)
export default service // 导出封装好的 axios 实例接口模块化
js
// src/api/user.js
import request from '@/utils/request'
export function getUserInfo() {
return request.get('/api/user/info')
}
export function login(data) {
return request.post('/api/auth/login', data)
}
export function updateUser(id, data) {
return request.put(`/api/user/${id}`, data)
}vue
<!-- 组件中使用 -->
<script setup>
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/api/user'
const user = ref(null)
onMounted(async () => {
user.value = await getUserInfo()
})
</script>环境变量切换 API 地址
不同环境(开发/测试/生产)使用不同的 API 地址,通过 .env 文件配置:
bash
# .env.development(开发环境)
VITE_API_URL=http://localhost:3000
# .env.staging(测试环境)
VITE_API_URL=https://staging-api.example.com
# .env.production(生产环境)
VITE_API_URL=https://api.example.combash
# 启动时自动读取对应环境的 .env 文件
npm run dev # 读取 .env.development
npm run build # 读取 .env.production
npm run build --mode staging # 读取 .env.staging⚠️ Vite 要求自定义环境变量必须以
VITE_开头,否则不会暴露给客户端代码。
跨域处理
什么是跨域
浏览器同源策略限制:协议 + 域名 + 端口 三者都相同才算同源
http://localhost:5173 → http://localhost:3000 ❌ 端口不同
https://a.com → https://b.com ❌ 域名不同
http://a.com:80 → https://a.com:443 ❌ 协议不同开发环境:代理
js
// vite.config.ts
import { defineConfig } from 'vite' // 引入 Vite 配置函数
export default defineConfig({
server: {
proxy: {
'/api': { // 匹配所有以 /api 开头的请求路径
target: 'http://localhost:3000', // 代理目标地址(后端服务地址)
changeOrigin: true, // 修改请求头中的 Host 为目标地址(解决跨域)
rewrite: (path) => path.replace(/^\/api/, '') // 重写路径:去掉 /api 前缀
}
}
}
})验证代理是否生效:
bash
# 1. 重启开发服务器
yarn dev
# 2. 在浏览器 Console 中发送请求
fetch('/api/user/info').then(r => r.json()).then(console.log)
# 3. 查看 Network 面板,确认请求被代理到 http://localhost:3000/user/info前端请求:/api/user/info
↓ 代理转发
后端收到:/user/info生产环境:Nginx 反向代理
nginx
location /api/ {
proxy_pass http://backend:3000/; # 末尾 / 表示替换路径:/api/user → /user
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 如果后端路径也包含 /api,改为:proxy_pass http://backend:3000;(不带末尾 /)Mock 数据
什么是 Mock?为什么要用 Mock?
Mock 就是"假数据"。当后端接口还没开发好时,前端可以用假数据先开发,不被后端阻塞。
什么时候用 Mock?
- 后端接口未就绪:前端先用假数据开发,等接口好了再替换
- 演示环境:给领导或客户演示时,需要固定的数据
- 测试边界情况:空数据、超长数据、错误状态等难以从后端获取的场景
- 离线开发:没有网络时也能正常开发
本地 Mock(开发阶段)
bash
npm install -D vite-plugin-mock mockjsjs
// mock/user.js
export default [
{
url: '/api/user/info', // Mock 接口的 URL(与真实接口一致)
method: 'get', // HTTP 请求方法
response: () => ({ // 响应函数,返回模拟数据
code: 0, // 业务状态码(0 表示成功)
data: { name: '张三', age: 25, role: 'admin' } // 模拟的用户数据
})
}
]js
// vite.config.ts
import { viteMockServe } from 'vite-plugin-mock' // 引入 Mock 插件
export default defineConfig({
plugins: [
viteMockServe({
mockPath: 'mock', // Mock 文件存放目录
localEnabled: true // 开发环境启用 Mock
})
]
})验证 Mock 是否生效:
bash
# 1. 安装依赖
npm install -D vite-plugin-mock mockjs
### 在线 Mock 平台
| 平台 | 说明 |
|------|------|
| [Mock.js](http://mockjs.com/) | 生成随机数据的库 |
| [Apifox](https://apifox.com/) | API 文档 + Mock + 调试 |
| [Postman Mock](https://www.postman.com/) | Postman 内置 Mock |
---
## 接口调试工具
### Chrome DevTools Network- 打开 F12 → Network → 选 Fetch/XHR
- 点击某个请求,查看:
- Headers:请求头、请求参数
- Payload:请求体(POST/PUT)
- Preview:响应预览(格式化 JSON)
- Response:响应原始内容
- Timing:各阶段耗时
### 接口调试快捷方式
```js
// 在 Console 中直接发请求测试
fetch('/api/user/info')
.then(r => r.json())
.then(console.log)
// POST 请求
fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'admin', password: '123456' })
}).then(r => r.json()).then(console.log)cURL 命令行调试
什么时候用 cURL?
cURL 是命令行发请求的工具,适合:
- 排除前端代码问题:如果 cURL 能正常返回数据,说明问题在前端代码
- 复现后端 bug:把请求参数复制出来,在终端里反复测试
- 自动化测试:在 CI/CD 中用 cURL 测试接口是否正常
bash
# GET 请求
curl https://api.example.com/user/info
# POST 请求
curl -X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 带 token
curl https://api.example.com/user/info \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."常见问题
请求发不出去
bash
# 检查清单
1. baseURL 是否正确
2. 代理配置是否生效(重启 dev server)
3. 是否被浏览器插件拦截(如广告拦截器)
4. 是否 CORS 错误(看 Console 报错)Cookie / Token 携带不上
js
// axios 默认不携带 cookie
axios.create({
withCredentials: true // 跨域请求携带 cookie
})
// token 方案:请求头携带
config.headers.Authorization = `Bearer ${token}`文件上传
js
// 使用 FormData
const formData = new FormData()
formData.append('file', file)
formData.append('type', 'avatar')
request.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (e) => {
const percent = Math.round((e.loaded / e.total) * 100)
console.log(`上传进度: ${percent}%`)
}
})