SpringBoot+MySQL+HTML宠物商城毕业设计实战指南
发布时间:2026/9/26 17:59:41来源:尧图网络
简介一份基于Spring Boot、MySQL和HTML实现的宠物商城管理系统属于高分毕业设计项目适合Java方向学生用于毕业设计、课程设计或期末大作业等场景。系统功能完善涵盖宠物商品展示、订单管理、用户管理及库存管理等核心模块界面简洁美观操作流程清晰完整体现了Spring Boot快速开发、MySQL数据存储与HTML页面渲染的技术栈整合。压缩包内共2个文件分别为ZIP项目源码包和SQL数据库脚本总大小约14.06MB源码包集成前端页面、后端业务逻辑及部署配置SQL脚本则用于创建数据表并初始化示例数据可直接还原运行环境。该项目当前已有59人学习或下载经严格调试确保开箱即用拿到后即可部署演示。对于需要快速获得一套完整商城系统作为参考的开发者而言它既可直接用于毕设答辩又能作为功能拓展和技术学习的扎实蓝本。1. 宠物商城管理系统SpringBootMySQLHTML这套组合到底能做出什么SpringBootMySQLHTML的宠物商城管理系统是这两年毕业设计里出现频率极高的一类单体Web项目后端一套SpringBoot接口提供业务能力MySQL负责存宠物、用户、订单这些核心数据前端用HTML页面配合Thymeleaf模板引擎做展示和交互。把它做成“高分毕业设计”的关键不是功能堆得多而是能不能把一条主链路讲清楚、跑通、扛得住追问。很多同学拿到题目后的第一反应是去搜现成源码结果下回来的项目要么基于老SSH框架依赖缺一堆要么拆成微服务本地根本起不来。这个题目的价值恰恰在于它足够收敛单体架构、三层结构、单一数据库从建表到页面渲染每一步都能在一个月内亲手做完。你不需要在生产级电商的并发、缓存、分布式事务上较劲但要把用户注册登录、宠物展示、购物车、下单、订单管理这条闭环做到能演示、能解释、能扩展。这篇文章按一条可复现的路径来讲先讲为什么选这套技术栈、数据库怎么设计再落到后端接口和前端页面怎么写然后是五个高频踩坑最后是一套答辩前可执行的验证方法。适合两类人一类是正在做毕设、需要一个可靠骨架的学生另一类是想快速搭一个小型商城原型验证业务的开发者。2. SpringBootMySQL的选型逻辑与数据库初始化先搞清楚为什么是这三样2.1 为什么SpringBootMySQLHTML是毕业设计的安全牌先回答一个很多人在开题时就会被问住的问题为什么不用SSM为什么不用Vue前后端分离这个系统的定位是“毕业设计级商城”不是“生产级电商”。SpringBoot相比SSM最大的优势是省掉了大量XML配置内置Tomcat一个java -jar就能跑起来。老师看你的部署过程时不需要看你调半天web.xml而是看你能否讲清楚自动配置和starter机制这是SpringBoot面试题里最常考的点放在答辩里也是加分项。MySQL的选择理由更直白它是目前简历和课程里最通用的关系型数据库可视化工具多出问题能搜到的解决方案也最多。这里建议直接用MySQL 8.x而不是5.7因为新版默认认证插件是caching_sha2_password虽然配置上多个步骤但更能体现你跟进过环境变化。数据库连接池用HikariCPSpringBoot 2.x默认内置不用额外引入。至于HTML为什么不用前后端分离核心原因是单体演示成本低。Vue工程要启动node服务、要处理跨域、要把打包产物和SpringBoot合并这一套流程对毕设来说时间成本偏高。而用Thymeleaf服务端渲染一个Controller方法返回一个页面名字数据通过Model传进HTML模板整个过程都在同一个应用里演示时只需要确保SpringBoot这一个进程活着。这套组合的边界也要心里有数并发能力有限、页面状态靠session或参数传递但这些在论文的“不足与展望”里如实写反而比硬吹更有说服力。维度SSM JSPSpringBoot ThymeleafSpringBoot Vue前后端分离配置成本高多个XML低自动配置中等需要处理跨域和打包演示过程需要容器部署单进程直接跑前端后端两个进程答辩提问点框架整合细节自动配置与starter鉴权与跨域方案适合作业周期偏紧最合适时间充裕可选2.2 数据库设计与建表SQL用户、宠物、订单、购物车七张表数据库设计是答辩时老师盯得最紧的部分。宠物商城不需要花哨的表但表之间的关系和字段约束要经得起追问。我一般会拆成七张表category品类、pet宠物、user用户、cart购物车、orders订单主表、order_item订单明细、favorite收藏。收藏表不是核心链路但加上它能让论文里的功能列表多一个亮点。-- 品类表 CREATE TABLE category ( id int NOT NULL AUTO_INCREMENT COMMENT 品类ID, name varchar(50) NOT NULL COMMENT 品类名称, sort int DEFAULT 0 COMMENT 排序值越小越靠前, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物品类; -- 宠物表 CREATE TABLE pet ( id int NOT NULL AUTO_INCREMENT COMMENT 宠物ID, name varchar(100) NOT NULL COMMENT 宠物名称, category_id int NOT NULL COMMENT 所属品类ID, price decimal(10,2) NOT NULL COMMENT 售价, stock int NOT NULL DEFAULT 0 COMMENT 库存, image varchar(255) DEFAULT NULL COMMENT 图片地址, description text COMMENT 描述, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1上架 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物信息; -- 用户表 CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码密文存储, phone varchar(20) DEFAULT NULL COMMENT 手机号, address varchar(255) DEFAULT NULL COMMENT 收货地址, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;-- 购物车表 CREATE TABLE cart ( id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL COMMENT 用户ID, pet_id int NOT NULL COMMENT 宠物ID, quantity int NOT NULL DEFAULT 1 COMMENT 数量, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车; -- 订单主表 CREATE TABLE orders ( id int NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id int NOT NULL COMMENT 用户ID, total_price decimal(10,2) NOT NULL COMMENT 总金额, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待支付 1已支付 2已发货 3已完成, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表; -- 订单明细表 CREATE TABLE order_item ( id int NOT NULL AUTO_INCREMENT, order_id int NOT NULL COMMENT 订单ID, pet_id int NOT NULL COMMENT 宠物ID, pet_name varchar(100) NOT NULL COMMENT 下单时的宠物名称快照, price decimal(10,2) NOT NULL COMMENT 下单时的单价快照, quantity int NOT NULL COMMENT 数量, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细;这里有两个设计点要说明。第一order_item里冗余了pet_name和price两个快照字段这是故意为之——如果宠物改价或改名历史订单里的金额和名称不能跟着变这在订单类系统里是基础要求答辩时可以主动讲出来。第二所有表都用utf8mb4而不是utf8因为utf8在MySQL里最多存3字节遇到emoji或生僻字会变成问号。然后是application.yml。这个文件是SpringBoot项目的命门我见过太多项目栽在数据库连接参数上。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pet_shop? useUnicodetruecharacterEncodingutf8 useSSLfalseallowPublicKeyRetrievaltrue serverTimezoneAsia/Shanghai username: root password: 你的密码 thymeleaf: cache: false mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl几个参数值得单独说。useSSLfalse是因为本地开发不需要SSL加密连接不关掉会看到大量SSL警告日志。allowPublicKeyRetrievaltrue是配合MySQL 8的caching_sha2_password用的不加这个启动时会直接报Public Key Retrieval is not allowed。serverTimezoneAsia/Shanghai则是解决数据库时间比本地晚8小时的问题。这些参数如果一步到位配好后面的时间能省出一大截。3. 用SpringBoot把商城后端跑起来核心接口与MyBatis-Plus落地3.1 用MyBatis-Plus写宠物表增删改查实体类、Mapper与分页插件后端持久层我首选MyBatis-Plus而不是原生MyBatis因为单表CRUD和分页这两件事MyBatis-Plus可以直接省掉大量重复SQL编写。这是血泪经验换来的选择拿到的旧项目里光一个PetMapper的XML就写了十几个标签而MyBatis-Plus里这些操作基本都是现成的。实体类直接映射宠物表注意字段名和表字段之间的对应关系Data TableName(pet) public class Pet { TableId(type IdType.AUTO) private Integer id; private String name; TableField(category_id) private Integer categoryId; private BigDecimal price; private Integer stock; private String image; private String description; TableField(status) private Integer status; private LocalDateTime createTime; }说明几点TableName指定表名当实体类名和表名不一致时必须写。TableId标注主键type IdType.AUTO表示数据库自增。TableField(category_id)用来处理Java驼峰字段和数据库下划线字段的映射——虽然MyBatis-Plus默认开了驼峰映射但显式标注能让代码更清晰也避免字段改名时出问题。Mapper层就更简单了继承BaseMapper就拥有了单表的所有基础方法Mapper public interface PetMapper extends BaseMapperPet { }这里不要觉得太简单就没价值。面答时候老师问“你怎么实现增删改查的”你可以回答“单表操作由MyBatis-Plus的BaseMapper提供复杂查询用LambdaQueryWrapper构造条件必要时写XML”。这个回答比“全靠复制粘贴”有力得多。分页是商城类系统的标配能力但MyBatis-Plus的分页有个前置条件必须注册分页拦截器否则Page对象返回的records为空total永远是0。这个坑几乎人人会踩细节放在第5章这里先把配置写好Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }PaginationInnerInterceptor是MyBatis-Plus分页插件DbType.MYSQL告诉它按MySQL的方言拼接LIMIT语句。不引入这个BeanselectPage方法不会生效。3.2 Controller层与Service层怎么写统一返回体和三个核心接口后端接口设计决定了前端对接时改不改代码。我习惯从第一行代码就引入统一返回体而不是每个接口各自返回Map或裸对象。统一返回体的好处是前端只需要判断一个code字段不用针对每个接口做特殊处理。Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT fail(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }然后在Controller里按三层结构往下走。这里的核心链路是宠物分页查询对应商城首页的商品列表RestController RequestMapping(/api/pet) public class PetController { Autowired private PetService petService; GetMapping(/page) public ResultIPagePet page( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword) { return Result.ok(petService.pageQuery(pageNum, pageSize, keyword)); } }Controller只做参数接收和结果包装真正的业务逻辑在Service里。pageNum和pageSize都有默认值keyword可选这种设计让接口在没有参数时也能直接访问方便调试。Service实现类里的核心是条件构造器public IPagePet pageQuery(Integer pageNum, Integer pageSize, String keyword) { PagePet page new Page(pageNum, pageSize); LambdaQueryWrapperPet wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Pet::getName, keyword) .eq(Pet::getStatus, 1) .orderByDesc(Pet::getCreateTime); return petMapper.selectPage(page, wrapper); }LambdaQueryWrapper的两个关键点like方法的第一个参数是boolean当keyword为空时不拼接这个条件这是避免动态SQL出错的标准写法eq(Pet::getStatus, 1)是只查上架商品下架的商品不能出现在前台列表里。除了分页登录和购物车是两个避不开的接口。登录我建议用POST JSON体提交密码用BCrypt加密存储不要用MD5——老师现在对MD5的批评已经成条件反射了。购物车加购的逻辑要注意幂等性同一个用户往购物车加同一个宠物应该累加数量而不是新增一行记录。public void addCartItem(Integer userId, Integer petId, Integer quantity) { Cart cart cartMapper.selectOne(new LambdaQueryWrapperCart() .eq(Cart::getUserId, userId) .eq(Cart::getPetId, petId)); if (cart ! null) { cart.setQuantity(cart.getQuantity() quantity); cartMapper.updateById(cart); } else { Cart newCart new Cart(); newCart.setUserId(userId); newCart.setPetId(petId); newCart.setQuantity(quantity); cartMapper.insert(newCart); } }selectOne这个方法的潛在风险是如果查出来多行记录它会直接报错。所以这里依赖数据库里(user_id, pet_id)的联合唯一索引没有唯一索引就不要用selectOne查业务数据。这里的索引在2.2节的建表语句里没有体现你要在cart表上补一个ALTER TABLE cart ADD UNIQUE KEY uk_user_pet (user_id, pet_id);还有一个答辩加分项值得提如果项目里涉及文件上传比如宠物图片可以加一个全局过滤器统一处理请求参数里的XSS风险。这不是核心链路但能体现安全意识。实现方式是实现OncePerRequestFilter包装HttpServletRequest的getParameter方法做转义然后通过FilterRegistrationBean注册过滤路径。这块放到论文“系统安全性”一节里写答辩时一笔带过即可别展开太深否则容易被追问细节。4. 用HTMLThymeleaf做商城前端页面渲染与接口对接4.1 宠物列表页的HTML模板从静态页面到Thymeleaf动态渲染前端部分最容易让人迷惑的是目录结构。SpringBoot里静态资源放在src/main/resources/static下比如css、js、图片访问时不需要加static前缀。Thymeleaf模板放在src/main/resources/templates下Controller返回的字符串就是templates目录下的文件名。这个约定搞清楚之后页面渲染就顺了。先写一个完整的HTML页面骨架标准HTML5结构注意langzh-cn和charsetutf-8是必须的否则页面可能出现中文乱码!DOCTYPE html html langzh-cn xmlns:thhttp://www.thymeleaf.org head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title宠物商城 - 首页/title link th:href{/css/style.css} relstylesheet /head body header classheader h1宠物商城/h1 nav a th:href{/index}首页/a a th:href{/cart}购物车/a a th:href{/order}我的订单/a /nav /header main classcontainer div classpet-grid th:eachpet : ${page.records} div classpet-card img th:src${pet.image} th:alt${pet.name} classpet-image h3 th:text${pet.name}宠物名称/h3 p classprice th:text¥ ${pet.price}¥0.00/p button th:onclickaddToCart( ${pet.id} )加入购物车/button a th:href{/detail/{id}(id${pet.id})} classbtn查看详情/a /div /div /main /body /html这段模板里有一堆Thymeleaf语法逐一说明。xmlns:th是命名空间声明让编辑器能识别th:开头的属性。th:href{/css/style.css}是URL表达式最终会生成带上下文路径的完整地址不会因为部署路径变化而404。th:eachpet : ${page.records}是循环渲染等价于Java里的for (Pet pet : page.getRecords())。th:text${pet.name}是输出变量值注意th:text会转义HTML特殊字符比直接拼接字符串安全。页面数据从哪来Controller里返回视图名并塞入ModelGetMapping(/index) public String index( Model model, RequestParam(defaultValue 1) Integer pageNum) { IPagePet page petService.pageQuery(pageNum, 8, null); model.addAttribute(page, page); return index; }这里返回的字符串index对应templates/index.html。model.addAttribute(page, page)相当于request.setAttributeThymeleaf里用${page.records}取自page对象里的records字段。每页8条数据这是商城首页常见的栅格数量。需要注意的一点这个Controller返回的是页面而不是JSON所以类上不能用RestController而是用Controller。如果两个注解混用前端会收到一个JSON字符串而不是HTML页面。这是SpringBoot初学者最容易犯的错。4.2 登录注册与购物车用Ajax把HTML页面和后端接口打通列表页是服务端渲染但登录、加购这类操作需要局部刷新常规做法是jQuery Ajax。先写登录页面里的核心脚本function login() { var username $(#username).val(); var password $(#password).val(); if (!username || !password) { alert(用户名和密码不能为空); return; } $.ajax({ url: /api/user/login, type: POST, contentType: application/json, data: JSON.stringify({ username: username, password: password }), success: function (res) { if (res.code 200) { sessionStorage.setItem(userId, res.data.id); sessionStorage.setItem(username, res.data.username); location.href /index; } else { alert(res.message); } }, error: function () { alert(网络异常请稍后重试); } }); }登录成功后把userId和username放进sessionStorage后续加购、下单接口就从sessionStorage取用户信息。这里不用localStorage的原因很简单关闭浏览器标签页后sessionStorage自动清空更符合登录态的预期。加购按钮的事件绑定和Ajax请求要注意传参格式。后端接口接收JSON体前端就必须设置contentType为application/json然后用JSON.stringify把对象序列化成字符串。很多对不上接口的情况都是因为前端用了表单格式提交。购物车页面的渲染要比列表页稍复杂一些因为要展示“哪只宠物、单价、数量、小计”最后算出总价。这里我给一个可以抄的模板片段tr th:eachitem : ${cartItems} td th:text${item.petName}宠物名/td td th:text¥ ${item.price}¥0.00/td td input typenumber th:value${item.quantity} min1 th:onchangeupdateQuantity( ${item.cartId} , this.value) /td td th:text¥ ${item.price * item.quantity}¥0.00/td td button th:onclickremoveFromCart( ${item.cartId} )删除/button /td /tr这里th:text${item.price * item.quantity}直接在模板里做运算是Thymeleaf支持的标准表达式能力。注意item里封装了cart表的id和pet表的name、price的联表查询结果所以前端拿到的不是一个单纯的Cart对象而是一个CartVO。这就是代码里常见的VO模式——把页面上需要展示的多个表字段拼成一个视图对象避免前端写多表关联逻辑。订单创建是前端交互链路里最敏感的一步它涉及库存扣减和事务。后端Service方法要加Transactional注解Transactional(rollbackFor Exception.class) public void createOrder(Integer userId) { ListCart cartList cartMapper.selectList( new LambdaQueryWrapperCart().eq(Cart::getUserId, userId)); if (cartList.isEmpty()) { throw new RuntimeException(购物车为空); } BigDecimal total BigDecimal.ZERO; for (Cart cart : cartList) { Pet pet petMapper.selectById(cart.getPetId()); if (pet.getStock() cart.getQuantity()) { throw new RuntimeException(库存不足 pet.getName()); } } // 扣库存、生成订单主表和明细、清空购物车 // 这些操作都在同一个事务里任何一步失败都会回滚 }Transactional(rollbackFor Exception.class) 是必写的默认情况下Spring事务只在RuntimeException时回滚加上rollbackFor Exception.class后checked exception也能触发回滚。库存扣减要先检查后扣减虽然这不是严格的并发安全方案但毕业设计到这一步已经足够。如果老师追问并发场景可以回答“生产环境会加悲观锁或乐观锁”这个台阶得自己留好。5. 宠物商城系统5个高频踩坑从启动翻车到页面404的排查记录5.1 启动报8080端口被占用现象SpringBoot启动到一半直接报错日志里出现Port 8080 was already in use。这是本地开发最常见的第一道坎。原因8080被其他进程占用。常见元凶是之前启动的Java进程没有完全退出或者Windows下Hyper-V动态保留端口把8080占了。解决先找出占用端口的进程然后按需处理。Windows下执行netstat -ano | findstr :8080 taskkill /PID 进程号 /F如果端口被系统保留可以换一个端口。不想改配置就在启动命令后面加参数java -jar pet-shop.jar --server.port8081我一般直接改application.yml里的server.port为8080本地固定端口方便前端联调。这个坎跨过去之后后面就顺畅了。5.2 MySQL连接报Public Key Retrieval is not allowed现象SpringBoot启动时数据源初始化失败控制台报Public Key Retrieval is not allowed或者报Unable to load authentication plugin caching_sha2_password。原因MySQL 8默认的认证插件是caching_sha2_passwordJDBC驱动在首次连接时需要向服务器获取公钥来加密密码而连接串里没有放行这个行为。解决在jdbc url后面加两个参数这个在前面2.2节已经给了完整配置useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai这三个参数是MySQL 8开发环境的三件套少了哪个都会出幺蛾子。useSSLfalse关掉SSL握手allowPublicKeyRetrievaltrue允许客户端获取公钥serverTimezone把时区校正到东八区。如果你用的是MySQL 5.7allowPublicKeyRetrieval不加也不影响但MySQL 8建议直接配齐。这个坑也对应很多人说的“mysql连接错误”和“mysql ssl连接错误”本质上都是同一批参数没配全。5.3 页面HTML出来了但css和图片全是404现象浏览器访问localhost:8080/indexHTML骨架和文字内容都出来了但页面完全没有样式控制台一堆Failed to load resource: the server responded with a status of 404报错路径指向/css/style.css。原因静态资源放错位置或者路径写法用了相对路径。很多人把style.css放进了templates目录然后页面里写href/css/style.css。实际上SpringBoot对templates目录下的文件只做模板解析不做静态资源映射而static目录才是静态资源的根目录。解决确保目录结构正确resources/static/css/style.css页面里用th:href而不是hreflink th:href{/css/style.css} relstylesheetth:href{/css/style.css}会生成带应用上下文根的绝对路径不管你部署在根路径还是子路径下都不会404。这是个细节但能让你的页面在所有机器上表现一致。顺带说一句{/detail/{id}(id${pet.id})}这种URL模板写法也是{}语法的一部分构造带路径参数的链接时比字符串拼接清爽得多。5.4 数据库和页面都显示中文问号现象后台管理添加宠物时填了“布偶猫”保存后在页面和Navicat里看到的是“???”。原因编码链路断了一环。MySQL连接串里没带characterEncodingutf8建表语句里没有DEFAULT CHARSETutf8mb4HTML页面meta标签漏了charsetutf-8。这三处只要有一处不匹配中文就会变成问号或乱码。解决从上到下检查三层。第一建表时统一指定utf8mb4可以查看现有表SHOW TABLE STATUS LIKE pet;第二jdbc url带上characterEncodingutf8。第三页面head里保留meta charsetutf-8。如果数据已经变成问号需要把表的数据导出再用正确编码重新导入这个说起来费劲最省事的办法是把脏数据删掉重新录一遍。数据库乱码这个坑属于典型的“配一次落终身”的问题建库时多花一分钟配置后面省一天。5.5 分页查不到数据但SQL单独执行能查到现象宠物列表页第一页空荡荡但直接在Navicat里执行同样的SQL能查到数据。更诡异的是有时候total是对的但records是空的。原因MyBatis-Plus的分页拦截器没有注册。没有PaginationInnerInterceptor这个Bean时selectPage方法不会真正执行分页SQLPage对象里的records可能为空total也可能为0但控制台日志里看不到LIMIT语句。解决检查MybatisPlusConfig是否加了Configuration注解、分页拦截器是否注册到MybatisPlusInterceptor里。对照3.1节的代码检查一遍重点看两行Configuration public class MybatisPlusConfig {这个Class有没有被Spring扫描到以及PaginationInnerInterceptor有没有真正add进去。这个坑特别适合写进论文的“问题与解决”章节因为它是典型的“代码看起来没问题、运行起来全是问题”的黑匣子故障排查过程能体现你对框架机制的理解。6. 答辩前怎么验证整套系统接口自测脚本与演示方法论6.1 用curl把核心链路过一遍注册、登录、加购、下单答辩现场最怕的是页面演示到一半接口报错。我的习惯是在答辩前用一组curl命令把核心链路完整跑一遍确认后端接口都是通的。这比手动点页面更快、更可重复而且出问题能直接看到返回的message。下面是一个可以直接保存成.sh脚本的验证流程#!/bin/bash BASEhttp://localhost:8080 # 1. 注册新用户 curl -X POST $BASE/api/user/register \ -H Content-Type: application/json \ -d {username:demo2,password:123456,phone:13800138000} echo # 2. 登录返回的JSON里包含userId curl -X POST $BASE/api/user/login \ -H Content-Type: application/json \ -d {username:demo2,password:123456} echo # 3. 分页查询宠物列表确认商品能查到 curl $BASE/api/pet/page?pageNum1pageSize10 echo # 4. 加购一只宠物假设宠物ID为1、数量为1 curl -X POST $BASE/api/cart/add \ -H Content-Type: application/json \ -d {userId:1,petId:1,quantity:1} echo # 5. 创建订单验证库存扣减和订单生成 curl -X POST $BASE/api/order/create \ -H Content-Type: application/json \ -d {userId:1} echo脚本里的userId和petId要按你数据库里的真实值改别照抄。每次返回的JSON里看code字段如果code不是200直接看message就能定位问题。这个脚本的额外好处是老师如果问“你怎么保证接口没问题”你可以说“我写了一套接口自测脚本部署后先跑脚本再演示”这比口头保证有力得多。6.2 答辩演示的节奏从注册到订单的完整闭环功能演示不要从后台管理开始讲。我见过太多人上去先展示添加宠物讲半天表单项等讲到核心的下单流程时时间已经不够了。正确的顺序是用户注册 → 用户登录 → 首页宠物列表 → 查看宠物详情 → 加入购物车 → 查看购物车 → 下单 → 查看订单列表。这条链路是商城的核心业务闭环走完它老师就知道系统是真正能用的而不仅仅是页面堆砌。演示过程中被问到最多的问题集中在三个地方密码为什么用BCrypt、库存扣减怎么保证不超卖、订单和购物车之间的事务怎么控制。对应的回答口径分别是BCrypt是自适应哈希算法自带随机盐能抵抗彩虹表攻击库存超卖在单体架构下可以用乐观锁解决也就是更新时加上stock 0的条件事务用Transactional控制任何一步异常都会回滚保证订单和库存数据一致。这三个问答能讲清楚“高分”基本就有底了。还有一个实用技巧提前准备好一个“演示数据”脚本把品类、宠物图片、用户、购物车数据都预置好确保演示时页面不是空的。别等到打开首页才发现一张宠物图片都没有那比任何技术问题都尴尬。图片可以放到static/images下用本地图片路径而不是外链这样离线也能演示。最后说一个我自己的教训。当年我做类似项目时分页拦截器没有注册页面一直查不出数据我以为是SQL写错了排查了一个通宵最后发现是配置类没生效。从那以后凡是用到框架提供的扩展能力时我都会先确认“对应Bean有没有被Spring管理”。这个习惯帮我避免了很多玄学问题。希望这篇笔记能让你在这个项目上少走几步弯路一次跑通答辩顺利。本文还有配套的精品资源点击获取
网站建设高端定制企业官网