五、接口调试工具
5.1 Postman
Postman 是最流行的 API 调试工具。
常用功能:
# 1. 环境变量管理
# 设置环境变量
{
{base_url}}/api/users
# 2. 集合(Collection)
# 将相关接口分组,可以批量运行测试
# 3. 测试脚本
pm.test("状态码为 200", function () {
pm.response.to.have.status(200);
});
pm.test("返回数据包含用户", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.data).to.be.an('array');
});
# 4. 前置脚本(如动态生成 token)
pm.environment.set("timestamp", Date.now());
# 5. 导入/导出
# 导出为 JSON,可以和团队共享
5.2 Apifox(国内推荐)
Apifox = Postman + Swagger + Mock + JMeter,一体化工具。
核心优势:
接口文档、调试、Mock、测试一体化
支持自动生成前端/后端代码
支持 OpenAPI(Swagger)导入导出
团队协作,实时同步
5.3 Swagger(OpenAPI)
Swagger 是最流行的 API 文档规范。
# openapi.yaml
openapi: 3.0.0
info:
title: 用户管理 API
version: 1.0.0
paths:
/users/{id}:
get:
summary: 获取用户信息
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
// Node.js 中使用 Swagger(nestjs)
@Get(':id')
@ApiOperation({ summary: '获取用户' })
@ApiResponse({ status: 200, description: '成功', type: UserDto })
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
六、前后端联调实战
6.1 完整的登录流程
后端实现(Node.js + Express)
// server/routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const router = express.Router();
// 用户登录
router.post('/login', async (req, res) => {
const { username, password } = req.body;
// 1. 参数校验
if (!username || !password) {
return res.status(400).json({
code: 400,
message: '用户名和密码不能为空'
});
}
// 2. 查询用户
const user = await db.findUserByUsername(username);
if (!user) {
return res.status(400).json({
code: 40001,
message: '用户不存在'
});
}
// 3. 验证密码
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(400).json({
code: 40002,
message: '密码错误'
});
}
// 4. 生成 token
const token = jwt.sign(
{ userId: user.id, username: user.username },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
// 5. 返回结果
res.json({
code: 200,
message: '登录成功',
data: {
token,
userId: user.id,
username: user.username,
avatar: user.avatar
}
});
});
// 验证 token(中间件)
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({
code: 401,
message: '未提供 token'
});
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({
code: 401,
message: 'token 无效或已过期'
});
}
};
// 获取当前用户信息(需要认证)
router.get('/me', authMiddleware, async (req, res) => {
const user = await db.findUserById(req.user.userId);
res.json({
code: 200,
data: {
id: user.id,
username: user.username,
email: user.email
}
});
});
module.exports = router;
前端实现(React)
// pages/Login.jsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { http } from '@/utils/request';
function Login() {
const navigate = useNavigate();
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
// 发送登录请求
const data = await http.post('/auth/login', formData);
// 保存 token
localStorage.setItem('token', data.token);
localStorage.setItem('userInfo', JSON.stringify(data));
// 跳转到首页
navigate('/dashboard');
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="用户名"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
/>
<input
type="password"
placeholder="密码"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
/>
{error && <div className="error">{error}</div>}
<button type="submit" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
);
}
6.2 处理异步状态
// hooks/useRequest.js
import { useState, useCallback } from 'react';
export function useRequest(apiFunc, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const execute = useCallback(async (...args) => {
setLoading(true);
setError(null);
try {
const result = await apiFunc(...args);
setData(result);
options.onSuccess?.(result);
return result;
} catch (err) {
setError(err);
options.onError?.(err);
throw err;
} finally {
setLoading(false);
}
}, [apiFunc, options]);
return { data, loading, error, execute };
}
// 使用
function UserProfile({ userId }) {
const { data: user, loading, error, execute: fetchUser } = useRequest(
userApi.getUserInfo
);
useEffect(() => {
fetchUser(userId);
}, [userId]);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <div>{user?.name}</div>;
}
6.3 请求重试机制
// utils/retry.js
export async function requestWithRetry(
requestFn,
maxRetries = 3,
delay = 1000
) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
lastError = error;
// 不重试 4xx 错误(客户端错误)
if (error.response?.status >= 400 && error.response?.status < 500) {
throw error;
}
// 最后一次重试不再等待
if (i < maxRetries - 1) {
// 指数退避:1s, 2s, 4s...
const waitTime = delay * Math.pow(2, i);
console.log(`第 ${i + 1} 次失败,${waitTime}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
}
throw lastError;
}
// 使用
const user = await requestWithRetry(() => fetchUser(userId));
6.4 并发请求控制
// 1. 并行请求(Promise.all)
const [users, orders, products] = await Promise.all([
userApi.getUserList(),
orderApi.getOrderList(),
productApi.getProductList()
]);
// 2. 竞速请求(Promise.race)
const result = await Promise.race([
fetchUser(userId),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), 5000)
)
]);
// 3. 限流请求(控制并发数)
async function limitedRequest(urls, limit = 3) {
const results = [];
const executing = [];
for (const url of urls) {
const promise = fetch(url).then(res => res.json());
results.push(promise);
if (urls.length >= limit) {
const e = promise.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}