第 9 课:Node.js + Express 后端实战 —— 为任务管理系统搭建专属 API 服务

在前 8 课中,我们已经完成了 Vue 3 前端任务管理系统的开发,但前端所有数据都依赖模拟接口,无法实现真实的数据持久化(比如刷新页面后任务数据丢失)。企业级项目中,后端负责提供数据存储、接口服务、身份验证等核心支撑。本节课将采用Node.js + Express + MongoDB技术栈(新手友好、前后端同语言),为前端任务管理系统搭建专属后端 API 服务,实现 "前端请求→后端处理→数据库存储" 的全栈联动,让你的项目具备真实生产环境的使用能力。

一、课前准备:后端开发环境搭建(10 分钟搞定)

1. 必备工具与环境

2. 环境验证

打开终端(CMD / 终端),输入以下命令验证环境是否安装成功:

bash

运行

复制代码
# 验证Node.js版本(需>=16.0.0)
node -v
# 验证npm版本
npm -v

3. 项目初始化

(1)创建后端项目文件夹

bash

运行

复制代码
# 创建项目文件夹
mkdir vue-task-system-backend
# 进入文件夹
cd vue-task-system-backend
# 初始化npm项目(一路回车即可,生成package.json)
npm init -y
(2)安装核心依赖

bash

运行

复制代码
# Express:轻量级后端框架,用于搭建API服务
npm install express -S
# mongoose:MongoDB数据库操作工具,简化数据增删改查
npm install mongoose -S
# cors:解决跨域问题(前端和后端端口不同导致的请求限制)
npm install cors -S
# jsonwebtoken:生成JWT令牌,用于用户身份验证
npm install jsonwebtoken -S
# bcryptjs:密码加密(比CryptoJS更安全,专门用于密码处理)
npm install bcryptjs -S
# dotenv:管理环境变量(如数据库地址、JWT密钥)
npm install dotenv -S
# nodemon:开发环境热重载(修改代码无需手动重启服务)
npm install nodemon -D
(3)配置 package.json 脚本

修改package.json,添加启动脚本:

json

复制代码
{
  "name": "vue-task-system-backend",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js", // 生产环境启动
    "dev": "nodemon app.js" // 开发环境启动(热重载)
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "jsonwebtoken": "^9.0.2",
    "mongoose": "^7.6.3"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}
(4)创建环境变量文件

新建.env文件,存储敏感配置(不要提交到代码仓库):

env

复制代码
# 服务端口
PORT=3000
# MongoDB连接地址(本地数据库:mongodb://127.0.0.1:27017/vue_task_system)
# 云数据库(MongoDB Atlas)需替换为自己的连接地址
MONGODB_URL=mongodb://127.0.0.1:27017/vue_task_system
# JWT密钥(自定义,越长越安全)
JWT_SECRET=vue_task_system_backend_2024_jwt_secret
# JWT过期时间(24小时)
JWT_EXPIRES_IN=86400s

二、核心实操一:搭建后端基础架构与数据库连接

1. 项目目录结构

先搭建清晰的目录结构,便于后续维护(新手严格按照此结构创建):

plaintext

复制代码
vue-task-system-backend/
├── .env                # 环境变量
├── package.json        # 项目配置
├── app.js              # 入口文件
├── config/             # 配置文件夹
│   └── db.js           # 数据库连接配置
├── models/             # 数据模型文件夹(对应MongoDB集合)
│   ├── User.js         # 用户模型
│   ├── Todo.js         # 任务模型
│   └── Category.js     # 分类模型
├── routes/             # 路由文件夹(对应接口模块)
│   ├── userRoutes.js   # 用户相关路由
│   ├── todoRoutes.js   # 任务相关路由
│   └── categoryRoutes.js # 分类相关路由
├── middleware/         # 中间件文件夹
│   ├── auth.js         # JWT身份验证中间件
│   └── response.js     # 统一响应格式中间件
└── utils/              # 工具函数文件夹
    └── index.js        # 通用工具(如密码加密、JWT生成)

