ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Vue+Node.js的二手书交易系统全栈开发实战

基于Vue+Node.js的二手书交易系统全栈开发实战 二手书交易系统这个题目我在不少技术群里见过每年毕业季都有一波人在问。仔细看下来大家问得最多的不是业务逻辑有多复杂而是环境装不上、接口连不通、页面渲染不出来这些基建问题。今天借着一个比较典型的“Node.js Vue 二手书籍交易系统商家/卖家版”项目把完整链路拆开讲一遍从需求设计到技术选型从前端页面到后端接口再到联调排错尽量还原一个真实项目的落地过程。这个系统本身要解决的痛点很直白二手书交易里买家想知道书还在不在、卖家想知道订单到哪一步了。如果只是拿 Excel 记流水账效率低还容易出错。用 Vue 做交互界面用 Node.js 做接口服务再用 MySQL 存数据就是一个很标准的前后端分离全栈项目。适合正在做毕设的学生也适合想练手全栈开发、搞清 Vue Node 怎么协作的入门开发者。接下来我会按实际项目推进的顺序来写相当于带你重新做一遍。1. 需求拆解与技术选型背后的逻辑1.1 这个系统到底要解决什么问题做项目之前先别急着写代码先把角色和业务流程捋清楚。这个系统里主要有两种角色普通用户买家和商家卖家。买家要能浏览书籍、搜索书籍、加购物车、下单购买商家要能发布书籍、修改库存、处理订单、查看销售记录。管理员这边一般还要有用户管理、书籍审核之类的能力不过如果是毕设级别的项目管理员功能可以简化把核心交易闭环做好就够了。一本书从卖家发布到买家收货核心状态无非就是在售、已下单、已售出。围绕这个状态系统需要有书籍管理、订单管理、用户管理三个大模块。再加一个购物车用户体验会完整很多。很多新手上来就急着建表、写接口结果做着做着发现业务口径对不上又开始推倒重来。先画一张简单的流程图把“买家干了什么”“卖家干了什么”两条线分开走一遍后面写接口时会清晰很多。我见过不少项目把“商家”和“买家”做成两套完全独立的系统其实没必要。用角色字段区分就好同一个用户既可以买书也可以卖书这才符合真实场景。你注册的时候选个身份或者后台直接改角色都比单独搞两套登录注册要省事。1.2 为什么是 Vue Node.js而不是别的组合每个时代都有每个时代偏爱的技术栈。现在做这种管理系统类项目Vue Node.js已经算是最顺手的组合之一。前端用 Vue 是因为它的生态成熟Vue Router 管路由、Vuex或 Pinia管状态、Element UI或 Element Plus提供了现成的后台管理组件表格、表单、弹窗、分页全都是拿来即用。你不用自己从零写一套 UI 组件库开发效率会高很多。后端选 Node.js 的 Express 框架最大的理由是 JavaScript 全栈语言统一。前端写 JS后端也写 JS数据类型、命名习惯、对象结构都保持一致前后端联调的时候不用在“Java 的 List 和 JS 的 Array 怎么转换”这种事上浪费时间。而且 Express 本身非常轻量路由、中间件、静态服务都很直观适合中小型项目快速搭建。如果需求特别复杂还可以上 NestJS但对这种交易系统来说 Express 完全够用。有人会纠结要不要用 Spring Boot Vue这也是毕设里非常常见的组合。但如果你不是 Java 生态的老手Node.js 的学习曲线明显更平缓。写一个 CRUD 接口Express 大概几行就搞定Spring Boot 光是那一堆注解和依赖就要折腾半天。这个项目的核心价值是快速跑通全栈业务流程那种“为了用框架而加复杂度”的事能避免就避免。1.3 整体架构与目录结构设计项目按前后端分离的方式组织我建议一开始就把目录分清楚别把前后端代码混在一个文件夹里。实操下来比较舒服的结构是这样book-trade-system/ ├── frontend/ # Vue 前端工程 │ ├── public/ │ ├── src/ │ │ ├── api/ # 封装好的接口请求 │ │ ├── assets/ # 静态资源 │ │ ├── components/ # 通用组件 │ │ ├── router/ # 路由配置 │ │ ├── store/ # 全局状态 │ │ ├── views/ # 页面组件 │ │ ├── App.vue │ │ └── main.js │ ├── package.json │ └── vue.config.js # 开发代理配置 ├── backend/ # Node.js 后端工程 │ ├── routes/ # 路由文件 │ ├── controllers/ # 业务逻辑 │ ├── models/ # 数据库操作 │ ├── middleware/ # 鉴权、错误处理等中间件 │ ├── utils/ # 工具函数 │ ├── app.js # 应用入口 │ ├── package.json │ └── .env # 配置文件 └── database/ └── init.sql # 建表脚本前后端目录分开有个实际好处部署的时候可以各管各的前端打包成静态文件丢到 Nginx后端用 PM2 或直接 node 跑起来就行。开发阶段则靠代理转发解决跨域问题这个后面会详细说。2. 环境搭建Node.js 安装、npm 配置与项目初始化2.1 Node.js 安装与环境变量配置怎么才能不踩坑环境这块是整个项目里最容易让人心态崩掉的地方。很多人在第一步就栽了而且栽得很一致——要么 node 命令在终端里找不到要么 npm 安装依赖时各种权限报错。先说 Node.js 安装。去官网下载对应操作系统的 LTS 版本安装包LTS 是长期维护版稳定性优先适合项目开发不建议追新版本。安装的时候有个关键点Windows 下安装向导里有一项 “Add to PATH”这一项必须勾上。勾上之后安装程序会自动把 Node.js 的目录写进系统环境变量你才能在任意目录下使用 node 和 npm 命令。如果你安装的时候没勾或者换了安装目录导致命令失效可以手动去“系统属性 - 环境变量 - Path”里把 Node.js 安装目录加进去比如D:\Program Files\nodejs\。安装完成之后打开终端输入两条命令验证node -v npm -v如果能看到版本号说明 Node.js 和 npm 都已经正常工作。如果提示“node 不是内部或外部命令”就是环境变量没配好按上面的方式手动添加即可。接下来是 npm 的配置。npm 默认源在国内环境下安装依赖非常慢甚至经常失败。建议先切换镜像源npm config set registry https://registry.npmmirror.com设置完成后可以输入npm config get registry验证返回https://registry.npmmirror.com就说明切换成功。这个操作会把你后续所有 npm install 的下载源都指向国内镜像安装速度会快很多。这里还藏着一个高频报错值得单独拎出来讲。很多人在 Windows 上用 PowerShell 运行 npm 命令时会遇到类似这样的提示npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。这不是 Node.js 本身的问题而是 PowerShell 的脚本执行策略不允许运行 .ps1 文件。解决方案很直接以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned然后输入Y确认再重新打开终端就好了。如果只是临时用一下也可以直接用 CMD 命令提示符来跑 npm 命令CMD 不受这个策略影响。这个坑几乎每个人都会遇到提前知道能省不少时间。2.2 初始化 Vue 前端工程与后端项目前端部分我推荐直接用 Vue CLI 或者 Vite 来创建工程。Vite 启动速度快、配置简洁Vue CLI 更传统稳定。这里以 Vue CLI 为例先全局安装脚手架npm install -g vue/cli安装完成之后在项目根目录下创建前端工程vue create frontend创建过程中会让你选预设配置建议选 “Manually select features”然后勾上 Babel、Router、Vuex、Linter。Router 和 Vuex 是后面肯定会用到的不用再手动集成。Vue 版本建议选 2.x 或 3.x 都可以考虑到组件库兼容性Vue 2.7 Element UI 很稳Vue 3 Element Plus 是更新的方案。我这次按 Vue 2.7 来写因为很多稳定项目中还是在用它。创建完前端工程后再初始化后端项目。手动创建一个 backend 目录进入目录后执行npm init -y这个命令会生成一个默认的 package.json 文件。然后安装 Express 相关依赖npm install express cors mysql2 jsonwebtoken bcryptjs multer简单地说明一下这些依赖的用途express 是 Web 框架cors 解决跨域mysql2 是 MySQL 驱动且支持 promise 语法jsonwebtoken 用来做登录态鉴权bcryptjs 用来加密用户密码multer 处理文件上传比如书籍封面图片。后端项目不需要像前端那样有复杂的构建流程但是为了让代码结构清晰一点我还是会在 backend 目录下手动创建好routes、controllers、models、middleware这些子目录。等到真正写接口的时候你会发现这种分层的管理方式会让项目越做越有序不会出现几百行的路由文件堆在一起无从下手的情况。3. 数据库设计与后端接口实现3.1 表结构设计先把字段想清楚再动手数据库设计是后端开发的基石字段建错了后面改起来非常痛苦。二手书交易系统最核心的表有四张用户表、书籍表、订单表、购物车表。如果要做收藏、评论之类的高级功能可以再扩展但核心业务四张表就够了。用户表设计成这样字段名类型说明idINT 自增主键用户IDusernameVARCHAR(50) UNIQUE用户名passwordVARCHAR(255)密码存 bcrypt 加密后的哈希值nicknameVARCHAR(50)昵称roleTINYINT角色0-普通用户 1-商家avatarVARCHAR(255)头像路径phoneVARCHAR(20)联系电话create_timeDATETIME注册时间书籍表是业务核心字段直接决定商品信息的丰富程度字段名类型说明idINT 自增主键书籍IDtitleVARCHAR(100)书名authorVARCHAR(50)作者publisherVARCHAR(100)出版社priceDECIMAL(10,2)售价original_priceDECIMAL(10,2)原价descriptionTEXT书籍描述、成色说明imageVARCHAR(255)封面图路径seller_idINT发布人/商家ID关联用户表statusTINYINT状态0-在售 1-已下单 2-已售出create_timeDATETIME上架时间订单表记录每笔交易的详情字段名类型说明idINT 自增主键订单IDorder_noVARCHAR(32) UNIQUE订单编号book_idINT书籍IDbuyer_idINT买家IDseller_idINT卖家IDamountDECIMAL(10,2)成交金额statusTINYINT订单状态0-待付款 1-已付款 2-已发货 3-已完成 4-已取消create_timeDATETIME下单时间购物车表则比较简单记录用户ID和书籍ID的关联以及加入时间即可。这些建表 SQL 可以直接在一个init.sql脚本里写完整后续在 MySQL 客户端或命令行里边执行边看报错比用可视化工具一步步点建表要高效。3.2 Express 接口设计注册登录、书籍管理、订单流程后端接口按模块拆分每个模块对应一个路由文件。路由只负责分发请求具体的业务逻辑放到 controller 里数据库操作放到 model 里。这样的好处是以后改逻辑不需要在路由文件里大海捞针。先做注册登录模块。用户注册时密码一定要加密存储bcryptjs 的使用方式非常简单const bcrypt require(bcryptjs); const { query } require(../models/db); // 注册接口 exports.register async (req, res) { const { username, password, nickname } req.body; // 检查用户名是否已存在 const [rows] await query(SELECT id FROM users WHERE username ?, [username]); if (rows.length 0) { return res.json({ code: 1, msg: 用户名已被注册 }); } // 密码加密10 是 salt 的轮数轮数越多越安全但耗时越久 const hash bcrypt.hashSync(password, 10); const [result] await query( INSERT INTO users (username, password, nickname) VALUES (?, ?, ?), [username, hash, nickname] ); res.json({ code: 0, data: { id: result.insertId } }); };登录接口的逻辑则是先按用户名查出用户然后用bcrypt.compareSync(password, user.password)校验密码是否一致。密码校验通过后用 jsonwebtoken 生成 token 返回给前端。前端后续请求只需要在请求头里带上Authorization: Bearer token后端中间件就能解析出当前用户是谁。书籍管理模块的接口按照 RESTful 风格设计。列表查询用 GET/api/books支持分页、关键词搜索、状态筛选详情用 GET/api/books/:id发布书籍用 POST/api/books下架或修改用 PUT/api/books/:id。其中发布和修改都需要检查当前登录用户是否为书籍的卖家不然任何用户都能改别人的商品信息这明显是不合理的。订单流程是这个系统里业务闭环最完整的一块买家下单时先生成订单记录同时把书籍状态改成“已下单”防止别人再买同一本书。这里有个经典问题两个用户同时下单怎么办解决方案是在查询书籍状态和更新书籍状态之间加上事务和行锁。MySQL 的 InnoDB 引擎支持SELECT ... FOR UPDATE先把那本书的行锁住再进行状态判断和订单插入最后COMMIT释放锁。简单的交易系统这样处理已经足够再复杂就得上 Redis 分布式锁了但那种级别在这个项目里属于过度设计。接口开发的过程中还有一件事必须处理参数校验。很多新手把前端传过来的数据直接拼进 SQL 或者直接存库这既不安全也不稳定。至少要做三件事必填字段检查、类型检查、长度检查。比如价格字段必须是数字不能是字符串abc用户名字段长度不能超过 50否则直接返回 400 错误。这些校验虽然代码写起来繁琐但能挡掉大量脏数据为后面的联调和测试省下不少功夫。3.3 鉴权中间件JWT 的原理与实现JWT 是现在前后端分离项目最常用的登录鉴权方式。它的原理可以简单理解成用户登录成功后服务端把用户 ID、角色这些信息签发到一个加密的 token 里这个 token 是自包含的服务端不需要单独存储 session。前端把 token 存到 localStorage 里每次请求的时候放到请求头后端解出来就知道是谁在调用接口。后端实现一个全局鉴权中间件用 express 的app.use挂载到需要保护的接口上const jwt require(jsonwebtoken); module.exports (req, res, next) { const header req.headers.authorization || ; const token header.split( )[1]; if (!token) { return res.status(401).json({ code: 401, msg: 未登录 }); } try { const decoded jwt.verify(token, process.env.JWT_SECRET); req.user decoded; next(); } catch (err) { return res.status(401).json({ code: 401, msg: 登录已过期 }); } };在设计接口的时候要把不需要登录就能访问的接口比如书籍列表、书籍详情和必须登录才能访问的接口分开。我一般会在路由文件层面做区分公共接口不挂中间件受保护接口单独挂。前端配合 Vue Router 的路由守卫在页面跳转时判断是否存在 token不存在就强制跳到登录页这样前后端各守一道防线才不会出现“没登录也能打开购物车”这种尴尬情况。4. 前端页面开发与核心交互实现4.1 路由设计Vue Router 的配置和参数传递前端页面的核心是路由系统。Vue Router 的配置不要一股脑全写在router/index.js里把路由按模块拆分然后合并成一张路由表会让结构更清晰。这个项目至少需要这些页面/首页展示书籍列表/books/:id书籍详情页/cart购物车页/order订单列表页买家视角/seller/books商家书籍管理页/seller/orders商家订单管理页/login登录页/register注册页路由传参是一个面试和实操都很常考的点。从列表页点进详情页有几种传参方式。路径参数方式在定义路由时写成/books/:id跳转时用this.$router.push({ path: /books/${bookId} });详情页通过this.$route.params.id拿到参数。这种方式 URL 语义清晰刷新页面参数也不会丢失推荐优先使用。查询参数方式则是这样this.$router.push({ path: /books, query: { id: bookId } });对应的取值用this.$route.query.id。查询参数适合传筛选条件、搜索词这类非核心标识用来传书籍主键 ID 显得不够正式。还要特别注意一个点路由表里每个页面的name最好都要命名好并且保持全局唯一。页面组件里大量使用this.$router.push({ name: BookDetail, params: { id: bookId } })的方式跳转这样即使以后改了路径只要路由 name 不变页面代码就不用跟着改。这个习惯很多老手都在用新手上路时可能不太在意但等页面多起来就知道有多香了。路由守卫也是这个项目必不可少的部分。在router.beforeEach里判断 meta 信息比如某个路由配置了meta: { requiresAuth: true }那就检查本地是否有 tokenrouter.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.matched.some(record record.meta.requiresAuth) !token) { next(/login); } else if (to.path /login token) { next(/); } else { next(); } });别小看这一段代码它能在用户还没发起任何接口请求之前就把非法访问挡在门外体验比“点进去发现接口报错再跳回来”要好得多。4.2 页面布局与组件拆分页面布局上我建议用 Element UI 的 Container 布局组件搭一个基本框架顶部是导航栏左侧是侧边栏右侧是内容区。导航栏显示网站名称和当前登录用户信息未登录时显示“登录/注册”入口已登录时显示“我的订单”“商家后台”和“退出登录”。首页的书籍列表用卡片列表来展示比较好看每张卡片包含封面图、书名、作者、价格和“查看详情”按钮。搜索框放在页面顶部通过输入书名关键词调用后端搜索接口。分页用 Element UI 的 Pagination 组件每页展示固定数量的书籍切换页码时重新拉取数据。书籍详情页的布局是左侧大图、右侧信息区的经典电商风格信息区展示书名、作者、出版社、价格、成色描述下方是“加入购物车”和“立即购买”按钮。如果当前用户就是这本书的卖家则不显示按钮改为显示“这是你发布的书籍”这样的提示避免卖家自己买自己的书。商家管理页需要两个子页面书籍管理可以新增、编辑、下架书籍和订单管理查看所有涉及自己店铺的订单。书籍管理页用表格组件展示每一行末尾都有操作按钮新增和编辑时用一个弹窗表单来操作表单里包含书名、作者、出版社、价格、原价、成色描述、封面图片上传。前端表单校验用 Element 自带的 rules 就能做比如价格必填且必须是大于 0 的数字。页面的组件拆分也要讲究。不要把每一块 UI 都写在页面文件里而是把通用部分抽出来书籍卡片是一个组件分页器是一个组件商家订单状态标签是一个组件。这样列表页和商家管理页可以复用同样的卡片组件不会出现“同一个书籍卡片在首页一个样式、在商家后台另一个样式”的尴尬情况。4.3 Axios 封装与状态管理前端和后端通信用的是 Axios。直接在每个页面里this.$http.get(/api/books)不是不行但一旦项目里要统一处理 token 注入、错误提示、加载状态没有封装就会非常麻烦。我习惯在src/api/request.js里创建一个 Axios 实例import axios from axios; const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }); // 请求拦截器自动带 token service.interceptors.request.use( config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }, error Promise.reject(error) ); // 响应拦截器统一处理错误 service.interceptors.response.use( response { const res response.data; if (res.code ! 0) { // Element Message 提示 Message.error(res.msg || 请求失败); return Promise.reject(new Error(res.msg)); } return res; }, error { if (error.response error.response.status 401) { localStorage.removeItem(token); location.href /login; } Message.error(网络异常请稍后重试); return Promise.reject(error); } ); export default service;有了这个封装每个页面的接口调用就变得非常干净。比如获取书籍列表的接口只需要一行import request from /api/request; export function getBookList(params) { return request({ url: /books, method: get, params }); }全局状态用 Vuex 管理主要存储用户信息和登录状态。登录成功后把用户信息写入state顺便同步到 localStorage。页面里通过mapState读取避免每个页面都自己去查一遍 localStorage。如果是 Vue 3 项目用 Pinia 是更现代的选择语法更简洁模块化也更好。5. 前后端联调、常见问题与避坑经验5.1 开发环境跨域问题怎么优雅地解决联调阶段第一件事就是处理跨域。前端跑在http://localhost:8080后端跑在http://localhost:3000前后端端口不一样浏览器默认会拦截跨域请求。开发阶段最简单的方式是在 Vue 工程里配置代理。在vue.config.js里这样写module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } };配置完之后前端请求/api/books开发服务器会把请求转发到http://localhost:3000/api/books。这种代理方案是开发体验最好的方式前端代码里不需要写完整的后端地址就写相对路径/api开头就行换环境部署的时候也不用改前端代码。当然如果你要直接从前端页面发请求到后端不走代理那后端就一定要配好 CORS。用 cors 中间件几行代码就搞定const cors require(cors); app.use(cors());如果业务需要携带 Cookie 或自定义请求头还需要额外配置app.use(cors({ origin: http://localhost:8080, credentials: true, allowedHeaders: [Content-Type, Authorization] }));我实际开发中一般两套方案都会留好后端的 CORS 配置同时前端也尽量走代理。因为代理模式下请求是“同源”的Cookie 处理、鉴权头传递都比较顺滑省去很多意外问题。5.2 高频报错速查表实操中容易翻车的点做完整项目过程中有一些高频错误我整理成了速查表几乎每个接手这个项目的人都会遇到报错现象原因解决方案npm.ps1 无法加载文件禁止运行脚本PowerShell 执行策略限制管理员身份运行Set-ExecutionPolicy RemoteSigned或改用 CMDnode -v找不到命令Node.js 未加入 PATH检查系统环境变量把 Node.js 安装目录加入 Pathnpm install 安装依赖非常慢或一直失败npm 默认源在国外npm config set registry https://registry.npmmirror.com前端请求接口报 404代理未配置或后端路由前缀不一致检查 vue.config.js 里的 target、路径里是否有/api前缀前端请求接口报 401token 不存在或已过期检查登录后是否成功存储 token检查请求拦截器是否注入 Header数据库查询报connect ECONNREFUSED后端连不上 MySQL确认 MySQL 服务已启动检查端口、用户名、密码配置图片上传后看不到图片静态目录未映射后端用app.use(/uploads, express.static(uploads))暴露目录刷新页面后变成 404Vue Router 是 history 模式后端或服务器未做 fallback部署环境配置 try_files 指向index.html或用 hash 模式第一个和第二个报错是环境层面的属于还没开始写代码就会碰到的坑几乎每个用 Windows 的新手都会遭遇提前打好预防针非常重要。数据库连接报错也很有代表性。mysql2连接数据库时如果密码不对、数据库名不对甚至密码里带特殊字符没有转义都会报错。建议把数据库连接配置单独放到.env文件里用dotenv读取并且统一用这种方式配置require(dotenv).config(); const mysql require(mysql2/promise); const pool mysql.createPool({ host: process.env.DB_HOST || localhost, user: process.env.DB_USER || root, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, waitForConnections: true, connectionLimit: 10 }); module.exports pool;使用连接池除了能省去反复创建连接的开销还能天然避免“并发多几个请求就报连接数超限”的问题。新手如果用一次性连接后端接口一旦收到稍微多一点的并发请求就会报ER_CON_COUNT_ERROR而连接池模式基本不会遇到这个问题。5.3 上线部署时的关键配置开发完成之后如果要部署到服务器上上线的配置和开发环境的配置还是有一些区别的。前端构建时执行npm run build生成静态文件在dist目录里把这些文件丢到 Nginx 的静态目录下同时配置一下接口反向代理location /api/ { proxy_pass http://127.0.0.1:3000/; }这里要注意proxy_pass末尾的/会不会把/api前缀带到后端。如果后端接口路径本身就带/api那 proxy_pass 要写http://127.0.0.1:3000;不带斜杠如果后端不带/api前缀则要写成带斜杠的形式。这个斜杠问题在部署时是经久不衰的经典踩坑点我每次都要翻之前的配置来确认。后端部署则建议用 PM2 来守护进程。PM2 可以自动重启崩溃的进程开机自启配置也方便pm2 start app.js --name book-trade-backend数据库部署时还要特别注意时区问题。MySQL 默认时区和 Node.js 的时区可能不同会导致数据库里存的时间跟本地时间差 8 个小时。建议在创建数据库连接时加上dateStrings: true, timezone: 08:00dateStrings: true会让日期以字符串形式返回避免 Node.js 和 MySQL 之间的时区转换导致日期显示偏移。这类问题排查起来极其隐蔽但只要提前配置好就能彻底规避。6. 关键业务逻辑演示购物车下单和商家发货6.1 加入购物车与购物车结算购物车功能看起来简单但里面也有几个细节值得注意。加入购物车的接口设计是接收当前登录用户的 ID 和书籍的 ID在购物车表里做一次去重判断。如果这本书已经在该用户的购物车里可以直接提示“已在购物车中”避免出现重复数据。购物车页面用表格展示商品信息每一行有数量选择和删除按钮底部显示总计金额。计算总价有两种方式一种是前端遍历购物车数据累加另一种是每次加载购物车时让后端算好。我倾向于前端计算因为实时性更好数量一改总价马上刷新不用每次都发请求。前端计算的前提是后端返回的数据里每个商品都要带单价这个在查询购物车时通过 JOIN 书籍表查出价格字段就能得到。结算按钮点击后订单接口的处理是一个完整的事务操作先检查书籍状态是否为在售然后插入订单记录、把书籍状态改成已下单、清空该用户的购物车中对应的商品。这三个操作必须放在同一个数据库事务里任何一个失败都要回滚。用mysql2/promise的写法是先从连接池拿到一个连接再用BEGIN、COMMIT、ROLLBACK控制。6.2 商家视角订单状态的推动与通知商家后台的订单管理页面核心操作主要有两个发货和完成交易。订单状态机可以定义为待付款 - 已付款 - 已发货 - 已完成。商家需要在已付款订单上看到收货地址和联系方式然后才能发货。一个容易被忽略的点是订单关联的书籍状态同步。订单已经完成的时候这本书的状态应该变成“已售出”不能再上架。所以商家发货时前端只更新订单状态还不够后端接口里要同时更新订单表和书籍表的状态。这个联动逻辑在接口层做做最简单前端一次调用后端把该做的事都完成尽量不要让前端把多个请求串起来做业务因为这样会放大网络请求失败的风险。很多毕设项目做完都没加消息通知功能。如果时间充裕可以给订单状态变更增加一个“站内消息”表买家下单时给商家发一条消息商家发货时给买家发一条消息。不一定要做实时推送就做成用户在页面上可以看到的未读消息列表就行这个功能会明显提升项目的完整度和答辩时的展示效果。6.3 代码层面的可维护性建议写这种体量的项目完全靠堆代码也能跑通但“跑通”和“可维护”是两码事。我建议在项目开发中至少保持这几个好习惯第一接口返回值格式统一。无论成功失败后端返回的结构都保持{ code, msg, data }这种格式。code 为 0 表示成功非 0 表示业务失败HTTP 状态码用来表示更底层的问题401 未登录、404 不存在、500 服务器异常。格式统一之后前端响应拦截器处理逻辑才能统一否则就是“每个接口各自为政”。第二数据库操作用参数化查询。整个项目不要出现字符串拼接 SQL 的情况。mysql2的?占位符天然防 SQL 注入而且写起来也不麻烦await query(SELECT * FROM books WHERE title LIKE ?, [%${keyword}%]);凡是字符串拼接 SQL 的代码在我这里一律过不了 code review。这不是洁癖是真的会出事。第三前端页面里的重复逻辑要抽成工具函数或自定义指令。比如时间格式化、价格千分位、状态文字转换这些“小工具”点散落在各处一旦要改格式全项目都要跟着动。放到utils目录下统一维护调用方只负责传值和渲染逻辑耦合会低很多。7. 给新手的实操建议与最后的提醒7.1 建议的开发顺序很多新手拿到这种系统需求之后最容易犯的错误是先从界面开始做做完一个静态页面再回头想想怎么填数据。这会导致页面和接口的字段根本对不上。我建议的开发顺序是数据库 - 后端接口 - 前端页面 - 联调测试。先把表建好再写接口用 Postman 或 Apifox 把每个接口都调通然后再开始做前端页面。前端页面的每个数据区域都能对应到具体接口开发效率会高很多。接口测试工具我推荐 Apifox 或 Postman。它们都能保存接口历史、按接口文档分组管理、模拟登录拿到 token 再去请求受保护接口。尤其是登录后的接口测试先在工具里登录获取 token然后把 token 配置到全局变量里请求时自动带上去这跟前端拦截器做的事情是一样的逻辑。7.2 做完整项目比纠结框架版本更重要有一点我想多说几句很多人在动手前会陷入“用 Vue 2 还是 Vue 3、用 Element UI 还是 Element Plus、用 Vuex 还是 Pinia”的选择困难中。这些选择确实存在但它们的差别远没有想象中那么大。Vue 2 和 Vue 3 的核心概念组件、路由、响应式、生命周期是一脉相承的Element UI 和 Element Plus 的组件用法也几乎一一对应。真正影响项目成败的是数据库表结构合不合理、接口逻辑有没有漏洞、前后端字段对不对得上。我用这个项目带过几个一起学习的朋友进度最快的那个人反而不是一开始就纠结技术栈的而是直接选了一个稳定组合就开工边做边查文档。遇到报错就搜索报错信息把问题记录下来。项目做完之后框架切换版本、重构一个页面模块都变成了水到渠成的事情因为他已经有了完整的项目思维而不是停留在某个 API 的用法上。最后分享一个我自己的小习惯项目开发过程中每完成一个功能我都会把“测试数据、测试账号、踩过的坑”顺手记录在一个 Markdown 文件里。到项目后期或者答辩前这个文件就是最好的回顾材料很多调试思路、问题原因都在里面。开发过程看起来很慢实际上反而帮你省下大量重复排查的时间。书可以一本一本看完代码也应该一行一行写明白做项目的意义都在这个过程里了。
返回列表