核心目标
正确建立通信,实现跨域支持、请求解析、统一响应、环境适配
核心配置项
1. 跨域配置(最关键!前端跨域请求必配)
前端运行在 http://localhost:8080/3000 等端口,后端运行在 http://localhost:3001 等端口,浏览器会触发「同源策略」拦截请求,必须配置 CORS(跨域资源共享)。
1.1 安装依赖
npm install cors --save
1.2 在app.js中配置
const express = require('express');
const cors = require('cors');
const app = express();
// 基础跨域配置(允许所有前端域名访问,开发环境可用)
app.use(cors());
// ------------------------进阶---------------------------
// 生产环境精准配置(仅允许指定前端域名)
// app.use(cors({
// origin: [
// 'https://your-frontend-domain.com', // 生产环境前端域名
// 'http://localhost:8080' // 开发环境前端本地地址
// ],
// credentials: true, // 允许前端携带Cookie(如登录态)
// methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的请求方法
// allowedHeaders: ['Content-Type', 'Authorization'] // 允许的请求头
// }));
2. 请求体解析(前端传参必配)
确保后端能正确解析前端传递的 JSON/表单/文件 等参数,需配置 Express 内置中间件:
// 解析 JSON 格式请求体(前端 axios.post 传 JSON 必配)
app.use(express.json());
// 解析表单格式请求体(如前端 form 提交、axios 传 application/x-www-form-urlencoded)
app.use(express.urlencoded());
3.端口与地址配置(前端能访问到后端)
确保后端监听的地址和端口对前端可见
进阶配置(生产环境必做)
1. 接口前缀统一(简化前端请求路径)
所有接口统一前缀(如 /api),前端无需拼接零散路径:
// 手动挂载(适合少量路由)
app.use('/api', userRouter);
app.use('/api', goodsRouter);
// 自动挂载使用之前封装的自动挂载方法
2. 错误处理(避免前端收到无意义的报错)
配置全局错误中间件,捕获所有未处理的异常,返回统一格式:
全局错误处理中间件(必须放在所有路由之后)
app.use((err, req, res, next) => {
console.error('全局异常:', err.stack); // 后端记录错误日志
// 给前端返回统一错误格式
error(res, '服务器内部错误', 500, err);
});
// 404 处理(前端访问不存在的接口)
app.use((req, res) => {
error(res, '接口不存在', 404);
});
3. 请求头与认证(如 Token 验证)
若前端需要携带登录态(如 JWT Token),配置允许认证头,并封装认证中间件:
|--------------------|-----------------------------------|---------------------------------------|
| 依赖名称 | 安装命令 | 核心作用 |
| jsonwebtoken | npm install jsonwebtoken --save | 生成 JWT Token + 验证 Token 合法性(核心) |
| express-jwt (可选) | npm install express-jwt --save | 简化 Express 中 JWT 认证中间件的编写(替代手动验证) |
| crypto-js | npm install crypto-js --save | 对敏感信息(如用户密码)进行加密(如 MD5/SHA256),避免明文存储 |
// 跨域配置中允许 Authorization 头(已在上面配置)
// 认证中间件(middleware/auth.js)
const jwt = require('jsonwebtoken');
const { error } = require('../utils/response');
const authMiddleware = (req, res, next) => {
// 从请求头获取 Token
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return error(res, '未登录,请先登录', 401);
}
try {
// 验证 Token
const user = jwt.verify(token, process.env.JWT_SECRET);
req.user = user; // 挂载用户信息到 req,后续接口可直接使用
next();
} catch (err) {
error(res, 'Token 过期或无效', 401);
}
};
// 在需要认证的路由中使用
const orderRouter = express.Router();
orderRouter.use(authMiddleware); // 所有订单接口需要登录
orderRouter.post('/', async (req, res) => {
console.log('当前登录用户:', req.user.id); // 直接使用认证后的用户信息
});
express-jwt(简化中间件)
替代手动编写 Token 验证逻辑,直接通过中间件拦截未认证请求:
const expressJwt = require('express-jwt');
const dotenv = require('dotenv');
dotenv.config();
// 全局认证中间件(排除登录/注册接口)
app.use(
expressJwt({
secret: process.env.JWT_SECRET,
algorithms: ['HS256'] // 指定加密算法(必须和生成 Token 时一致)
}).unless({
path: ['/api/user/login', '/api/user/register'] // 无需认证的接口
})
);
// Token 验证失败的全局处理
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
return error(res, 'Token 过期或无效', 401);
}
next(err);
});
crypto-js(密码加密)
用户注册 / 登录时,加密密码后存储到数据库(避免明文):
const CryptoJS = require('crypto-js');
// 加密密码(如 MD5)
const encryptPassword = (password) => {
// 加盐(salt)提升安全性,salt 需配置在环境变量中
return CryptoJS.MD5(password + process.env.PWD_SALT).toString();
};
// 验证密码(登录时对比加密后的密码)
const checkPassword = (inputPwd, dbPwd) => {
return encryptPassword(inputPwd) === dbPwd;
};
在 .env 文件中配置敏感信息,避免硬编码:
# .env 文件
JWT_SECRET=your_jwt_secret_key_123456 # JWT 密钥(随机字符串,越长越安全)
JWT_EXPIRES_IN=2h # Token 过期时间
PWD_SALT=your_password_salt_789 # 密码加盐字符串
前端对接示例(以 Axios 为例)
前端需配置请求基地址、请求头、响应拦截器,和后端统一格式适配:
// src/utils/request.js(前端 Axios 封装)
import axios from 'axios';
// 配置后端基地址(和后端端口/前缀一致)
const request = axios.create({
baseURL: 'http://localhost:3001/api', // 后端接口前缀
timeout: 10000, // 请求超时
withCredentials: true // 允许携带Cookie(若后端配置了 credentials: true)
});
// 请求拦截器:添加 Token 等请求头
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器:统一处理后端返回格式
request.interceptors.response.use(
(response) => {
const res = response.data;
// 后端 success: true 直接返回数据
if (res.success) {
return res.data;
}
// 业务错误(如参数错、未登录)
ElMessage.error(res.msg); // 前端提示错误信息
return Promise.reject(res);
},
(error) => {
// 网络错误/500错误
ElMessage.error(error.response?.data?.msg || '请求失败');
return Promise.reject(error);
}
);
export default request;