☰
SpringBoot+MySQL宠物商城系统工程实践指南
2026/10/7 2:55:22 网站建设 项目流程

简介:这是一套基于SpringBoot+MySQL+HTML开发的宠物商城管理系统,面向计算机专业本科生及Java初学者,适用于毕业设计、课程设计与期末大作业等实践场景。系统覆盖商品展示、订单管理、用户中心、库存控制等核心电商功能,界面简洁美观,前后端代码完整,经严格调试可直接运行,无需额外修改。资源包共2个文件(1个ZIP源码包+1个SQL数据库脚本),总大小14.06MB;ZIP内含完整SpringBoot后端工程与HTML前端页面,SQL脚本支持MySql5.7+一键建库建表,配合IDEA、Maven、Navicat等主流工具即可快速部署。目前已有59人学习下载,资源由实战经验丰富的开发者整理,包含清晰目录结构、可运行配置说明及微信小程序对接基础支持,是掌握Java Web全栈开发流程的高性价比实践范例。

1. 为什么这个「基于SpringBoot+MySQL+HTML宠物商城管理系统」能拿高分?——它不是套壳Demo,而是把毕业设计里最常被忽略的「工程闭环能力」全打穿了

你翻过几十个GitHub上的“毕业设计模板”,发现它们要么是纯后端接口堆砌、前端用Thymeleaf硬塞几行HTML凑数;要么是Vue单页应用配个SpringBoot空壳,连登录态都靠localStorage硬扛。但真正让答辩老师眼睛一亮的,从来不是“用了SpringBoot”,而是你能把一个真实小商业场景里的数据流、状态流转、边界校验、部署落地全部串成一条线。这个宠物商城系统,表面看是卖猫粮狗粮,实则是一套完整的Java Web工程训练闭环:从MySQL建模时对「宠物品种-库存-订单-用户地址」四张表的外键约束设计,到SpringBoot里用@Valid+自定义ConstraintValidator拦住“0库存下单”这种业务逻辑漏洞;从HTML页面里用原生<form>+<input type="number">做前端防呆,到Nginx反向代理后静态资源分离的真实部署路径。它不炫技,但每一步都踩在本科毕设评分标准的得分点上——可运行、可验证、可解释、可维护。适合那些不想抄代码糊弄、又怕自己从零搭架构翻车的同学。


2. 用SpringBoot 2.7.x + MySQL 5.7 搭出最小可运行骨架:三步跑通首页加载

2.1 创建项目并锁定关键依赖版本(避坑SpringBoot 3.x兼容性雷区)

毕业设计最怕什么?不是功能写不出来,而是环境一换就报java.lang.NoClassDefFoundError: javax/servlet/Servlet。SpringBoot 3.x全面切换到Jakarta EE 9+命名空间,而MySQL Connector/J 8.0+默认用jakarta.*包,但很多老教材、IDE模板还卡在javax.*。我们选SpringBoot 2.7.18(LTS最后版)+ MySQL Connector/J 8.0.33,既保稳定又兼容新特性。用Spring Initializr生成基础项目时,勾选:

  • Spring Web
  • Spring Data JPA
  • MySQL Driver
  • Lombok(省掉getter/setter手写)
  • Thymeleaf(比纯HTML+JS更易整合后端数据)

提示:不要勾选Spring Security(毕设阶段先做明文登录,安全加固放第二轮),也不要选DevTools(答辩演示机没装IDEA,打包jar必须干净)。

生成后,在pom.xml中显式锁定MySQL驱动版本(防止Maven传递依赖拉错):

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency>

2.2 MySQL建库建表:用真实业务逻辑倒推字段设计(不是照搬ER图)

别直接复制网上的“宠物商城数据库设计图”。真实场景里,库存字段不能只叫stock,得拆成available_stock(可售)和locked_stock(已下单未支付)——否则并发下单时超卖无法避免。我们建4张核心表(SQL直接可执行):