2. 步骤 1:数据库连接配置(config/db.js)

javascript

运行

复制代码
const mongoose = require('mongoose');
require('dotenv').config();

// 连接MongoDB数据库
const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGODB_URL, {
      // MongoDB 6.0+无需配置这些参数,可省略
      // useNewUrlParser: true,
      // useUnifiedTopology: true
    });
    console.log(`MongoDB连接成功:${conn.connection.host}`);
  } catch (error) {
    console.error(`MongoDB连接失败:${error.message}`);
    process.exit(1); // 连接失败退出进程
  }
};

module.exports = connectDB;

3. 步骤 2:统一响应格式中间件(middleware/response.js)

前端需要统一的响应格式,方便处理数据,避免每次接口返回不同格式:

javascript

运行

复制代码
// 统一成功响应
const successResponse = (res, data = null, message = '操作成功') => {
  res.status(200).json({
    code: 200,
    message,
    data
  });
};

// 统一错误响应
const errorResponse = (res, message = '操作失败', code = 500) => {
  res.status(code).json({
    code,
    message,
    data: null
  });
};

module.exports = {
  successResponse,
  errorResponse
};

4. 步骤 3:JWT 身份验证中间件(middleware/auth.js)

验证前端请求头中的 JWT 令牌,确认用户是否登录,用于保护需要权限的接口:

javascript

运行

复制代码
const jwt = require('jsonwebtoken');
const { errorResponse } = require('./response');
require('dotenv').config();

// 身份验证中间件
const protect = (req, res, next) => {
  let token;

  // 从请求头中获取token(格式:Bearer <token>)
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
    try {
      // 提取token(去掉Bearer前缀)
      token = req.headers.authorization.split(' ')[1];
      // 验证token
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      // 将用户ID存入请求对象,后续接口可直接使用
      req.user = { id: decoded.id };
      next(); // 验证通过,执行下一个中间件/接口
    } catch (error) {
      console.error(error);
      errorResponse(res, '无效的令牌,请重新登录', 401);
    }
  }

  if (!token) {
    errorResponse(res, '未提供令牌,无权访问', 401);
  }
};

module.exports = { protect };

5. 步骤 4:工具函数(utils/index.js)

封装密码加密和 JWT 生成工具,避免重复代码:

javascript

运行

复制代码
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
require('dotenv').config();

// 密码加密
const encryptPassword = async (password) => {
  // 生成盐值(rounds=10,越高越安全,速度越慢)
  const salt = await bcrypt.genSalt(10);
  // 加密密码
  return await bcrypt.hash(password, salt);
};

// 密码验证(对比明文密码和加密密码是否一致)
const verifyPassword = async (plainPassword, hashedPassword) => {
  return await bcrypt.compare(plainPassword, hashedPassword);
};

// 生成JWT令牌
const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN
  });
};

module.exports = {
  encryptPassword,
  verifyPassword,
  generateToken
};

6. 步骤 5:入口文件(app.js)

整合所有配置,启动后端服务:

javascript

运行

复制代码
const express = require('express');
const cors = require('cors');
const connectDB = require('./config/db');
const { successResponse, errorResponse } = require('./middleware/response');
require('dotenv').config();

// 连接数据库
connectDB();

// 创建Express应用
const app = express();
const PORT = process.env.PORT || 3000;

// 中间件配置
app.use(cors()); // 允许所有跨域请求(生产环境可配置指定域名)
app.use(express.json()); // 解析JSON格式请求体
app.use(express.urlencoded({ extended: false })); // 解析表单格式请求体

// 测试接口
app.get('/', (req, res) => {
  successResponse(res, { message: 'Vue任务管理系统后端服务运行中' }, '服务正常');
});

// 挂载路由(所有接口前缀为/api)
app.use('/api/users', require('./routes/userRoutes'));
app.use('/api/categories', require('./routes/categoryRoutes'));
app.use('/api/todos', require('./routes/todoRoutes'));

