核心目标
正确建立通信,实现跨域支持、请求解析、统一响应、环境适配
核心配置项
1. 跨域配置(最关键!前端跨域请求必配)
前端运行在http://localhost:8080/3000等端口,后端运行在http://localhost:3001等端口,浏览器会触发「同源策略」拦截请求,必须配置 CORS(跨域资源共享)。
1.1 安装依赖
npm install cors --save1.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),配置允许认证头,并封装认证中间件:
依赖名称 | 安装命令 | 核心作用 |
|
| 生成 JWT Token + 验证 Token 合法性(核心) |
(可选) |
| 简化 Express 中 JWT 认证中间件的编写(替代手动验证) |
|
| 对敏感信息(如用户密码)进行加密(如 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;