多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

NodeJs+Express+Mysql宠物用品购物网站毕业设计实战与避坑指南

NodeJs+Express+Mysql宠物用品购物网站毕业设计实战与避坑指南 简介面向人工智能相关专业毕业设计、课程设计与全栈入门学习者的宠物用品购物网站完整源码案例基于Node.jsExpressMySQL实现前后端分离架构。项目覆盖RESTful API设计、用户认证与授权、商品展示、订单管理、支付集成思路及测试部署要点从Express中间件与路由处理到MySQL数据持久化再到前端Vue组件与接口联调均有代码可循用户注册登录、宠物商品分类筛选、购物车与订单状态流转等典型场景可对照源码逐步梳理。资源压缩包共669个文件约12.02MB以JavaScript、Vue、CSS、HTML等前后端源码为主另含SVG图标、GIF动效、JSON配置、图标字体与说明文档目录结构清晰便于按模块查阅。目前已有95人学习下载适合需要参考真实项目结构、梳理全栈开发流程或完成毕业设计的人使用。1. 为什么把宠物用品购物网站当作人工智能专业的毕业设计一个宠物用品购物网站看起来不像人工智能项目但它在 AI 专业毕业设计里很常见你需要在半学期内交付一个能跑起来的网站系统还要覆盖数据库、后端接口和后台管理。基于 NodeJsExpressMysql 的前后端分离架构是这个场景下翻车概率最低的组合。Node.js 装起来快Express 的路由结构十分钟就能搭好Mysql 的表设计也直观前后端分离让答辩时可以分开演示接口和页面。这篇文章会从 Node.js 环境配置、Mysql 安装、数据库表结构、登录与商品接口、前端页面联调讲到答辩前的检查顺序。对正在找 NodeJsExpressMysql 毕业源码案例的人来说照着这篇文章的顺序做能少走很多弯路。2. 选型背后的理由NodeJsExpressMysql 组合为什么最适合毕业设计2.1 前后端分离到底分离了什么接口约定与开发节奏先想清楚一件事前后端分离不是“前端一个人写、后端一个人写”而是把页面渲染和数据返回彻底分开。后端只负责把 JSON 数据吐出来前端拿到 JSON 自己拼 HTML。对毕业设计来说这个好处的实际意义很大你不需要让 Express 的模板引擎去渲染页面前端页面用浏览器直接打开后端用接口调试工具单独测试。我一般用 Apifox 或者 Postman 来测不用额外装什么插件浏览器里也能用自带的 fetch 验证。开发节奏上先定义接口文档把每个地址返回什么 JSON 结构定下来再各写各的。这样到最后联调的时候问题只会出现在接口地址写没写对、参数名对不对不会纠缠在页面和服务器的耦合关系里。网上很多“前后端分离项目实战”教程动不动就上 Vue 或 React但那是业界的做法。毕业设计的重点是把 NodeJsExpressMysql 这条数据链路跑通前端哪怕用原生 HTMLJavaScript 也能体现“分离”这个设计思想。后面我会用一个不依赖脚手架的前端页面来做演示目的是让你把注意力集中在接口和数据结构上。理解了这条链路之后想换 Vue 或 React也只是换个拼页面的方式。“人工智能”这个词在这个项目里到底站在哪很多学校的人工智能大作业要求的不是算法本身而是一个完整的 Web 系统。网站只是载体真正的工程能力体现在数据建模、接口设计和异常处理上。这也是为什么很多 AI 方向的学生会选这类题材它不需要复杂的训练环境但能证明你具备把需求变成系统的能力。2.2 Node.js 安装与 npm 国内镜像源配置在 Windows 上安装 Node.js实际上有两个容易卡住的地方一是不知道自己装的是哪个版本二是 npm 命令突然报错。常见做法是去 Node.js 官网下载 LTS 版本的 Windows 安装包例如 18.x 或 20.x。注意不要装最新的大版本有些新版本模块还没跟上反而给自己找麻烦。安装完成后在 CMD 或 PowerShell 里执行下面两条命令确认。node -v npm -v能输出版本号就说明 Node.js 和 npm 都装好了。然后立刻做一件事——配置国内镜像源因为 npm 默认源在海外安装 Express 的时候经常卡在十几分钟不动。npm config set registry https://registry.npmmirror.com npm config get registry把源设置成 npmmirror 会明显加快依赖下载速度。这一步不是可选项。我之前帮别人调试项目时发现十次里有八次是卡在下载速度上最后急到去手动复制别人的 node_modules很折腾。如果你在 PowerShell 里敲 npm 命令提示无法加载文件是 PowerShell 的脚本执行策略限制了 .ps1 文件和 Node.js 安装本身没关系。解决办法是换到 CMD 里敲命令或者以管理员身份打开 PowerShell执行 Set-ExecutionPolicy RemoteSigned然后输入 Y 确认。2.3 Express 4 还是 Express 5初始化项目用哪套命令Express 现在有两个大版本4.x 和 5.x。Express 5 正式版已经发布一段时间了但说实话对于毕业设计来说Express 4 依然是更稳的选择因为网上能搜到的答案、中间件示例几乎都是 4.x 的写法。你遇到问题时能搜到的解决办法更多。Express 4 的启动代码是常见的 app.listen(3000)而 Express 5 的路由通配符语法有变化这些细节对新手很不友好。常规做法是安装的时候直接把版本锁在 4.x避免意外装上 5.x。npm init -y npm install express4 mysql2 cors jsonwebtoken bcryptjs这里一并装上了 mysql2、cors、jsonwebtoken、bcryptjs 四个依赖后面各有用处。mysql2 是操作 Mysql 的驱动cors 解决跨域问题jsonwebtoken 生成登录令牌bcryptjs 做密码加密。为什么不用 mysql 而用 mysql2因为 mysql2 支持 Promise配合 async/await 写起来比回调友好很多而且它对 Mysql 8.0 的默认认证方式支持更好。那为什么常有人把 Express 和 Fastify 放在一起对比Fastify 性能更好自带 schema 校验但社区资料少插件体系没 Express 丰富。对毕设项目来说Express 的中间件生态是最大的优势比如 cors、express.json 这些中间件几行代码就搞定出了问题也好排查。我并不是否定 Fastify而是建议你把 Express 作为第一选项把 Fastify 作为“略有延伸”在答辩时提一句就行。2.4 Mysql 5.7 还是 8.0Windows 本机安装的要点关于 Mysql我的建议是如果完全没装过数据库直接装 Mysql 8.0如果想和某些老教程保持一致而装 5.7也行但要注意 5.7 在 Windows 上的安装包只支持到 5.7.44。8.0 和 5.7 的认证方式不一样具体差异是Mysql 8.0 默认使用 caching_sha2_password而 Node.js 的 mysql2 驱动已经支持了。所以重点不在于选 5.7 还是 8.0而在于你用对驱动。Windows 上安装 Mysql 8.0 的常见做法是下载 zip 版本解压然后以管理员身份打开 CMD在 bin 目录下执行初始化命令。mysqld --initialize-insecure net start mysql mysql -u root -p初始化时加上 --initialize-insecureroot 用户初始密码为空省去翻日志的麻烦。安装目录和 my.ini 中的 basedir、datadir 路径要一致。如果你在 CMD 里执行 net start mysql 后提示“服务正在启动”然后卡住不动大概率是 my.ini 里的路径写错了。另外my.ini 中字符集一定要设置为 utf8mb4这一条直接决定后面有没有中文乱码问题。2.5 宠物用品网站的数据库表结构设计5 张核心表与字段说明在写接口之前先把数据库建好。宠物用品网站的数据库我用 pet_shop 这个库核心是下面 5 张表。设计思路不复杂用户表存账号密码分类表管理商品分类商品表存宠物粮、玩具、用品的具体信息购物车表记录用户加购的商品订单表承接下单信息。表名用途核心字段users用户信息id, username, password, phone, avatar, create_timecategories商品分类id, name, sortproducts商品信息id, category_id, name, price, stock, image, sales, statuscart购物车id, user_id, product_id, quantityorders订单信息id, user_id, total_amount, status, create_time建表 SQL 我一般写成这样直接复制到 Navicat 或 mysql 命令行执行即可。CREATE DATABASE IF NOT EXISTS pet_shop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE pet_shop; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), avatar VARCHAR(255), create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, sort INT DEFAULT 0 ); CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, category_id INT NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, image VARCHAR(255), sales INT DEFAULT 0, status TINYINT DEFAULT 1, INDEX idx_category (category_id), INDEX idx_sales (sales) ); CREATE TABLE cart ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, product_id INT NOT NULL, quantity INT DEFAULT 1 ); CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, total_amount DECIMAL(10,2), status TINYINT DEFAULT 0, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );注意 products 表里我建了 idx_sales 索引。宠物用品网站的商品列表通常按销量和上架时间排序Mysql 排序在数据量大时才看得出区别索引能减少排序时的扫描成本。后面接口里会用到 ORDER BY sales DESC索引在这里起作用。关于 Mysql 锁的分类你可以不用深究但要知道 InnoDB 在事务隔离级别下UPDATE 和 DELETE 操作会加行锁所以订单状态更新这类短事务尽量快避免长时间持锁拖慢其他请求。3. 后端接口实现用 ExpressMysql 跑通登录与商品管理3.1 初始化项目目录与 Express 服务器最小代码目录结构不建议太复杂我通常这样做项目根目录放 app.js 作为入口db 文件夹放数据库连接routes 文件夹按模块拆路由utils 文件夹放统一返回封装。毕设项目按这个结构写答辩时也讲得清楚。const express require(express); const cors require(cors); const app express(); const PORT 3000; app.use(cors()); app.use(express.json()); app.get(/api/health, (req, res) { res.json({ code: 0, msg: ok, data: { time: Date.now() } }); }); app.listen(PORT, () { console.log(API server running at http://localhost:${PORT}); });这段代码里cors() 解决前端页面在另一端口访问时的跨域问题express.json() 解析前端 POST 过来的 JSON 请求体。app.get(/api/health) 是一个健康检查接口用来确认服务器有没有正常启动这是排查环境问题时最先要看的。启动后访问 http://localhost:3000/api/health能返回 JSON 就说明 Express 本身没问题。3.2 mysql2 连接池与统一返回格式封装数据库连接不建议每次请求都创建一个连接这样浪费资源并发高一点服务器就扛不住。常见做法是使用连接池mysql2 的 createPool 默认会维护一组连接用完之后归还而不是销毁。const mysql require(mysql2); const pool mysql.createPool({ host: 127.0.0.1, user: root, password: 你的密码, database: pet_shop, waitForConnections: true, connectionLimit: 10, queueLimit: 0, charset: utf8mb4 }); const db pool.promise(); module.exports db;参数说明connectionLimit 是连接池最大连接数十个人的毕设项目 10 足够了waitForConnections 表示连接池满时请求排队等待charset 指定 utf8mb4配合数据库字符集设置能避免中文乱码。后面所有接口都用 db.query 执行 SQLmysql2 的 promise 封装支持 await不用再包裹 Promise。统一返回格式这个细节很多人忽略但它能让你前端联调时省很多事。所有接口统一返回 { code, msg, data } 三层结构code 为 0 表示成功非 0 表示失败。这样前端只判断 code 就行。function ok(data, msg success) { return { code: 0, msg, data }; } function fail(msg error, code -1) { return { code, msg, data: null }; } module.exports { ok, fail };3.3 用户注册登录接口与 JWT 鉴权登录模块是购物网站最核心的部分。注册接口的逻辑是先检查用户名是否存在如果不存在就把密码用 bcryptjs 加密后写入 users 表。密码绝不能明文存储这是底线。const db require(../db); const bcrypt require(bcryptjs); const jwt require(jsonwebtoken); const { ok, fail } require(../utils/response); const JWT_SECRET pet_shop_secret; app.post(/api/register, async (req, res) { const { username, password } req.body; if (!username || !password) { return res.status(400).json(fail(用户名和密码不能为空)); } const [rows] await db.query(SELECT id FROM users WHERE username ?, [username]); if (rows.length 0) { return res.json(fail(用户名已存在)); } const hash bcrypt.hashSync(password, 10); const [result] await db.query( INSERT INTO users (username, password) VALUES (?, ?), [username, hash] ); res.json(ok({ id: result.insertId }, 注册成功)); }); app.post(/api/login, async (req, res) { const { username, password } req.body; const [rows] await db.query(SELECT id, username, password FROM users WHERE username ?, [username]); if (rows.length 0) { return res.json(fail(用户不存在)); } const user rows[0]; if (!bcrypt.compareSync(password, user.password)) { return res.json(fail(密码错误)); } const token jwt.sign( { id: user.id, username: user.username }, JWT_SECRET, { expiresIn: 7d } ); res.json(ok({ token, user: { id: user.id, username: user.username } })); });登录成功返回 token前端后续请求需要在请求头带上 Authorization: Bearer token。JWT_SECRET 写在代码里对毕设演示没问题但你要知道真实项目里这个密钥应该放在环境变量中不应该提交到代码仓库。为了让商品新增、修改、删除这些操作需要登录权限我一般写一个简单的 auth 中间件来校验请求头中的 token。function auth(req, res, next) { const header req.headers.authorization || ; const token header.replace(Bearer , ); if (!token) { return res.status(401).json(fail(未登录)); } try { req.user jwt.verify(token, JWT_SECRET); next(); } catch (e) { res.status(401).json(fail(token 无效或已过期)); } }3.4 商品增删改查接口与模糊搜索商品接口是购物网站的业务核心。列表接口要支持分类筛选、关键词搜索和分页排序规则按销量和 id 倒序让新上架的商品也能露出。app.get(/api/products, async (req, res) { const { keyword , categoryId 0, page 1, pageSize 10 } req.query; const offset (Number(page) - 1) * Number(pageSize); let sql SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON p.category_id c.id WHERE p.name LIKE ? ; const params [%${keyword}%]; if (Number(categoryId) 0) { sql AND p.category_id ?; params.push(Number(categoryId)); } sql ORDER BY p.sales DESC, p.id DESC LIMIT ? OFFSET ?; params.push(Number(pageSize), offset); const [rows] await db.query(sql, params); const [[{ total }]] await db.query( SELECT COUNT(*) AS total FROM products WHERE name LIKE ?, [%${keyword}%] ); res.json(ok({ list: rows, total })); });这里有几个容易出错的地方。LIMIT 和 OFFSET 在 mysql2 里必须传整数所以要用 Number() 包一下不然会报语法错误。keyword 参数拼进 LIKE 时用 % 通配符包裹同时 SQL 参数化防止注入。排序用 ORDER BY p.sales DESC 对应商品销量排序这是电商类网站最常见的排序逻辑。新增商品和删除商品的接口比较简单用 auth 中间件保护起来即可。app.post(/api/products, auth, async (req, res) { const { name, price, stock, category_id, image, description } req.body; const [result] await db.query( INSERT INTO products (name, price, stock, category_id, image, description) VALUES (?, ?, ?, ?, ?, ?), [name, price, stock, category_id, image, description] ); res.json(ok({ id: result.insertId }, 商品添加成功)); }); app.delete(/api/products/:id, auth, async (req, res) { const id Number(req.params.id); await db.query(DELETE FROM products WHERE id ?, [id]); res.json(ok(null, 删除成功)); });4. 前端页面消费接口不引前端框架也能做前后端分离4.1 静态页面托管与 fetch 请求封装后端接口写完前端要做的就是用一个页面把这些接口串起来。不用 Vue只用原生 HTML JavaScript照样能体现前后端分离。做法是在项目根目录建 public 文件夹放 index.html 和 app.js然后在 Express 里加一行静态文件托管。app.use(express.static(public));这样访问 http://localhost:3000 就会加载 public/index.html。前端发请求时如果直接用 fetch(url) 每次都写一遍 header会很啰嗦所以我习惯封装一个 request 函数。async function request(url, options {}) { const token localStorage.getItem(token); const headers { Content-Type: application/json, ...(token ? { Authorization: Bearer token } : {}) }; const res await fetch(url, { ...options, headers }); const data await res.json(); if (data.code ! 0) { alert(data.msg); throw new Error(data.msg); } return data.data; }token 存到 localStorage 里每次请求自动带上不用每个接口单独写鉴权代码。data.code ! 0 时统一弹错误提示这是用统一返回格式换来的便利。4.2 商品列表渲染与购物车逻辑商品列表页面用 JavaScript 拼 HTML数据从 /api/products 接口拿。这个逻辑不难但要注意渲染之前要清空容器节点不然筛选条件变化时会重复追加。async function loadProducts() { const keyword document.getElementById(keyword).value; const data await request(/api/products?keyword${encodeURIComponent(keyword)}page1pageSize20); const container document.getElementById(productList); container.innerHTML ; data.list.forEach(item { const div document.createElement(div); div.className product-card; div.innerHTML h3${item.name}/h3 p价格¥${item.price}/p p销量${item.sales}/p button onclickaddToCart(${item.id})加入购物车/button ; container.appendChild(div); }); }encodeURIComponent 处理 keyword防止用户输入中文或特殊字符时 URL 解析出错。加入购物车操作调用后端接口把 user_id 和 product_id 写入 cart 表。这里有一个常见的业务问题购物车是应该存数据库还是存 localStorage如果只做演示存 localStorage 确实更简单但前后端分离项目应该在后端处理这样换设备购物车还在。毕设答辩时评委更愿意看到你诚实地把购物车落库了。4.3 后台管理的增删改查交互后台管理和前台是两套页面但接口是同一套。管理端需要商品的新增、编辑、删除操作。页面里用表单收集商品信息提交时调用接口。操作请求方法接口地址获取商品列表GET/api/products新增商品POST/api/products删除商品DELETE/api/products/:id登录POST/api/login注册POST/api/register一个容易忽略的点是提交表单时的字段名要和后端参数名完全一致。比如后端接收 category_id前端就传 category_id不要传成 categoryId。字母大小写不一样会导致后端取到 undefined。用 fetch 提交表单数据时把对象转换成 JSON 字符串同时确保 header 里 Content-Type 是 application/json。async function handleSubmit(event) { event.preventDefault(); const name document.getElementById(name).value; const price document.getElementById(price).value; const category_id document.getElementById(category_id).value; await request(/api/products, { method: POST, body: JSON.stringify({ name, price, category_id }) }); alert(添加成功); loadProducts(); }这段代码里event.preventDefault() 用来阻止表单默认跳转刷新页面不加的话接口请求会被浏览器打断。前后端分离模式下页面刷新和接口调用是两个独立的事。5. 避坑排查与常见问题NodeJsMysql 毕设最容易翻车的 5 个场景5.1 现象npm.ps1 无法加载文件提示禁止运行脚本很多人在 PowerShell 里执行 npm install 时直接报错“npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本”。这个报错不是 npm 安装坏了而是 PowerShell 的执行策略默认禁止运行 .ps1 脚本。原因是 npm 在 Windows 上提供的是 PowerShell 脚本系统为了保护环境把它们禁了。解决办法有两个任选其一。最省事的是以后都用 CMD 执行 npm 命令CMD 不受这个限制。如果坚持用 PowerShell就以管理员身份打开 PowerShell执行命令Set-ExecutionPolicy RemoteSigned输入 Y 确认后重启 PowerShell 就能用了。RemoteSigned 的意思是本地脚本允许运行从互联网下载的脚本需要数字签名这个策略对日常开发足够安全。5.2 现象Mysql 8.0 连接报错 caching_sha2_password如果你用的不是 mysql2而是早期版本的 mysql 模块连接 Mysql 8.0 时会报 ER_NOT_SUPPORTED_AUTH_MODE 错误提示客户端不支持 caching_sha2_password 认证。这个问题的本质是账号认证插件不兼容。解决办法有两个方向。第一个是换用 mysql2 驱动这是最推荐的因为 mysql2 支持 Mysql 8.0 的默认认证插件。第二个是在数据库里把 root 用户的认证方式改回 mysql_native_password但这样会降低安全性而且 8.0 后续版本已经在逐步废弃这个插件了。我一般直接选第一个改一行 require 就行。5.3 现象接口返回中文乱码前端传中文进数据库查出来显示成问号或者乱码通常是字符集链路断了。造成乱码的位置可能是数据库字符集、数据库连接字符集、页面编码三个环节中的任何一个。排查顺序是先看数据库执行 SHOW VARIABLES LIKE character_set_database确认库是 utf8mb4。再看 mysql2 连接配置确认 charset 参数是 utf8mb4。最后看前端页面 meta 标签确认 charset 是 utf-8。这三处都对了中文就不会出现乱码。最容易漏的是 mysql2 连接配置里的 charset 参数很多人建库时用了 utf8mb4但连接串没指定结果表中数据是乱码。我在前文的 db/index.js 里已经加了这一步直接抄就行。5.4 现象启动服务提示 EADDRINUSE 端口被占用Express 启动时如果报 EADDRINUSE说明 3000 端口已经被别的程序占了。常见原因是你之前启动过一次服务器CtrlC 没杀干净或者终端还挂着旧进程。排查命令是netstat -ano | findstr :3000最后一列是 PID然后打开任务管理器找到这个 PID 结束进程。我自己的习惯是开发时把端口写在 app.js 顶部统一管理避免和其他项目冲突。如果你在跑多个毕设项目把端口改成 3001、3002 是最快的办法。另外还有一种“假占用”是因为启动报错没退出进程还在等待先按 CtrlC 退出再重新启动。5.5 现象前端请求跨域失败后端收到了 OPTIONS 请求当前端页面和后端接口不在同一个端口时浏览器会发跨域请求。很多人在 Express 里只加了 app.use(cors())但还是报跨域错误后端日志里看到前端先发了一个 OPTIONS 请求。原因是跨域请求分为简单请求和预检请求。当请求头包含 Authorization 时浏览器会先发送 OPTIONS 请求询问服务器允不允许。解决方法是让 cors() 中间件处理所有 OPTIONS 请求或者直接允许所有来源并允许自定义请求头。app.use(cors({ origin: *, methods: [GET, POST, PUT, DELETE], allowedHeaders: [Content-Type, Authorization] }));这段配置里origin 设为 * 表示允许所有来源访问allowedHeaders 加上 Authorization 是必须的不然带 token 的请求在预检阶段就会被拦下来。写完这个配置重启服务器再看OPTIONS 请求会返回 204正常请求就不会被拦截了。6. 答辩前的验证顺序一套几分钟跑完的冒烟测试与数据填充技巧答辩前一天我会把整套系统按顺序过一遍。第一件事是用命令启动 Mysql 服务确认 net start mysql 执行成功。第二件事是检查 my.ini 里的端口默认 3306确认没有被别的服务占用。第三件事是启动 Express 服务访问健康检查接口确认 API 正常。第四件事是测登录先注册一个测试账号再用这个账号登录拿 token。第五件事是测商品列表用这个 token 请求需要鉴权的接口确认 auth 中间件没有拦截正常请求。这套流程可以用一个简单的 Node.js 脚本自动化省得每次手动拿浏览器试。// smoke.js const base http://localhost:3000; async function check() { const loginRes await fetch(${base}/api/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: admin, password: 123456 }) }); const loginData await loginRes.json(); console.log(登录接口:, loginData.code 0 ? 通过 : 失败); const listRes await fetch(${base}/api/products?page1pageSize5); const listData await listRes.json(); console.log(商品列表接口:, Array.isArray(listData.data.list) ? 通过 : 失败); } check();执行方式很简单在项目根目录运行 node smoke.js。脚本里 fetch 返回的是 Response 对象必须先调用 .json() 拿数据再判断业务字段。日志里会写明每个接口是否通过不再用肉眼看浏览器控制台。还有一个数据填充的细节。商品列表页如果只有两三条数据页面效果很单薄演示时也不好看。用一条 SQL 批量生成测试数据是最快的办法但要注意生成的商品名称不能重复不然展示起来很奇怪。我一般用一个递归 CTE 生成序列然后靠 RAND() 生成随机价格和销量。INSERT INTO products (category_id, name, price, stock, image, sales, status) SELECT 1, CONCAT(测试商品, n), ROUND(50 RAND() * 150, 2), FLOOR(10 RAND() * 100), /images/default.jpg, FLOOR(1 RAND() * 500), 1 FROM (SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5) t;这段 SQL 会插入 5 条测试商品name 字段带序号保证唯一。如果你想要 50 条把 UNION ALL 的子查询扩展到 50。RAND() 生成的随机数每次执行不一样所以多次执行也不会数据雷同。填充之后商品列表排序立刻能看到销量高低差异答辩演示流畅很多。做完这些项目就处在一个“稳定可复现”的状态。我在指导本科生的时候经常说毕设项目首要目标不是功能多而是随时能重新跑起来。Mysql 服务挂了能快速重启Node.js 依赖丢了能快速重装数据库表结构能随时重建这就足够了。我自己经历过几次答辩前环境坏掉的情况后来养成了这个习惯所有配置写进 my.ini 和 package.json所有初始化步骤写进 README不依赖任何手动记忆。希望帮到你。本文还有配套的精品资源点击获取
返回列表