// 404接口
app.use('*', (req, res) => {
  errorResponse(res, '接口不存在', 404);
});

// 启动服务
app.listen(PORT, () => {
  console.log(`后端服务启动成功,运行在端口:${PORT}`);
});

三、核心实操二:定义数据模型(MongoDB 集合)

数据模型对应 MongoDB 的集合(类似 MySQL 的表),定义数据结构和验证规则。

1. 用户模型(models/User.js)

javascript

运行

复制代码
const mongoose = require('mongoose');

// 用户模型 Schema
const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: [true, '用户名不能为空'],
    unique: true, // 用户名唯一
    trim: true, // 去除前后空格
    minlength: [3, '用户名长度不少于3位'],
    maxlength: [20, '用户名长度不超过20位']
  },
  password: {
    type: String,
    required: [true, '密码不能为空'],
    minlength: [6, '密码长度不少于6位']
  },
  email: {
    type: String,
    required: [true, '邮箱不能为空'],
    unique: true,
    trim: true,
    match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, '请输入有效的邮箱']
  },
  avatar: {
    type: String,
    default: 'https://picsum.photos/200/200' // 默认头像
  },
  createTime: {
    type: Date,
    default: Date.now
  }
}, {
  timestamps: true // 自动添加createdAt和updatedAt字段
});

// 定义模型
const User = mongoose.model('User', userSchema);

module.exports = User;

2. 分类模型(models/Category.js)

javascript

运行

复制代码
const mongoose = require('mongoose');

// 分类模型 Schema
const categorySchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, '分类名称不能为空'],
    unique: true,
    trim: true
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User', // 关联用户模型
    required: [true, '用户ID不能为空'] // 每个分类归属一个用户
  },
  createTime: {
    type: Date,
    default: Date.now
  }
}, {
  timestamps: true
});

// 定义模型
const Category = mongoose.model('Category', categorySchema);

module.exports = Category;

3. 任务模型(models/Todo.js)

javascript

运行

复制代码
const mongoose = require('mongoose');

// 任务模型 Schema
const todoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: [true, '任务内容不能为空'],
    trim: true
  },
  completed: {
    type: Boolean,
    default: false // 默认未完成
  },
  categoryId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Category', // 关联分类模型
    required: [true, '分类ID不能为空']
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User', // 关联用户模型
    required: [true, '用户ID不能为空']
  },
  createTime: {
    type: Date,
    default: Date.now
  }
}, {
  timestamps: true
});

// 定义模型
const Todo = mongoose.model('Todo', todoSchema);

module.exports = Todo;

四、核心实操三:实现业务接口(路由)

1. 用户相关接口(routes/userRoutes.js)

实现注册、登录、获取用户信息功能:

javascript

运行

复制代码
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const { successResponse, errorResponse } = require('../middleware/response');
const { protect } = require('../middleware/auth');
const { encryptPassword, verifyPassword, generateToken } = require('../utils');

