每年毕设季和课程设计季,商城类项目都是最热门的选题之一,而“商城 + 垂直领域 + 前后端分离”基本就是稳妥方案的代名词。今天要复盘的这个项目——Nodejs+vue二手母婴用品商城全程服务管理系统,乍看是套着标准商城外壳的题目,实际做下来会发现它比普通商城多了一层更真实的“管理”逻辑:商品不是管理员录进去的,而是用户自己发布的闲置物品;交易也不是简单地加购下单,而是包含了发布审核、买卖撮合、订单流转、评价信任的完整链路。
我按实际开发顺序来复盘:先拆需求,再讲为什么技术选型是 Node.js + Vue,接着把环境搭建里最容易卡住的 Node.js 安装、npm 报错和 Vue 项目初始化一次讲透,然后分后端和前端两条线展开核心实现,最后聊联调、打包、上线阶段的坑以及这个项目还能怎么扩展。无论你是正在赶毕设的学生,还是想用完整全栈项目练手的开发者,这篇内容应该都能对得上号。
1. 二手母婴商城的真实需求:这个系统到底在管什么
1.1 为什么是“二手母婴”而不是普通电商
普通商城做的是“选品—下单—履约”的标准化链路,商品、库存、价格都由平台统一控制;而二手交易平台的核心难点在于“非标品”“个人对个人”“信任不足”三件事。母婴用品恰恰把这三个难点集齐了。
先看需求侧:婴儿床、婴儿推车、温奶器、婴儿衣物、早教玩具,很多品类的实际使用周期只有几个月。孩子一长大,这些东西就沦为“扔了可惜、留着占地”的闲置资产。与此同时,这些用品买一手往往要几百上千块,对绝大多数家庭来说都是一笔不小的支出。于是“低价收一个成色不错的二手”和“快速把闲置变现”这两种需求非常稳定地同时存在。这就是为什么二手母婴不是一个小众噱头,而是一个有真实交易频率的垂直场景。
再看系统侧:二手交易不能像普通商品那样直接上架,平台需要对发布内容做审核,避免卫生、安全、违禁品类混进来;买家需要能留言咨询、看到卖家的历史评价;交易完成后订单状态要可追踪。这些合起来,就是“全程服务管理”的核心含义——商品从发布到下架、订单从创建到完成、用户从注册到评价,三条生命周期都在系统里被管理起来。这也是这个题目在答辩时真正能讲出东西的地方:它不是一个纯电商 demo,而是一个带平台治理逻辑的交易系统。
1.2 角色、业务流程与功能边界
做这类系统,我习惯先定角色再把流程走一遍,因为角色直接决定权限设计和页面结构。按常规方案,系统拆成三类角色:
- 买家:浏览、搜索、咨询、下单、评价,同时也可以发布自己的闲置。
- 卖家:本质是普通用户,核心动作是发布闲置、管理在售商品、发货、查看卖出记录。
- 管理员:平台运营视角,负责商品审核、用户管理、订单监控、数据统计。
核心业务流程是一条闭环:用户注册登录 → 卖家发布闲置(标题、分类、价格、图片) → 管理员后台审核 → 审核通过后商品进入在售列表 → 买家通过分类浏览或关键词搜索找到商品 → 进入详情页查看、留言咨询 → 加入购物车或直接下单 → 卖家发货 → 买家确认收货 → 双方评价。
这里最容易忽略的是“审核”这一环。很多新手做商城时默认商品由管理员直接录入,结果把系统做成了后台管理系统而不是交易平台。二手闲置业务里,用户自主发布、平台审核上架,才是这个题目区别于普通电商项目的关键点。哪怕你的需求文档里没有明确写审核流程,也建议保留,因为它是“全程服务管理”的题眼。
1.3 功能模块怎么落地成一张开发清单
把业务流程翻译成功能模块,我一般会控制在五个模块以内,避免一开始就铺得太大。这里给出一个最小可行版本的功能清单:
| 模块 | 核心功能点 | 涉及角色 |
|---|---|---|
| 用户中心 | 注册登录、修改资料、我的发布、我的订单、收藏列表 | 全部用户 |
| 商品模块 | 发布闲置、图片上传、分类筛选、关键词搜索、详情展示、上下架 | 用户 / 管理员 |
| 交易模块 | 购物车、创建订单、订单列表、发货、确认收货、取消订单 | 用户 |
| 互动模块 | 商品留言、订单评价、收藏 | 用户 |
| 管理后台 | 商品审核、用户管理、订单监控、数据统计 | 管理员 |
这个清单既是编码阶段的 sprint backlog,也是答辩 PPT 的目录。不要一上来就把优惠券、秒杀、积分商城这些旁支功能塞进去,先把“发布—审核—交易—评价”这条主干打通,后面想加什么都是增量。
2. 技术选型的权衡:Node.js + Vue 这套组合为什么合适
2.1 前后端分离的第一原则:职责拆清楚
项目标题已经把技术栈定成了 Node.js + Vue,这里真正要做的决策不是“用什么语言”,而是“前后端怎么分工”。整体仍然是前后端分离架构:Vue 负责页面渲染和用户交互,Node.js(Express)负责提供 RESTful API,MySQL 负责数据持久化。
为什么不用传统服务端模板(比如 EJS、JSP)?核心原因是页面交互复杂度。商城类系统有大量的列表筛选、购物车、订单状态切换、富交互表单,用模板引擎渲染页面,后端要把前端逻辑一起扛,开发和调试的体验都很差。前后端分离之后,前端工程师和后端工程师可以并行开发,联调时各查各的代码,问题定位清晰。部署也灵活:开发环境用 Vite 代理解决跨域,生产环境把前端打包产物直接交给后端托管,完全跑得通。
2.2 为什么后端选 Node.js 而不是 Spring Boot
这是我在帮人选型时被问过最多的问题。直接说结论:如果这个项目只需要“能跑、能演示、能讲清楚架构”,Node.js 是性价比最高的选择;如果目标是企业级高并发、微服务、大规模团队协作,才值得上 Spring Boot。
Node.js + Express 有几条很实际的优点。第一,语言统一,前后端都是 JavaScript,前端同学不需要额外学一门 Java 语法,知识点复用率高。第二,生态成熟,jsonwebtoken 解决 JWT 签发、multer 解决文件上传、mysql2 连接 MySQL、bcryptjs 做密码哈希,每一个都是几行代码就能集成的成熟方案。第三,学习曲线平缓,Express 的路由和中间件模型非常直观,调试只需要 console.log 加上浏览器 Network 面板就够了。
当然,Node.js 的短板也要说清楚:CPU 密集型任务不适合它,复杂事务的一致性保障需要自己做得更仔细。但就一个二手母婴商城的管理系统来说,这些短板完全碰不到。选型不是越重越好,而是匹配当前阶段的目标。
2.3 Vue 3 还是 Vue 2:别在 2024 年之后还开 Vue 2 的新项目
现在的 Vue 生态已经明显分化,我做这个项目时用的是 Vue 3 + Vite + Element Plus,原因很简单:Vite 启动速度快、组合式 API 写起来更清爽、Element Plus 的表格和表单组件足够撑起管理后台和商城主页面。
| 技术方案 | 适用场景 | 需要注意的点 |
|---|---|---|
| Vue 2 + Element UI + Vue CLI | 网上老教程多,部分学校的课件还停留在这一代 | 已停止维护,新项目不建议再开 |
| Vue 3 + Vite + Element Plus | 当前主流,组合式 API 清晰,适合本项目 | 与 Vue 2 API 差异大,需要适应 script setup |
| Vue 3 + Vite + Vant | 做移动端 H5 风格,手机端演示观感好 | 组件风格偏移动端,后台管理页面会不太协调 |
如果你之前完全没接触过组合式 API,从 Vue 3 开始确实有短暂的不适应期,但它并不会比 Vue 2 的 options API 更难,只是写法变了。考虑到项目周期和资料丰富度,我仍然建议直接上 Vue 3。
2.4 数据存储和配套工具的合理边界
存储方案沿用最常见的搭配:MySQL 5.7 或 8.0 都行,Node 侧用 mysql2 驱动,连接池方式管理连接。为什么不用 MongoDB?因为订单、用户、商品之间有关联查询和事务需求,关系型数据库的表结构和状态字段更适合交易系统,而且 MySQL 在学校和面试场景里认可度更高。
图片存储先不考虑云服务。开发阶段直接把上传图片放到本地 uploads 目录,后端用 express.static 暴露出去就能访问。这样做的好处是减少外部依赖,演示环境断网也能跑通全流程;等真到了要上线的阶段,再迁移到云对象存储也不迟。密码加密用 bcryptjs,鉴权 token 用 jsonwebtoken,这两样是 Node 后端项目里绕不开的标准件。
3. 环境配置是第一道坎:Node.js、npm 报错与 Vue 项目初始化
3.1 Node.js 安装与环境变量配置:多数问题的根源
环境配置阶段,新手问题集中在三处:装完 Node 后发现 node -v 找不到命令、npm install 报脚本执行错误、依赖下载慢到无法忍受。第一步先把 Node.js 装好。
到官网下载 LTS 版本而不是 Current 尝鲜版,这是稳定优先的选择。Windows 下安装包会自动写入 PATH,装完重启终端即可验证。如果用的是免安装版,需要手动把解压目录加入系统环境变量的 PATH,比如解压到 D:\nodejs,就把 D:\nodejs 加进去。验证方式固定两行命令:node -v 看到 v18.x 或 v20.x,npm -v 能看到版本号。
这里有一个非常隐蔽的坑:改完环境变量必须新开一个终端窗口才生效。很多同学改完 PATH 后继续用旧终端执行 node -v,发现还是找不到命令,于是反复重装,其实只是终端没刷新。遇到这种情况,新开一个窗口基本就能解决。
3.2 高发报错:npm.ps1 无法加载文件,因为在此系统上禁止运行脚本
这个报错在 Windows 用户中出现频率极高,完整信息是:“npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本”。它的根因是 PowerShell 的默认执行策略为 Restricted,禁止任何 .ps1 脚本运行,而 npm 在 PowerShell 里的调用入口恰好是 npm.ps1。这不是 npm 坏了,是脚本策略拦住了它。
解决办法推荐方式一:放宽执行策略。以管理员身份打开 PowerShell,执行:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser输入 Y 确认,再执行 Get-ExecutionPolicy,看到 RemoteSigned 就成功了。RemoteSigned 的含义是本地脚本放行,远程下载的脚本必须有签名,安全级别仍然可控,不必直接关掉系统保护。
方式二更省事:换终端。如果你不想动 PowerShell 设置,直接打开 CMD 或 Git Bash,再执行 npm install 就不会触发这个报错。VS Code 里也可以把默认终端切到 Command Prompt。赶工时我经常用这个方式绕过问题,等空下来再补执行策略设置。
3.3 npm 换源和依赖安装:把网络问题扼杀在源头
依赖下载慢是国内开发环境绕不开的话题,常规操作是换镜像源:
# 查看当前源 npm config get registry # 切换到国内镜像源 npm config set registry https://registry.npmmirror.com # 验证 npm config get registry换源之后如果 npm install 还是报错,就要按顺序排查:删除 node_modules 和 package-lock.json,执行 npm cache clean --force,再重新安装。如果报错信息里带 network 关键词,优先考虑网络问题;带 peer 或 ENOENT 关键词,基本是版本冲突,需要在 package.json 里显式指定兼容版本。判断报错关键词比一遍遍重试更有用。
3.4 用 Vite 创建 Vue 项目并装齐基础依赖
环境就绪后用 Vite 创建项目,命令是:
npm create vite@latest mom-mall -- --template vue cd mom-mall npm install npm install vue-router@4 pinia element-plus axios npm run devVite 的启动速度比 Vue CLI 快很多,原因是它基于原生 ES Modules 做按需编译,不需要像 webpack 那样先打包全部依赖。启动后浏览器打开 http://localhost:5173 就能看到初始页面。到这一步,前端骨架已经搭好,接下来是更花时间的后端接口和业务逻辑。
4. 后端从零开始:数据库设计、接口与鉴权实现
4.1 数据库设计:先把状态机想清楚再建表
商城系统的核心不是表多,而是状态正确。做这个项目时,我先把两张核心表的字段、状态定义写清楚,再动手写代码,后面编码会顺很多。
商品状态定义:
- 0 待审核:用户刚刚发布。
- 1 在售:管理员审核通过,前台可见。
- 2 已售:订单完成流转后自动变更。
- 3 已下架:卖家主动下架或违规被下架。
订单状态定义:
- 0 待付款。
- 1 待发货:模拟支付完成。
- 2 待收货:卖家已发货。
- 3 已完成:买家确认收货。
- 4 已取消:买家或卖家主动取消。
核心表设计如下:
| 表名 | 主要字段 | 说明 |
|---|---|---|
| users | id, username, password, nickname, avatar, phone, role | role:0普通用户、1管理员 |
| goods | id, user_id, title, category, price, original_price, cover, images, description, status | 价格用 DECIMAL(10,2),images 用 JSON 数组存多图 |
| orders | id, order_no, buyer_id, seller_id, goods_id, amount, status, address | order_no 为唯一订单号,时间戳加随机串生成 |
| comments | id, order_id, goods_id, user_id, rating, content, create_time | 评价挂在订单和商品上 |
三个容易踩的细节。第一,价格字段千万不要用 FLOAT 或 DOUBLE,货币必须用 DECIMAL,否则会出现 0.1 + 0.2 不等于 0.3 的精度问题。第二,goods 表的 images 字段用 JSON 类型或逗号分隔字符串都行,但查询返回给前端时要记得做序列化处理。第三,所有表都建议带 create_time 和 update_time,管理员后台做数据统计时,这两个字段就是唯一依靠。
4.2 后端项目结构与 Express 入口
我习惯把后端代码按 routes、controllers、middleware、models、config 拆开,目录结构如下:
server/ ├── app.js # 入口:中间件、路由挂载 ├── config/ │ ├── db.js # 数据库连接池 │ └── jwt.js # 密钥与过期时间 ├── routes/ # 路由层,只负责分发 │ ├── users.js │ ├── goods.js │ └── orders.js ├── controllers/ # 控制器,处理业务逻辑 │ ├── userController.js │ ├── goodsController.js │ └── orderController.js ├── middleware/ │ └── auth.js # JWT 鉴权中间件 └── uploads/ # 图片上传目录入口文件 app.js 的关键代码:
const express = require('express'); const cors = require('cors'); const path = require('path'); const userRoutes = require('./routes/users'); const goodsRoutes = require('./routes/goods'); const orderRoutes = require('./routes/orders'); const app = express(); app.use(cors()); app.use(express.json()); app.use('/uploads', express.static(path.join(__dirname, 'uploads'))); app.use('/api/users', userRoutes); app.use('/api/goods', goodsRoutes); app.use('/api/orders', orderRoutes); app.listen(3000, () => { console.log('server running at http://localhost:3000'); });这里把 routes 挂载到 /api/users、/api/goods、/api/orders 前缀下,前端请求路径必须和这个前缀严格对齐,联调阶段大量 404 都从这里来。
4.3 接口清单与业务边界
| 方法 | 路径 | 用途 | 权限 |
|---|---|---|---|
| POST | /api/users/register | 注册 | 公开 |
| POST | /api/users/login | 登录,返回 token | 公开 |
| GET | /api/users/me | 获取当前用户信息 | 登录 |
| GET | /api/goods | 分页/搜索/分类查询 | 公开 |
| GET | /api/goods/:id | 商品详情 | 公开 |
| POST | /api/goods | 发布闲置 | 登录用户 |
| PUT | /api/goods/:id/status | 上下架/审核 | 卖家或管理员 |
| POST | /api/orders | 创建订单 | 登录用户 |
| GET | /api/orders/mybuy | 我买到的 | 登录用户 |
| GET | /api/orders/mysell | 我卖出的 | 登录用户 |
| PUT | /api/orders/:id/status | 发货/确认收货/取消 | 登录用户 |
接口设计遵循 RESTful 风格,资源用名词,动作用 HTTP 方法。真正的业务边界在于:发布商品只能修改自己的,审核商品只有管理员能改,订单状态变更需要判断当前用户是买家还是卖家。这些判断写在 controller 里,路由层只负责分发。
4.4 JWT 鉴权和密码安全:不能跳过的两道保险
登录接口签发 token 是前后端分离项目的标准做法:
const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); // 注册时密码加密 const hashed = await bcrypt.hash(password, 10); // 登录成功后签发 token const token = jwt.sign( { id: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '7d' } );中间件校验 token 的写法:
function auth(req, res, next) { const header = req.headers.authorization || ''; const token = header.startsWith('Bearer ') ? header.slice(7) : null; if (!token) return res.status(401).json({ message: '未登录' }); try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); } catch (err) { return res.status(401).json({ message: 'token 无效或过期' }); } }密码永远不要明文存数据库,这是底线。bcryptjs 加盐哈希,存的是哈希值,比对的时候用 bcrypt.compare(password, hashedPassword) 做校验。虽然这是商城项目的常规操作,但在答辩或面试里能把这个点讲清楚,面试官对你的好感度会明显上升。
4.5 图片上传与静态资源的两个坑
用户发布闲置商品必然要传图片,用 multer 处理:
const multer = require('multer'); const storage = multer.diskStorage({ destination: 'uploads/', filename: (req, file, cb) => { const ext = path.extname(file.originalname); cb(null, Date.now() + '-' + Math.round(Math.random() * 1e9) + ext); } }); const upload = multer({ storage });文件名加时间戳和随机数,是为了避免用户上传同名文件时互相覆盖。另一个坑是必须在前端请求路径中拼接完整地址,比如后端返回 /uploads/xxx.jpg,前端实际访问时要拼成 http://localhost:3000/uploads/xxx.jpg。很多联调阶段图片裂图的问题,不是上传失败,而是后端没有暴露 uploads 静态目录,或者前端忘了拼端口和前缀。
5. 前端页面与交互:路由、发布、下单和数据请求封装
5.1 前端目录结构怎么组织
Vue 项目的代码组织,直接影响后续添加页面的成本。我习惯按下图拆分:
src/ ├── api/ # 接口请求封装,按模块拆分 │ ├── user.js │ ├── goods.js │ └── order.js ├── router/ # 路由配置 ├── store/ # pinia 状态 ├── views/ # 页面 │ ├── Home.vue │ ├── GoodsList.vue │ ├── GoodsDetail.vue │ ├── Publish.vue │ ├── Cart.vue │ ├── OrderList.vue │ ├── Login.vue │ └── admin/ ├── components/ # 复用组件 └── utils/request.js # axios 实例这个结构没有太多花哨之处,但每个功能模块的代码都有明确归属。新增一个页面,知道建文件夹、配路由、写 api 三个动作就够了。
5.2 前端路由与参数传递:params、query 和 props
路由配置是商城系统的骨架,这里以 Vue Router 4 为例:
import { createRouter, createWebHistory } from 'vue-router'; const routes = [ { path: '/', name: 'home', component: Home }, { path: '/goods', name: 'goods-list', component: GoodsList }, { path: '/goods/:id', name: 'goods-detail', component: GoodsDetail, props: true }, { path: '/login', name: 'login', component: Login }, { path: '/publish', name: 'publish', component: Publish, meta: { requiresAuth: true } }, ];商品列表跳详情页会碰到第一个高频问题:用 params 还是 query。用动态路由 /goods/:id 加 params 传参,跳转 URL 是 /goods/12,刷新页面参数还在;用 query 方式,URL 是 /goods?id=12,同理刷新不丢。真正容易踩的坑是组件里拿不到参数——要么忘了在路由配置里开 props: true,要么直接读 route.params.id 之前没确认当前路由名称对得上。建议在详情页声明 defineProps(['id']),然后依赖它发起请求。
登录态路由守卫的写法:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next({ name: 'login', query: { redirect: to.fullPath } }); } else { next(); } });5.3 商品发布页面:表单、图片上传和自定义 v-model
发布闲置是用户端最重要的表单页面。用 Element Plus 的 el-form 做校验,核心是三个字段:标题、分类、价格,再加一组图片上传。价格字段用 el-input-number 控制精度,避免用户填出乱七八糟的小数。
图片上传的逻辑要拆成两步:先把图片传到后端,拿到图片 URL,再把 URL 写入表单数据,最后随商品信息一起提交。千万不要试图把图片二进制塞进 JSON 请求体里,二进制和 JSON 分离处理才是常规做法。
这里值得扩展一个 Vue 概念:自定义 v-model。如果你想把图片上传封装成组件,在子组件里定义:
defineProps(['modelValue']); const emit = defineEmits(['update:modelValue']);父组件用 v-model 绑定图片地址数组,子组件上传成功后 emit('update:modelValue', newList) 即可。官方文档“组件 v-model”这一节值得花十分钟通读,商城项目里大量表单组件复用都会用到。
5.4 购物车与下单流程的页面状态设计
购物车有两种常见方案:存 localStorage 或存后端。考虑到项目目标是快速跑通演示,localStorage 方案就够了——刷新不丢、不需要建表、代码量小。下单时把购物车里选中的商品调 POST /api/orders 创建订单,后端校验商品状态和价格后生成订单记录,返回订单号,前端跳转到待付款列表,点击“模拟支付”按钮把订单状态置为已付款。
订单列表页面是状态按钮逻辑最绕的地方,按“当前用户是买家还是卖家”来渲染操作按钮:
- 买家视角:待付款显示“取消订单”;待发货显示“提醒发货”;待收货显示“确认收货”;已完成显示“去评价”。
- 卖家视角:待发货显示“发货”;待收货显示“查看物流”;已完成显示“查看评价”。
这个视角划分是商城系统的典型业务规则,也是答辩时值得主动讲清楚的一点。
5.5 axios 封装:请求拦截器、响应拦截器和统一错误处理
前端工程化的一个标志就是请求层统一封装。所有接口请求走同一个 axios 实例:
import axios from 'axios'; const service = axios.create({ baseURL: '/api', timeout: 10000, }); service.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); service.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token'); location.href = '/login'; } return Promise.reject(error); } ); export default service;好处非常直观:每个页面都不用手动拼接 Authorization 头,token 过期统一跳登录,响应返回结构统一,业务页面只需要关心拿到的数据。调试阶段配合浏览器开发者工具的 Network 面板,基本能解决 80% 的联调问题。
6. 联调、打包与上线的常见坑
6.1 开发环境的跨域:用 Vite 代理而不是写死端口
前端开发服务器默认跑在 5173 端口,后端 Node 服务在 3000 端口,如果不处理,浏览器会拦截跨域请求。虽然后端用 cors() 中间件能解决一部分,但更推荐在 Vite 配置里做代理:
// vite.config.js export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, }, }, }, });配置之后,前端代码里请求 /api/goods 时,Vite Dev Server 会把它转发到 http://localhost:3000/api/goods,浏览器看起来是同源的,跨域问题直接消失。为什么不建议在 axios 里把 baseURL 写死成 http://localhost:3000?因为写死端口意味着上线时得改代码。用相对路径 /api 作为 baseURL,开发和生产环境切换时不需要动业务代码。
6.2 联调阶段的高频报错排查
联调阶段最容易出现的几个问题,我整理成一张对照表,方便排查:
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
| 接口 404 | 请求路径和后端路由前缀对不上 | 检查前端 baseURL 与后端 app.use 挂载路径 |
| 图片裂图 | uploads 静态目录没暴露或 URL 少了端口 | 确认 express.static 已配置、URL 前缀完整 |
| 创建订单报 400 | 缺必填字段或 token 过期 | 看请求体有没有携带商品 ID、地址等字段 |
| 登录后刷新又跳登录 | token 没有在刷新后放回请求头 | 用请求拦截器统一从 localStorage 注入 |
| 生产部署后接口 404 | 前端 dist 托管路径与 API 路径冲突 | 后端托管静态文件时注意路由优先级 |
排查思路有一个总原则:先在 Network 面板确认请求是否发出、状态码是什么、响应体是什么,再动手改代码。“请求没发出去”“请求发了但被拦截”“后端返回了错误”是三种完全不同的 debug 路径,花十秒钟确认现象比瞎猜强得多。
6.3 打包部署:dist 给后端还是 Nginx
本地开发完成后,演示和提交都涉及打包:
npm run buildVite 构建后生成 dist 目录。两种典型部署方式:
一是后端托管。把 dist 内容复制到后端项目的 public 目录,后端加一行 app.use(express.static('public')),访问 http://localhost:3000 就能看到前端页面,页面里的 /api 请求天然同源。这种方案最简单,适合答辩演示。
二是 Nginx 托管。Nginx 配置 root 指向 dist,/api 路径 proxy_pass 到 Node 的 3000 端口,适合正式上线,但需要额外懂一点 Nginx 配置。
后端进程建议用 PM2 常驻,或者在演示时保证终端窗口不要被误关。部署前的检查清单包括:数据库连接配置独立成文件、JWT_SECRET 换成随机字符串、前端接口路径用相对路径 /api、uploads 目录确保存在且有写入权限。这几项在演示现场出过太多幺蛾子了,提前检查能省掉很多尴尬。
7. 做完这个项目后的一点思考与扩展方向
7.1 让项目更像“全程服务”的几个增强点
如果时间有余,可以把系统往“服务管理”方向再推一步,这也是这个题目的真正题眼。
管理员端增强:商品审核列表是要做的第一件事,通过/驳回带原因,用户端能同步看到审核状态。这个功能工作量不大,但对“全程服务管理”的说服力非常强。
消息通知:审核结果、订单状态变化通过站内信或者简单的前端徽标通知用户。不需要对接短信服务,数据库里加一张 notifications 表就够了。
评价与信任体系:在商品页展示卖家好评率和历史评价,降低二手交易的信任门槛。这是二手平台区别于普通商城的重要特征。
数据看板:管理员首页展示今日发布量、成交量、交易金额。用几条 SQL count 查询就能做出来,但演示观感提升特别明显。
7.2 给正在做同类项目的人几句实在建议
关于要不要从头手写:直接 clone 别人的源码改改能交差,但那样学不到东西。我建议核心链路——注册登录、商品发布、下单、订单状态流转——自己完整敲一遍。这个项目看起来页面很多,其实核心链路就那么几条,把链路打通再去补细节,效率远高于按照页面清单一个个磨。
关于前后端联调的认知:无论前端写得再好、后端接口设计得再清晰,联调阶段一定会出问题。这不是你的个例,是所有前后端项目都要经历的阶段。调试工具提前配好:浏览器开发者工具的 Network 面板看请求状态和响应体,Node 后端用 console.log 看入参。定位到“请求没发出去”“后端返回错误”“返回了但页面没渲染”这三大类中的哪一类,问题基本上就解决了一半。
最后分享一个小技巧:给项目写一份 README,把启动步骤、管理员账号、测试账号、角色说明都写清楚。演示之前一定提前把服务启动好,准备两个浏览器上下文,一个登录买家,一个登录管理员,讲解时切换角色展示不同视角。这个细节会让答辩老师觉得项目很完整,而这些准备功夫其实只需要二十分钟。