ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot宠物商城毕业设计实战:MySQL多表+原生HTML前端

SpringBoot宠物商城毕业设计实战:MySQL多表+原生HTML前端 简介本资源是一套基于SpringBootMySQLHTML开发的宠物商城管理系统面向计算机专业本科生及Java初学者适用于毕业设计、课程设计与期末大作业等实践场景。系统功能完整涵盖商品展示、订单管理、用户中心、库存控制与支付对接等核心模块界面简洁美观代码结构清晰经严格调试可直接运行显著降低二次开发门槛。压缩包共2个文件1个ZIP源码包1个SQL数据库脚本总大小14.06MBZIP内含前后端完整代码、IDEA项目配置及微信小程序适配支持SQL脚本提供建库建表语句与初始数据便于快速初始化环境。目前已有59人学习下载资源由实战经验丰富的开发者整理附带Navicat数据库操作指引与Maven依赖说明特别适合希望掌握主流Java Web全栈开发流程的学习者从环境搭建到功能部署形成闭环实践路径。1. 这不是又一个“增删改查”Demo基于 SpringBoot MySQL HTML 的宠物商城系统真能当毕业设计交上去你翻过 GitHub 上那些标着「毕设」「Java 毕业设计」的仓库——十有八九是用户管理商品管理订单管理三件套连前端都用 Thymeleaf 拼个th:each就完事后台 Controller 里塞满Autowired private XxxService连事务边界在哪都懒得画。但这个「宠物商城管理系统」不一样它没用任何前端框架Vue/React纯 HTML CSS JS 写了完整的商品详情页轮播、购物车实时计数、订单状态机流转待付款→已发货→已完成、甚至带宠物品种筛选的二级联动下拉猫英短/布偶/缅因狗金毛/泰迪/柯基。MySQL 里建了 9 张表含pet_category分类树形结构、pet_skuSKU 级别库存与价格、order_item支持同一订单混搭活体宠物食品玩具还配了application-prod.yml和application-dev.yml双环境配置。我拿它帮三个学生过盲审导师问得最狠的是「活体宠物怎么退换货」——系统里真写了「7 天无理由不适用但提供 24 小时健康保障期」的业务规则校验逻辑。适合 Java 初学刚撸完《Spring Boot 实战》第 5 章、想交一份让答辩老师愿意多问两句的毕业设计的同学。别再抄「图书管理系统」了宠物商城的业务细节足够撑起 30 页论文的「需求分析」和「数据库设计」章节。2. 从零跑通项目结构、依赖配置与三步启动法2.1 项目骨架与核心模块划分这不是单模块 Maven 工程。解压后你会看到标准的 SpringBoot 多模块结构pet-shop-parentpom.xml 声明 parent统一管理 SpringBoot 2.7.18 版本、Lombok、MyBatis-Pluspet-shop-common工具类IdWorker雪花 ID 生成器、ResultT统一响应封装、PetEnum枚举定义pet-shop-apiController 层含/api/pet、/api/order、/api/user三大 REST 接口组pet-shop-serviceService 层关键逻辑在OrderServiceImpl.java创建订单时校验库存、扣减、生成物流单号pet-shop-daoMapper 接口 XMLPetMapper.xml里有resultMap显式映射pet_category.name到PetDO.categoryName提示pet-shop-dao模块的pom.xml中scopeprovided/scope仅对pet-shop-service生效避免 MyBatis-Plus 与 SpringBoot Starter 冲突。这是很多同学本地跑不通的根源——他们直接把 Mapper 放进api模块导致MapperScan扫描失效。2.2 MySQL 数据库初始化建库、建表、插初始数据项目根目录下有sql/pet_shop_init.sql但不能直接全量执行。必须分三步手动创建数据库并指定编码Windows 下 MySQL 8.0 必须显式声明CREATE DATABASE pet_shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意utf8mb4是硬性要求。宠物名称含 emoji如或生僻字如「黇」鹿时utf8会报错Incorrect string value。这是血泪经验——我有个学生在答辩现场演示「宠物领养」功能输入「黇鹿」直接 500导师当场皱眉。执行建表语句sql/pet_shop_init.sql前半部分-- 表 pet_category支持三级分类宠物大类→品种→细分特征 CREATE TABLE pet_category ( id BIGINT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT 分类名称, parent_id BIGINT DEFAULT 0 COMMENT 父级ID0为一级, level TINYINT NOT NULL COMMENT 层级1大类2品种3特征, sort_order INT DEFAULT 0 COMMENT 排序权重 ); -- 表 pet_skuSKU 级别区分活体/食品/用品 CREATE TABLE pet_sku ( id BIGINT PRIMARY KEY, pet_id BIGINT NOT NULL COMMENT 关联 pet_info.id, sku_code VARCHAR(32) NOT NULL UNIQUE COMMENT SKU编码如 CAT_BR-001, price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1上架0下架 );关键点pet_sku.stock字段类型为INT非BIGINT因为宠物商城单 SKU 库存不会超 2147483647sku_code加了UNIQUE约束防止人工录入重复。插入基础数据sql/pet_shop_init.sql后半部分-- 插入一级分类猫、狗、兔、鸟 INSERT INTO pet_category (id, name, parent_id, level, sort_order) VALUES (1, 猫, 0, 1, 1), (2, 狗, 0, 1, 2), (3, 兔, 0, 1, 3); -- 插入二级分类猫→英短、布偶狗→金毛、泰迪 INSERT INTO pet_category (id, name, parent_id, level, sort_order) VALUES (101, 英短, 1, 2, 1), (102, 布偶, 1, 2, 2), (201, 金毛, 2, 2, 1), (202, 泰迪, 2, 2, 2);注意parent_id必须严格对应一级分类id否则前端「品种筛选」下拉将为空。我见过学生把parent_id写成1字符串MySQL 隐式转换失败页面 JS 报Cannot read property length of null。2.3 启动服务三步验证法绕过常见启动失败不要直接mvn spring-boot:run按顺序执行检查application-dev.yml的数据库连接pet-shop-api/src/main/resources/application-dev.ymlspring: datasource: url: jdbc:mysql://localhost:3306/pet_shop?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password # ← 这里必须填你本地 MySQL 密码 driver-class-name: com.mysql.cj.jdbc.Driver关键参数说明serverTimezoneAsia/Shanghai解决The server time zone value XXX is unrecognized错误Windows 系统尤其高频allowPublicKeyRetrievaltrueMySQL 8.0 默认关闭公钥检索不加此参数会报Public Key Retrieval is not alloweduseSSLfalse开发环境禁用 SSL避免证书配置麻烦生产环境必须开确认 MyBatis-Plus 分页插件已启用pet-shop-api/src/main/java/com/petshop/config/MybatisPlusConfig.javaBean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 必须注册 PaginationInnerInterceptor否则 PageHelper 不生效 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }这是很多同学「分页失效」的元凶——他们删了这行以为Page对象能自动分页结果select * from pet_info全表查出 10 万条。启动并验证端口与接口cd pet-shop-api mvn clean compile mvn spring-boot:run -Dspring.profiles.activedev启动成功后立刻验证访问http://localhost:8080/actuator/health→ 返回{status:UP}访问http://localhost:8080/api/pet/list?page1size10→ 返回 JSON 数组非空访问http://localhost:8080/static/index.html→ 显示首页注意路径是/static/非/如果卡在Starting Servlet web server大概率是 MySQL 连接失败如果返回 404检查pet-shop-api/src/main/resources/static/目录是否存在index.html项目用静态资源模式非 Thymeleaf 模板。3. 前端 HTML 的真实工作流从 index.html 到购物车结算3.1 首页结构解析为什么不用 Vue 却能动态渲染pet-shop-api/src/main/resources/static/index.html是整个前端的入口。它没用任何框架靠原生 JS AJAX 实现动态加载!-- 商品列表容器 -- div idproduct-list classrow !-- JS 动态插入 div classcol-md-4.../div -- /div !-- 分页控件 -- nav aria-labelPage navigation ul idpagination classpagination/ul /nav关键 JS 文件在static/js/index.js// 1. 页面加载时发起请求 document.addEventListener(DOMContentLoaded, function() { loadProducts(1, 10); // 加载第1页每页10条 }); // 2. 加载商品列表调用后端 /api/pet/list 接口 function loadProducts(page, size) { fetch(/api/pet/list?page${page}size${size}) .then(response response.json()) .then(data { const container document.getElementById(product-list); container.innerHTML ; // 清空旧内容 data.records.forEach(pet { const card div classcol-md-4 mb-4 div classcard h-100 img src${pet.coverUrl} classcard-img-top alt${pet.name} div classcard-body h5 classcard-title${pet.name}/h5 p classcard-text${pet.description.substring(0, 50)}.../p div classd-flex justify-content-between align-items-center span classtext-danger¥${pet.price}/span button onclickaddToCart(${pet.id}) classbtn btn-sm btn-primary加入购物车/button /div /div /div /div ; container.insertAdjacentHTML(beforeend, card); }); renderPagination(data); // 渲染分页 }); }逻辑说明data.records是后端PagePetVO的records字段MyBatis-Plus 自动注入分页信息pet.coverUrl是数据库pet_info.cover_url字段值为相对路径如/images/cat/british-shorthair.jpg图片需放在static/images/目录onclickaddToCart(${pet.id})是内联事件虽不推荐但胜在简单——毕业设计无需过度工程化3.2 购物车实现localStorage 后端同步双保险购物车数据存在两处前端 localStorage存临时选中项关页面不丢后端 session存登录用户专属购物车CartService.java中HttpSession.setAttribute(cart, cartList)static/js/cart.js核心逻辑// 添加到购物车前端 function addToCart(petId) { let cart JSON.parse(localStorage.getItem(cart) || []); const existing cart.find(item item.petId petId); if (existing) { existing.quantity 1; // 同一商品多次添加数量累加 } else { cart.push({ petId: petId, quantity: 1 }); } localStorage.setItem(cart, JSON.stringify(cart)); updateCartBadge(); // 更新顶部购物车图标角标 } // 提交订单前端触发后端创建 function submitOrder() { const cart JSON.parse(localStorage.getItem(cart) || []); if (cart.length 0) { alert(购物车为空); return; } // 发送 POST 请求到 /api/order/create fetch(/api/order/create, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ items: cart }) // 传入 [{petId:1,quantity:2},...] }) .then(response response.json()) .then(data { if (data.code 200) { alert(订单创建成功订单号${data.data.orderNo}); localStorage.removeItem(cart); // 清空本地购物车 updateCartBadge(); window.location.href /static/order-success.html?orderNo data.data.orderNo; } else { alert(下单失败 data.msg); } }); }参数说明items数组中的petId必须是pet_info.id非pet_sku.id后端OrderServiceImpl.createOrder()会根据petId查询默认 SKUorder-success.html是静态成功页通过 URL 参数orderNo渲染订单号避免后端重定向3.3 订单状态机用数据库字段驱动前端显示订单状态流转不是靠前端按钮控制而是由后端业务逻辑更新order_info.status字段status含义前端显示文案1待付款span classbadge bg-warning待付款/span2已付款span classbadge bg-info已付款/span3已发货span classbadge bg-primary已发货/span4已完成span classbadge bg-success已完成/span5已取消span classbadge bg-secondary已取消/spanstatic/js/order-detail.js根据status渲染不同操作按钮function renderOrderActions(order) { const actions document.getElementById(order-actions); actions.innerHTML ; switch(order.status) { case 1: actions.innerHTML button onclickpayOrder(${order.orderNo}) classbtn btn-success立即支付/button button onclickcancelOrder(${order.orderNo}) classbtn btn-outline-danger取消订单/button ; break; case 2: actions.innerHTML span classtext-muted等待商家发货.../span; break; case 3: actions.innerHTML a href/static/logistics.html?orderNo${order.orderNo} classbtn btn-primary查看物流/a; break; } }关键点状态变更必须走后端接口如payOrder()调用/api/order/pay前端不能直接fetch(/api/order/updateStatus?status2)—— 这是毕业设计答辩时导师必问「如何防止用户篡改状态」的考点。4. 避坑指南五个让答辩老师当场皱眉的真实翻车现场4.1 现象启动时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因pom.xml中 MySQL 驱动版本与 MySQL 服务器版本不匹配。项目用mysql:mysql-connector-java:8.0.33但你的本地 MySQL 是 5.7.x。解决方案 A推荐升级本地 MySQL 到 8.0官网下载.msi安装包勾选Add to PATH方案 B降级驱动在pet-shop-parent/pom.xml中修改dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version !-- MySQL 5.7 兼容版本 -- /dependency注意5.1.49 不支持allowPublicKeyRetrievaltrue需从url中删除该参数。4.2 现象首页商品图片全部 404控制台报GET http://localhost:8080/images/cat/british-shorthair.jpg 404原因图片文件未放入正确路径。static/index.html中src${pet.coverUrl}的值来自数据库但static/images/目录下没有对应文件。解决创建目录pet-shop-api/src/main/resources/static/images/cat/、/dog/、/rabbit/将项目doc/images/下的示例图片共 12 张按分类复制进去检查数据库pet_info.cover_url字段值是否与实际路径一致如cat/british-shorthair.jpg4.3 现象点击「加入购物车」无反应控制台报Uncaught ReferenceError: addToCart is not defined原因index.html中script src/js/index.js/script的路径错误。SpringBoot 静态资源默认从static/目录映射/js/index.js实际应为/static/js/index.js。解决修改index.html中的 script 标签!-- 错误 -- script src/js/index.js/script !-- 正确 -- script src/js/index.js/script !-- SpringBoot 默认 static 映射为根路径 --验证方法直接浏览器访问http://localhost:8080/js/index.js应返回 JS 源码。4.4 现象下单后订单状态一直是「待付款」物流页打不开原因OrderServiceImpl.createOrder()中调用了logisticsService.generateTrackingNo()但logisticsServiceBean 未注入Service注解缺失或包扫描路径错误。解决检查pet-shop-service/src/main/java/com/petshop/service/impl/LogisticsServiceImpl.java是否有Service注解检查pet-shop-api/src/main/java/com/petshop/PetShopApplication.java的SpringBootApplication是否扫描到com.petshop.service包SpringBootApplication(scanBasePackages com.petshop) public class PetShopApplication { ... }如果LogisticsServiceImpl在com.petshop.logistics包下需显式添加scanBasePackages。4.5 现象搜索「金毛」返回空但数据库pet_info.name确实有「金毛寻回犬」原因PetMapper.xml中的模糊查询 SQL 写成了name LIKE %${name}%拼接 SQL非预编译且name参数未加%符号。解决修改PetMapper.xml中的select语句!-- 错误写法 -- WHERE name LIKE %${name}% !-- 正确写法使用 CONCAT 防 SQL 注入 -- WHERE name LIKE CONCAT(%, #{name}, %)同时确保 Controller 中RequestParam String name传入的是纯关键词如金毛而非金毛%。5. 毕业设计加分技巧三处可深挖的「论文亮点」与答辩话术5.1 数据库设计亮点用「品种树形结构」替代扁平分类表很多同学的「图书分类」就一张表category(name, parent_id)但宠物商城的pet_category表增加了level字段1大类2品种3特征并在PetCategoryService.listByLevel()中实现了「按层级查所有品种」的递归查询// 查询所有二级品种猫/狗下的具体品种 public ListPetCategory listVarieties() { QueryWrapperPetCategory wrapper new QueryWrapper(); wrapper.eq(level, 2).orderByAsc(sort_order); return this.list(wrapper); }答辩话术“老师我在数据库设计时没有简单用category_id关联而是构建了三层分类树。这样做的好处是前端可以独立加载‘猫’下的所有品种英短、布偶再根据选中的品种加载其细分特征如英短的‘蓝眼’‘银渐层’避免一次查出 200 个分类造成前端卡顿。论文第 3.2 节的 ER 图中我特别标注了level字段的业务含义。”5.2 业务逻辑亮点订单创建时的「库存预占」与「幂等性」处理OrderServiceImpl.createOrder()方法中关键逻辑不是简单update pet_sku set stock stock - ?而是用SELECT FOR UPDATE锁定 SKU 行防止超卖检查stock requiredQuantity执行UPDATE并记录日志含订单号、SKU、扣减量使用 Redis 缓存订单号拦截重复提交if (redisTemplate.hasKey(order: orderNo)) throw new BizException(重复提交)答辩话术“老师我在订单创建环节做了两个防护一是数据库行锁保证高并发下库存不超卖二是 Redis 幂等校验防止用户手抖连点两次‘提交订单’。虽然毕业设计不强调高并发但这个设计体现了我对电商核心链路的理解。代码在pet-shop-service/src/main/java/com/petshop/service/impl/OrderServiceImpl.java第 89 行开始。”5.3 前端交互亮点用原生 JS 实现「无刷新地址栏跳转」static/js/category.js中点击「猫→英短」时URL 从/static/index.html变为/static/index.html?category101但页面不刷新// 监听 URL 参数变化 window.addEventListener(popstate, function(e) { const params new URLSearchParams(window.location.search); const categoryId params.get(category); if (categoryId) { loadProductsByCategory(categoryId, 1, 10); } }); // 点击品种时更新 URL不刷新 function navigateToCategory(id, name) { const url ?category${id}name${encodeURIComponent(name)}; history.pushState({ category: id }, , url); loadProductsByCategory(id, 1, 10); }答辩话术“老师我没有用 Vue Router而是用原生history.pushState实现了 SPA 效果。这样既满足了‘前后端分离’的现代 Web 要求又避免了引入框架带来的学习成本。您看点击品种后地址栏变了但页面是局部刷新的——这在论文第 4.5 节的‘前端技术选型’中有详细对比。”从那以后我每次帮学生改毕设都会强制他们在README.md里写清三点1数据库初始化的三步命令2application-dev.yml必改的三个参数3首页图片路径的映射关系。不是怕他们不会是怕答辩前两小时他们对着 404 图片抓狂。希望帮到你。本文还有配套的精品资源点击获取
返回列表