// 1. 用户注册
router.post('/register', async (req, res) => {
  try {
    const { username, password, email } = req.body;

    // 检查用户名是否已存在
    const userExists = await User.findOne({ username });
    if (userExists) {
      return errorResponse(res, '用户名已存在', 400);
    }

    // 检查邮箱是否已存在
    const emailExists = await User.findOne({ email });
    if (emailExists) {
      return errorResponse(res, '邮箱已存在', 400);
    }

    // 密码加密
    const hashedPassword = await encryptPassword(password);

    // 创建用户
    const user = await User.create({
      username,
      password: hashedPassword,
      email
    });

    if (user) {
      successResponse(res, {
        id: user._id,
        username: user.username,
        email: user.email,
        avatar: user.avatar,
        token: generateToken(user._id) // 注册成功直接返回token
      }, '注册成功');
    } else {
      errorResponse(res, '创建用户失败', 400);
    }
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 2. 用户登录
router.post('/login', async (req, res) => {
  try {
    const { username, password } = req.body;

    // 查找用户
    const user = await User.findOne({ username });
    if (!user) {
      return errorResponse(res, '用户名不存在', 400);
    }

    // 验证密码
    const isPasswordValid = await verifyPassword(password, user.password);
    if (!isPasswordValid) {
      return errorResponse(res, '密码错误', 400);
    }

    // 返回用户信息和token
    successResponse(res, {
      id: user._id,
      username: user.username,
      email: user.email,
      avatar: user.avatar,
      token: generateToken(user._id)
    }, '登录成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 3. 获取当前用户信息(需要身份验证)
router.get('/me', protect, async (req, res) => {
  try {
    // 根据用户ID查找用户(排除密码字段)
    const user = await User.findById(req.user.id).select('-password');
    if (!user) {
      return errorResponse(res, '用户不存在', 400);
    }
    successResponse(res, user, '获取用户信息成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

module.exports = router;

2. 分类相关接口(routes/categoryRoutes.js)

实现分类增删改查功能:

javascript

运行

复制代码
const express = require('express');
const router = express.Router();
const Category = require('../models/Category');
const { successResponse, errorResponse } = require('../middleware/response');
const { protect } = require('../middleware/auth');

// 1. 获取当前用户所有分类(需要身份验证)
router.get('/', protect, async (req, res) => {
  try {
    const categories = await Category.find({ userId: req.user.id }).sort({ createTime: -1 });
    successResponse(res, categories, '获取分类列表成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 2. 新增分类(需要身份验证)
router.post('/', protect, async (req, res) => {
  try {
    const { name } = req.body;

    // 检查分类名称是否已存在
    const categoryExists = await Category.findOne({ name, userId: req.user.id });
    if (categoryExists) {
      return errorResponse(res, '分类名称已存在', 400);
    }

    // 创建分类
    const category = await Category.create({
      name,
      userId: req.user.id
    });

    successResponse(res, category, '新增分类成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 3. 删除分类(需要身份验证)
router.delete('/:id', protect, async (req, res) => {
  try {
    const categoryId = req.params.id;

    // 检查分类是否存在且归属当前用户
    const category = await Category.findOne({ _id: categoryId, userId: req.user.id });
    if (!category) {
      return errorResponse(res, '分类不存在或无权删除', 400);
    }

    // 删除分类
    await Category.findByIdAndDelete(categoryId);
    successResponse(res, null, '删除分类成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

module.exports = router;

3. 任务相关接口(routes/todoRoutes.js)

实现任务增删改查、状态更新功能:

javascript

运行

复制代码
const express = require('express');
const router = express.Router();
const Todo = require('../models/Todo');
const { successResponse, errorResponse } = require('../middleware/response');
const { protect } = require('../middleware/auth');

// 1. 获取当前用户所有任务(需要身份验证)
router.get('/', protect, async (req, res) => {
  try {
    const todos = await Todo.find({ userId: req.user.id }).sort({ createTime: -1 });
    successResponse(res, todos, '获取任务列表成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 2. 新增任务(需要身份验证)
router.post('/', protect, async (req, res) => {
  try {
    const { title, categoryId } = req.body;

    // 创建任务
    const todo = await Todo.create({
      title,
      categoryId,
      userId: req.user.id
    });

    successResponse(res, todo, '新增任务成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 3. 更新任务状态(需要身份验证)
router.put('/:id/status', protect, async (req, res) => {
  try {
    const todoId = req.params.id;
    const { completed } = req.body;

    // 检查任务是否存在且归属当前用户
    const todo = await Todo.findOne({ _id: todoId, userId: req.user.id });
    if (!todo) {
      return errorResponse(res, '任务不存在或无权修改', 400);
    }

    // 更新任务状态
    const updatedTodo = await Todo.findByIdAndUpdate(
      todoId,
      { completed },
      { new: true } // 返回更新后的任务
    );

    successResponse(res, updatedTodo, '更新任务状态成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

// 4. 删除任务(需要身份验证)
router.delete('/:id', protect, async (req, res) => {
  try {
    const todoId = req.params.id;

    // 检查任务是否存在且归属当前用户
    const todo = await Todo.findOne({ _id: todoId, userId: req.user.id });
    if (!todo) {
      return errorResponse(res, '任务不存在或无权删除', 400);
    }

    // 删除任务
    await Todo.findByIdAndDelete(todoId);
    successResponse(res, null, '删除任务成功');
  } catch (error) {
    console.error(error);
    errorResponse(res, '服务器错误');
  }
});

module.exports = router;

五、核心实操四:接口测试与前后端联调

1. 启动后端服务

bash

运行

复制代码
# 启动开发环境(热重载)
npm run dev

启动成功后,终端会显示:

plaintext

复制代码
MongoDB连接成功:127.0.0.1
后端服务启动成功,运行在端口:3000

2. 用 Postman 测试接口

(1)测试注册接口
  • 请求地址:http://localhost:3000/api/users/register
  • 请求方式:POST
  • 请求体(JSON):

json

复制代码
{
  "username": "testuser",
  "password": "123456",
  "email": "testuser@163.com"
}
  • 响应结果:返回用户信息和 token,说明注册成功。
(2)测试登录接口
  • 请求地址:http://localhost:3000/api/users/login
  • 请求方式:POST
  • 请求体(JSON):

json

复制代码
{
  "username": "testuser",
  "password": "123456"
}
  • 响应结果:返回用户信息和 token,说明登录成功。
(3)测试新增分类接口(需要身份验证)
  • 请求地址:http://localhost:3000/api/categories
  • 请求方式:POST
  • 请求头:Authorization: Bearer <登录返回的token>
  • 请求体(JSON):

json

复制代码
{
  "name": "工作"
}
  • 响应结果:返回分类信息,说明新增分类成功。

3. 前端对接后端接口

修改前端项目的src/utils/axios.js,更新基础地址并添加请求头携带 token:

javascript

运行

复制代码
import axios from 'axios';
import { ElMessage } from 'element-plus';
import { useUserStore } from '@/stores/user';

// 创建axios实例
const service = axios.create({
  // 替换为后端服务地址
  baseURL: 'http://localhost:3000/api',
  timeout: 5000
});

// 请求拦截器:添加JWT token
service.interceptors.request.use(
  (config) => {
    const userStore = useUserStore();
    if (userStore.token) {
      // 给请求头添加Authorization字段
      config.headers.Authorization = `Bearer ${userStore.token}`;
    }
    return config;
  },
  (error) => {
    console.error('请求错误:', error);
    return Promise.reject(error);
  }
);

// 响应拦截器:统一处理响应
service.interceptors.response.use(
  (response) => {
    const res = response.data;
    // 成功响应
    if (res.code === 200) {
      return res;
    } else {
      // 业务错误
      ElMessage.error(res.message || '请求失败');
      return Promise.reject(res);
    }
  },
  (error) => {
    // 网络错误或服务器错误
    ElMessage.error(error.message || '服务器错误');
    return Promise.reject(error);
  }
);

export default service;

修改前端各 API 文件(以src/api/user.js为例),对接真实后端接口:

javascript

运行

复制代码
import request from '../utils/axios';

// 登录接口
export const userLogin = (userData) => {
  return request({
    url: '/users/login',
    method: 'POST',
    data: userData
  });
};

// 获取当前用户信息
export const getUserInfo = () => {
  return request({
    url: '/users/me',
    method: 'GET'
  });
};

// 注册接口(新增)
export const userRegister = (userData) => {
  return request({
    url: '/users/register',
    method: 'POST',
    data: userData
  });
};

4. 前后端联调测试

启动前端项目(npm run dev),访问http://127.0.0.1:5173/,进行以下操作:

  1. 注册新用户(若未注册);
  2. 登录用户,进入系统;
  3. 新增分类、新增任务、修改任务状态、删除任务;
  4. 刷新页面,数据不会丢失(已持久化到 MongoDB);
  5. 退出登录后重新登录,数据依然存在。

六、综合实战:后端部署(可选,生产环境)

1. 部署到云服务器(以阿里云 ECS 为例)

  1. 云服务器安装 Node.js、MongoDB;

  2. 上传后端项目到服务器;

  3. 安装依赖:npm install --production(生产环境不安装开发依赖);

  4. pm2启动服务(进程守护,防止服务意外停止):

    bash

    运行

    复制代码
    # 安装pm2
    npm install pm2 -g
    # 启动服务
    pm2 start app.js --name vue-task-system-backend
    # 设置开机自启
    pm2 startup
    # 保存进程配置
    pm2 save

2. 部署到云函数(无需服务器,新手友好)

  • 腾讯云 SCF / 阿里云 FC:将 Express 项目改造为云函数格式,直接部署;
  • Vercel:支持 Node.js 后端部署,无需复杂配置,关联 GitHub 仓库即可自动部署。

七、本节课总结与后续学习建议

1. 本节课核心收获

  • 环境搭建:掌握 Node.js + Express + MongoDB 后端开发环境搭建;
  • 核心技能:掌握数据库连接、数据模型定义、接口开发、身份验证(JWT);
  • 全栈联动:实现前端与后端的真实数据交互,理解全栈项目的工作流程;
  • 项目落地:让前端任务管理系统具备真实数据持久化能力,可用于生产环境。

2. 课后作业(必做)

  1. 独立完成后端项目搭建,确保所有接口正常工作;
  2. 完成前端与后端的对接,实现注册、登录、任务 / 分类管理的全流程;
  3. 新增 "修改用户信息""编辑任务" 接口,完善业务功能;
  4. 部署后端项目到云服务器或云函数,生成线上接口地址。

3. 后续后端学习建议

  • 技术深化:学习 Koa2(更优雅的 Node.js 框架)、TypeScript + Node.js;
  • 数据库拓展:学习 MySQL(关系型数据库)、Sequelize(ORM 工具);
  • 安全优化:学习接口限流、SQL 注入防护、XSS 攻击防护;
  • 框架学习:学习 Spring Boot(Java 后端)、Django(Python 后端),拓展技术栈;
  • 微服务:学习微服务架构、Docker 容器化部署,应对大型项目开发。

至此,你已掌握 "Vue 3 前端 + Node.js 后端" 的全栈开发能力,能够独立开发中小型全栈项目。后续可通过持续实战,不断提升后端架构设计和性能优化能力,成为一名全栈开发工程师!

明天开始进行新的课程讲解!

相关推荐
世界唯一最大变量2 小时前
此算法能稳定求出柏林52城问题最优解7540.23(整数时为7538),比传统旅行商问题的算法7544.37还优
前端·python·算法
-拟墨画扇-2 小时前
Git | 简介与安装
大数据·git·elasticsearch
Nan_Shu_6142 小时前
学习:TypeScript (1)
前端·javascript·学习·typescript
沛沛老爹2 小时前
Web开发者快速上手AI Agent:基于Advanced-RAG的提示词应用
前端·人工智能·langchain·llm·rag·web转型·advanced-rag
5967851542 小时前
HTML元素
前端·html
行业探路者2 小时前
视频和音频二维码生成及二维码扫描器的应用价值解析
大数据·人工智能·安全·二维码·设备巡检
鹏多多2 小时前
React使用useLayoutEffect解决操作DOM页面闪烁问题
前端·javascript·react.js
zhengxianyi5152 小时前
vue devSever中如何配置多个proxy 代理及pathRewrite路径重写
前端·javascript·vue.js·proxy·前后端分离·devserver·pathrewrite