-- 用户表:密码用BCrypt加密,不存明文 CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL UNIQUE, `password` varchar(100) NOT NULL, `phone` varchar(20), `address` varchar(200), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 宠物商品表:区分"活体"和"用品",用type字段控制展示逻辑 CREATE TABLE `pet_product` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `type` enum('LIVE','SUPPLY') NOT NULL COMMENT 'LIVE=活体, SUPPLY=用品', `price` decimal(10,2) NOT NULL, `available_stock` int NOT NULL DEFAULT 0, `locked_stock` int NOT NULL DEFAULT 0, `description` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单主表:status用int而非字符串,方便数据库索引 CREATE TABLE `order_master` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `total_amount` decimal(10,2) NOT NULL, `status` tinyint NOT NULL DEFAULT 0 COMMENT '0=待支付,1=已支付,2=已发货,3=已完成,4=已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 订单明细表:关联商品ID,记录下单时快照价格(防止商品调价影响历史订单) CREATE TABLE `order_detail` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_id` bigint NOT NULL, `product_id` bigint NOT NULL, `quantity` int NOT NULL, `snapshot_price` decimal(10,2) NOT NULL COMMENT '下单时商品快照价格', PRIMARY KEY (`id`), KEY `idx_order_id` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

参数说明:utf8mb4确保支持emoji(比如宠物昵称带🐶);tinyint存订单状态比varchar快3倍以上;所有外键关系暂不加FOREIGN KEY约束(毕设演示阶段优先保证SQL能一键执行,JPA层用@ManyToOne逻辑关联更可控)。

2.3 写一个Thymeleaf首页:用原生HTML语法嵌入后端数据(拒绝JS框架绑架)

很多同学用Vue写前端,结果答辩时npm install失败、node_modules体积爆炸。Thymeleaf是毕设友好型方案:HTML文件本身是合法网页,浏览器双击就能预览样式,后端注入数据只是加几个th:text属性。创建src/main/resources/templates/index.html:

<!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>宠乐购 - 首页</title> <link href="/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h1>欢迎来到宠乐购</h1> <!-- 商品列表:Thymeleaf遍历后端传来的List<PetProduct> --> <div class="row"> <div class="col-md-4 mb-3" th:each="product : ${products}"> <div class="card"> <div class="card-body"> <h5 class="card-title" th:text="${product.name}">商品名称</h5> <p class="card-text" th:text="${'¥' + #numbers.formatDecimal(product.price, 0, 2)}">价格</p> <p class="text-muted" th:text="${product.description}">描述</p> <a href="#" class="btn btn-primary" th:onclick="'location.href=\'/product/detail?id='+${product.id}+\''">查看详情</a> </div> </div> </div> </div> </div> <script src="/js/bootstrap.bundle.min.js"></script> </body> </html>

对应Controller只需返回ModelAndView:

@Controller public class HomeController { @Autowired private PetProductRepository productRepository; @GetMapping("/") public String home(Model model) { // 查询前8个商品(实际项目应分页,毕设阶段简化) List<PetProduct> products = productRepository.findTop8ByOrderByIdDesc(); model.addAttribute("products", products); return "index"; // 对应templates/index.html } }

逻辑说明:th:each是Thymeleaf核心指令,#numbers.formatDecimal处理价格格式化,th:onclick生成跳转链接——所有这些都不需要额外JS,纯服务端渲染。好处是:答辩现场断网也能打开HTML看样式,调试时直接System.out.println(products)就能确认数据是否查出。


3. 实现「下单不超卖」的核心业务逻辑:用数据库乐观锁+应用层校验双保险

3.1 数据库层:用UPDATE语句原子性扣减库存(比@Transactional更可靠)

很多人以为加个@Transactional就能防超卖,但事务只保证操作原子性,不解决并发读写冲突。真正的防线在SQL里:下单时不是先SELECT再UPDATE,而是直接用UPDATE ... SET available_stock = available_stock - ? WHERE id = ? AND available_stock >= ?。这样,如果库存不足,SQL影响行数为0,后端立刻感知失败。

@Repository public class PetProductRepositoryImpl implements PetProductRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public boolean deductStock(Long productId, Integer quantity) { String sql = "UPDATE pet_product SET available_stock = available_stock - :quantity " + "WHERE id = :productId AND available_stock >= :quantity"; int updated = entityManager.createNativeQuery(sql) .setParameter("productId", productId) .setParameter("quantity", quantity) .executeUpdate(); return updated > 0; // 返回true表示扣减成功 } }

参数说明:executeUpdate()返回影响行数,>=条件确保扣减前库存充足。此SQL在MySQL中会自动加行锁,无需手动SELECT ... FOR UPDATE。

3.2 应用层:用@Valid注解拦截非法请求(前端防不住,后端必须拦)

HTML表单可以被F12随意修改,所以后端必须二次校验。比如下单接口接收OrderCreateDTO,其中quantity必须大于0且为整数:

public class OrderCreateDTO { @NotNull(message = "商品ID不能为空") private Long productId; @Min(value = 1, message = "购买数量不能小于1") @Max(value = 999, message = "购买数量不能超过999") private Integer quantity; // getter/setter... } @RestController public class OrderController { @PostMapping("/order/create") public Result createOrder(@Valid @RequestBody OrderCreateDTO dto, BindingResult result) { if (result.hasErrors()) { return Result.fail(result.getFieldError().getDefaultMessage()); } // 执行扣库存、生成订单等逻辑... return Result.success(); } }

逻辑说明:@Valid触发JSR-303校验,BindingResult捕获错误。比在Controller里写if(dto.getQuantity() < 1)更规范,且错误信息可统一返回JSON。

3.3 前端防呆:用HTML5原生属性限制输入(降低用户误操作)

别小看<input type="number">,它能在提交前阻止非数字输入:

<!-- 在商品详情页的下单表单中 --> <form th:action="@{/order/create}" method="post"> <input type="hidden" name="productId" th:value="${product.id}"> <div class="mb-3"> <label>购买数量</label> <input type="number" name="quantity" min="1" max="999" value="1" class="form-control" required> </div> <button type="submit" class="btn btn-success">立即下单</button> </form>

注意:min/max是HTML5属性,浏览器会自动校验,但必须配合后端@Valid——因为用户可禁用JS或用Postman绕过前端。


4. 避坑:毕业设计中最常翻车的5个具体问题(血泪经验总结)

4.1 现象:启动时报错Failed to configure a DataSource

原因:application.yml里MySQL配置项写错层级,比如把spring.datasource.url写成spring.datasource.url:(多了冒号)或缩进不对。YAML对空格极其敏感。
解决:用在线YAML校验工具(如yamlchecker.com)粘贴配置;确保url、username、password在同一缩进层级;URL末尾必须加?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8。

4.2 现象:Thymeleaf页面显示??product.name??而不是真实数据

原因:Controller方法没加@ResponseBody或返回值类型不是String,导致Spring Boot误判为REST接口,返回JSON而非HTML视图。
解决:Controller类加@Controller(不是@RestController),方法返回String且不加@ResponseBody;检查templates目录是否在src/main/resources下(不是src/main/java)。

4.3 现象:MySQL中文乱码,插入数据变成????

原因:MySQL服务端字符集是latin1,而建表时没指定DEFAULT CHARSET=utf8mb4。
解决:

  1. 修改MySQL配置文件my.cnf(Linux)或my.ini(Windows):
[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
  1. 重启MySQL服务;
  2. 重建数据库:CREATE DATABASE pet_shop DEFAULT CHARSET utf8mb4;

4.4 现象:打包成jar后,静态资源(CSS/JS)404

原因:Spring Boot 2.7默认静态资源路径是/static,但你把bootstrap.min.css放在了src/main/resources/static/css/,而HTML里写的是<link href="/css/bootstrap.min.css">——少了一级static。
解决:两种方案任选其一:

  • 方案A(推荐):HTML中路径写全<link href="/static/css/bootstrap.min.css">;
  • 方案B:在application.yml中配置spring.web.static-path-pattern=/,让/css/直接映射到static/css/。

4.5 现象:登录后跳转首页,但用户名没显示在导航栏

原因:Thymeleaf中th:text="${session.user.username}",但Controller没把User对象存入Session。
解决:登录成功后手动存Session:

@PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password, HttpSession session) { User user = userService.findByUsername(username); if (user != null && passwordEncoder.matches(password, user.getPassword())) { session.setAttribute("user", user); // 关键!存入Session return "redirect:/"; } return "login"; }

5. 把系统真正跑起来:本地打包+Windows一键部署(答辩演示不求酷,但求稳)

5.1 用Maven命令打包成独立jar(不依赖外部Tomcat)

毕业设计答辩最怕什么?现场装Tomcat、配环境变量、改端口……Spring Boot内置Tomcat,打包成jar双击就能运行。在项目根目录执行:

mvn clean package -Dmaven.test.skip=true

说明:-Dmaven.test.skip=true跳过测试(毕设阶段单元测试非必需,避免因测试失败中断打包);生成的jar在target/pet-shop-0.0.1-SNAPSHOT.jar。

5.2 编写Windows批处理脚本(.bat),实现双击启动+日志分离

新建start.bat,内容如下:

@echo off title 宠乐购系统 echo 启动中... java -jar -Dfile.encoding=UTF-8 target\pet-shop-0.0.1-SNAPSHOT.jar > app.log 2>&1 pause

参数说明:-Dfile.encoding=UTF-8解决Windows CMD乱码;> app.log 2>&1把所有输出(包括错误)重定向到日志文件;pause防止窗口闪退。答辩时双击此bat,看到Started PetShopApplication in X.XXX seconds即成功。

5.3 配置Nginx反向代理(模拟真实部署,加分项)

虽然jar自带Web服务器,但加一层Nginx能体现工程素养。下载Nginx for Windows,修改conf/nginx.conf:

server { listen 80; server_name localhost; location / { proxy_pass http://localhost:8080; # Spring Boot默认端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 静态资源走本地,不经过Spring Boot location /static/ { alias D:/pet-shop/target/classes/static/; } }

逻辑说明:用户访问http://localhost,Nginx转发到http://localhost:8080;/static/路径的CSS/JS由Nginx直接返回,减轻Spring Boot压力。答辩时演示“关闭jar进程,网站依然能打开首页”(因为静态页缓存生效),老师会眼前一亮。

5.4 给答辩PPT配一张「系统架构图」(手绘风格更显真诚)

别用Visio画复杂微服务图。就画三层:

  • 用户层:浏览器图标 + “HTML/CSS/JS”文字
  • 应用层:Spring Boot Logo + “Controller/Service/Repository”标签
  • 数据层:MySQL Logo + “user/pet_product/order_master”三张表名
    用箭头标出数据流向,右下角写一行小字:“所有代码开源,部署仅需JDK8+MySQL5.7”。这张图不用技术深度,但清晰传达你理解了分层思想。

6. 我的三个硬核习惯:让毕设代码从「能跑」升级到「值得讲」

6.1 日志不打System.out.println(),用Lombok的@Slf4j注解+异步Appender

答辩老师问:“订单创建失败时,你怎么定位问题?” 如果你说“我看了控制台”,他会皱眉。专业做法是:用SLF4J打结构化日志,关键操作记录参数和耗时。Lombok让这事变得极简:

@Slf4j @Service public class OrderService { public Order createOrder(OrderCreateDTO dto) { long start = System.currentTimeMillis(); log.info("开始创建订单,参数:{}", dto); // 自动序列化dto // ... 业务逻辑 long cost = System.currentTimeMillis() - start; log.info("订单创建完成,ID={},耗时={}ms", order.getId(), cost); return order; } }

配置logback-spring.xml启用异步日志(避免I/O阻塞主线程):

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="FILE"/> </appender>

这样答辩时,你可以说:“所有操作都有日志ID,出问题时grep订单号就能定位全流程”。

6.2 数据库字段加@Column(nullable = false),让JPA生成的DDL和手写SQL保持一致

很多人建完表就扔,结果新增字段时忘记改实体类,导致NullPointerException。用JPA注解强制约束,让编译期就暴露问题:

@Entity @Table(name = "pet_product") public class PetProduct { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name", nullable = false, length = 100) private String name; @Column(name = "price", nullable = false, precision = 10, scale = 2) private BigDecimal price; // 其他字段... }

效果:当name为null时,JPA插入会直接抛DataIntegrityViolationException,而不是存入NULL后前端显示空白——这比运行时才发现更早拦截缺陷。

6.3 每个Controller方法加@ApiOperation注解,用Swagger自动生成接口文档

答辩时老师说:“你这个下单接口怎么调用?” 你掏出Postman截图?不如直接打开http://localhost:8080/swagger-ui.html。加两行依赖:

<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>

然后在Controller上加注解:

@RestController @RequestMapping("/order") @Api(tags = "订单管理", description = "创建、查询、取消订单") public class OrderController { @PostMapping("/create") @ApiOperation(value = "创建订单", notes = "需登录态,返回订单ID") public Result createOrder(@Valid @RequestBody OrderCreateDTO dto) { // ... } }

这样答辩时,你打开Swagger UI,点“Try it out”,填参数、点Execute,当场演示接口调用——比口头描述强十倍。而且文档随代码更新,不会出现“接口变了文档没改”的尴尬。

我带过十几届毕设学生,见过太多人花两周写功能,却在答辩前夜疯狂修bug。真正拉开差距的,不是你用了多少新技术,而是你有没有把每个环节的“确定性”做到极致:SQL能一键执行、jar包双击就跑、日志能精准溯源、接口文档自动生成。这套宠物商城系统,就是用最朴实的SpringBoot+MySQL+HTML,把工程细节抠到毫米级。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询