
简介一套基于Spring Boot与Vue框架的宠物寄养管理系统后端负责业务逻辑与接口前端负责页面展示与交互属于典型的前后端分离毕业设计项目资源面向Java方向的毕业生、课程设计学生及期末大作业开发者也可供自学前后端开发的初学者作为完整实战范例帮助理解从数据库设计到前后端联调的全过程。压缩包大小约13.19MB共包含207个文件其中以113个Java源码文件为核心另有26个HTML页面、XML配置、SQL数据库脚本、JavaScript脚本及图片、字体等静态资源并附有Word使用手册SQL脚本可快速初始化数据库配置文件与依赖包均已整合导入开发工具即可启动运行。该资源已有806人学习/下载属于高分毕业设计级别代码结构按业务模块划分涵盖宠物寄养、订单查询、商品展示等常见功能注释清晰便于二次开发读者可通过源码学习Spring Boot整合数据库及Vue项目构建与组件化思路对完成课设、答辩或技术进阶均有实际帮助。1. 宠物寄养管理系统SpringBoot Vue 的前后端分离毕业设计到底值不值得下宠物寄养管理系统是 Java 毕业设计里出现频率极高的一类题目凡是做过的人基本都绕不开 SpringBoot 做后端、Vue 做前端、MySQL 存数据这条技术线。这个资源包就是把整套系统源码和数据库脚本打包好的成品拿到之后能直接导入 IDEA 跑起来也能看到完整的业务闭环用户选宠物、下寄养订单、管理员接单、安排寄养间、结算费用、查看评价整个流程不是那种应付事的 demo 级别而是能写进毕业论文、能现场演示的完整项目。适合的人群很明确正在做 Java 课程设计或毕业设计的在校生想看看真实前后端分离项目怎么组织代码的初学者以及需要一套可二次开发的基座来改业务逻辑的从业者。我需要提醒一句下载源码只是第一步真正值钱的是你读懂它、改出你自己的版本。下面我会按「项目结构 → 数据库设计 → 后端核心模块 → 前端对接 → 部署踩坑」的顺序拆开讲每一步都给你能直接抄的代码和参数说明。2. 前后端分离的项目骨架目录结构、技术选型与启动顺序2.1 为什么是 SpringBoot Vue而不是 JSP 或纯模板引擎先聊技术选型。如果你是最近两年做 Java 毕设SpringBoot Vue 几乎成了默认答案。SpringBoot 这边内嵌 Tomcat、自动配置、起步依赖Starter把过去 Spring MVC 那一堆 XML 配置全干掉了Vue 这边组件化开发让页面逻辑和 UI 拆分得很干净Element UI 组件库能快速搭出后台管理界面。两者通过 RESTful API 通信前端用 axios 发请求后端返回 JSON互不干预。这套组合相比 JSP Servlet 的老方案优势在职责分离前端改样式不动 Java 代码后端加接口不动页面。对答辩来说能讲清楚前后端分离RESTful 设计跨域处理这几个点已经比很多只会跑通流程的组高一个档次。资源包里用的技术栈我拆开看了一遍是标准的 SpringBoot 2.x MyBatis Plus MySQL 5.7/8.0 Vue 2 Element UI没有偏门依赖环境兼容性比较稳。2.2 拿到压缩包后第一步先看懂目录再动手解压后你会看到典型的 Maven 多模块或单模块结构。我按最常见的组织方式来拆解后端是一个标准 SpringBoot 工程前端是一个 Vue CLI 工程两者通常在同一个根目录下分开存放pet-boarding-system ├── backend # SpringBoot 后端工程 │ ├── src/main/java │ │ └── com/example/petboarding │ │ ├── controller # 控制层接收前端请求 │ │ ├── service # 业务逻辑层 │ │ ├── mapper # MyBatis 数据访问层 │ │ ├── entity # 实体类对应数据库表 │ │ └── config # 跨域、拦截器等配置 │ ├── src/main/resources │ │ ├── application.yml # 数据源、端口等核心配置 │ │ └── mapper # MyBatis XML 映射文件 │ └── pom.xml ├── frontend # Vue 前端工程 │ ├── src │ │ ├── api # axios 请求封装 │ │ ├── views # 页面组件 │ │ ├── router # 路由配置 │ │ └── main.js │ └── package.json └── sql └── pet_boarding.sql # 数据库脚本后端端口默认配的是 8080前端 devServer 默认跑在 8081通过 proxy 把 /api 前缀的请求转发到后端。这里有个关键配置参数很多第一次跑的人会栽在它上面我直接贴出来server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_boarding?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver注意两个点serverTimezoneAsia/Shanghai不加的话MySQL 8.0 连数据库时会报时区错误useSSLfalse是为了避免本地环境证书校验的告警。数据库连接池的初始大小和最大连接数通常没单独配走 MyBatis Plus 的默认值小项目完全够用。启动顺序必须是先启动 MySQL、导入数据库脚本再启动后端最后启动前端。你要是先启前端页面能打开但所有请求都会 404因为后端根本没起来。提示如果你本机装的是 MySQL 8.0驱动类必须用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver。资源包里默认配置按 8.0 写的低版本 MySQL 需要手动改。2.3 前端依赖安装与跨域代理设置前端是 Vue CLI 工程首先装依赖cd frontend npm install如果npm install慢或者报错切换镜像源再试npm config set registry https://registry.npmmirror.com npm installnpm install会生成node_modules目录体积比较大属于正常现象。装完依赖后看vue.config.js或config/index.js里的 devServer 配置本地联调时跨域代理就靠它module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }changeOrigin: true表示把请求头里的 Host 改写成目标地址避免后端收到请求时因为 Host 不匹配产生问题pathRewrite把/api前缀剥掉。后端 Controller 里的 RequestMapping 如果不带/api前缀这个配置就必须保留否则请求路径对不上。启动前端用npm run serve浏览器访问http://localhost:8081看到登录页说明前端正常。整个联调链路就是浏览器 → Vue devServer(8081) → proxy 转发 → SpringBoot(8080) → MySQL。链路里任何一环断了直观表现都是页面白屏或接口报 404。3. 数据库设计拆解从建库脚本反推业务表结构3.1 核心表用户、宠物、寄养订单、寄养间、评价打开pet_boarding.sql看一眼就知道这个项目的业务体量。它不是单表跑通的玩具而是有完整的表关联关系。我按业务优先级排序把核心表列出来表名用途关键字段user用户表区分管理员和普通用户id, username, password, role, phonepet宠物档案表id, user_id, pet_name, pet_type, age, genderboarding_order寄养订单表id, user_id, pet_id, room_id, start_date, end_date, status, total_priceboarding_room寄养间表id, room_name, room_type, price_per_day, statusevaluation评价表id, order_id, user_id, content, rating, create_time这几张表的关系是这样的一个用户拥有多个宠物一个宠物在同一时间段只能有一个有效的寄养订单一个寄养间在某个时间段内不能重复分配。订单表是连接用户、宠物、寄养间的枢纽评价表挂在订单下保证只有下过单的人才能评价。建表脚本里有个细节值得注意boarding_order的status字段用的是 TINYINT 类型0 表示待支付1 表示已支付待入住2 表示寄养中3 表示已完成4 表示已取消。这种用数字表示状态的设计在毕业设计里非常常见比字符串字段省空间但代码里必须写清楚状态枚举否则三个月后你自己看代码都记不住 3 代表什么。3.2 关键外键约束与时间字段设计看 SQL 脚本你会发现外键约束没有全建。这个不是偷懒是实战里经常主动放弃物理外键的做法。物理外键FOREIGN KEY在数据一致性上有保障但会拖慢插入和删除性能尤其订单表频繁读写场景下外键检查会影响吞吐量。这个项目用的是 MyBatis Plus 逻辑关联也就是在 Java 实体里写private Long userId通过业务代码保证关联数据的正确性。CREATE TABLE boarding_order ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) DEFAULT NULL, pet_id bigint(20) DEFAULT NULL, room_id bigint(20) DEFAULT NULL, start_date date DEFAULT NULL, end_date date DEFAULT NULL, status tinyint(4) DEFAULT 0, total_price decimal(10,2) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_room_id (room_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单编号建议用自增主键而不是业务编号原因在于订单并发场景下生成唯一业务编号比较麻烦自增主键省事且索引效率高。total_price用decimal(10,2)而不是 float 或 double是因为浮点数在金额计算时会有精度丢失等差价一分钱都是大事。时间字段是另一个坑。这里start_date和end_date用的是 date 类型只存日期不存时间因为寄养本来就是按天计算的而create_time用 datetime 记录订单创建的具体时刻用来做排序和统计。区分好按天计算的时间和记录事件发生的时刻建表时才不会一上来就全用 datetime。3.3 初始化数据不要删掉 SQL 里的管理员账号脚本末尾通常有一段 insert 语句默认插入一个管理员账号和一个测试用户。常见做法是INSERT INTO user (username, password, role, phone) VALUES (admin, MD5(123456), ADMIN, 13800000000), (user1, MD5(123456), USER, 13900000000);这里的password字段本身不是明文是 MD5 加密后的值。你看代码里登录逻辑时要注意它用的是 MD5 摘要后比对还是先查出来再比对。如果是明文比对你可以直接改 SQL 里的字符串来设置密码如果是加密比对改了 SQL 不等于改了密码必须在系统里走注册流程或写一段测试代码生成密文再回填。注意MD5 做密码存储其实是过时的做法正常商用系统该用 BCrypt。但毕设项目里 MD5 还是大量存在答辩老师问起来你能说清楚 MD5 的缺陷和 BCrypt 的盐值机制反而是加分项。另外utf8mb4字符集必须保留。如果你的数据库脚本导入后中文变乱码基本是连接字符串里的characterEncodingutf8和表字符集不一致造成的统一改成 utf8mb4 即可。4. 后端核心模块实战登录鉴权、寄养下单与状态流转4.1 登录接口与 Token 鉴权拦截器到底拦住了什么这个系统的登录逻辑不算复杂但它是前后端分离项目必须讲清楚的一环。看LoginController里的代码登录成功后会生成一个 token 返回给前端前端把它存在 localStorage 里之后的每个请求都在请求头里带上它。后端用一个拦截器统一校验。PostMapping(/api/login) public Result login(RequestBody User user) { User dbUser userService.findByUsername(user.getUsername()); if (dbUser null || !MD5Util.md5(user.getPassword()).equals(dbUser.getPassword())) { return Result.error(用户名或密码错误); } String token JwtUtil.createToken(dbUser.getId(), dbUser.getRole()); return Result.ok().put(token, token).put(role, dbUser.getRole()); }这段代码的逻辑是先按用户名查库比对 MD5 后的密码通过后生成 JWT token。JWT 里通常塞了用户 ID 和角色后续接口可以通过解析 token 拿到当前用户身份不需要每次查数据库。拦截器的作用范围要留意看WebMvcConfig或InterceptorConfig里的 pathPatterns 配置Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/login, /api/register); }/api/login和/api/register是白名单不校验 token其余/api/**下的请求都先过拦截器。这个配置如果漏了/api/register前端注册用户时会一直报 401很多人调接口调不通就是在这个地方翻的车。4.2 寄养下单价格计算与日期重复校验订单模块是业务核心也是最容易出现逻辑漏洞的地方。看BoardOrderService里的下单方法重点在两点价格计算和寄养间占用校验。public Result createOrder(BoardOrder order) { // 校验参数 if (order.getStartDate() null || order.getEndDate() null) { return Result.error(日期不能为空); } if (order.getEndDate().isBefore(order.getStartDate())) { return Result.error(结束日期不能早于开始日期); } // 计算天数加1是因为前后日期都算寄养日 long days ChronoUnit.DAYS.between(order.getStartDate(), order.getEndDate()) 1; BoardRoom room roomMapper.selectById(order.getRoomId()); if (room null) { return Result.error(寄养间不存在); } // 检查房间在时间段内是否已被占用 Integer count orderMapper.checkRoomOccupied(order.getRoomId(), order.getStartDate(), order.getEndDate()); if (count ! null count 0) { return Result.error(该寄养间在此时间段已被预约); } order.setTotalPrice(room.getPricePerDay().multiply(BigDecimal.valueOf(days))); order.setStatus(0); orderMapper.insert(order); return Result.ok(下单成功); }天数计算这块有个细节如果客户 1 号送来、3 号接走按天寄养的业务通常算 3 天而不是 2 天所以代码里 1。有的项目会不加这个 1这直接导致价格少算一天属于业务口径问题不是 bug但你要清楚自己项目里用的是哪种口径。日期重复校验是最容易遗漏的部分。上面checkRoomOccupied的本质是查订单表里是否存在同一房间且时间段有交集的记录SELECT COUNT(*) FROM boarding_order WHERE room_id #{roomId} AND status IN (0, 1, 2) AND NOT (end_date #{startDate} OR start_date #{endDate})状态过滤条件很关键IN (0,1,2)意味着待支付、已支付、寄养中的单子都算占用已取消和已完成的不算。很多半成品项目在做这个查询时忘了过滤状态导致已取消的订单还占着房间后面管理员的排房功能就全部错位。SQL 里的NOT (...)写法是区间重叠判断的经典模式两个区间不重叠的条件是一个的结束早于另一个的开始或一个的开始晚于另一个的结束取反就是有重叠。4.3 管理员接单与状态机流转谁动了订单状态从用户下单到订单完成一共有 5 个状态。我在代码里找了一圈状态流转的典型代码在AdminOrderController里PostMapping(/api/admin/order/receive) public Result receiveOrder(RequestBody MapString, Long params) { Long orderId params.get(orderId); BoardOrder order orderMapper.selectById(orderId); if (order null) { return Result.error(订单不存在); } if (order.getStatus() ! 0) { return Result.error(当前状态不可接单); } order.setStatus(1); orderMapper.updateById(order); return Result.ok(接单成功); }状态机设计的核心原则是任何状态下只能执行该状态允许的操作。待支付订单可以取消或支付已支付订单才能接单寄养中订单才能完成已完成订单才能评价。如果代码里每个状态转换都做前置校验业务就不会乱。你要答辩时重点讲这块老师最吃这一套。4.4 MyBatis Plus 的用法CRUD 为什么这么快这个项目用了 MyBatis Plus最直观的体感是单表 CRUD 几乎不写 SQL。比如UserMapper继承BaseMapperUser后selectById、insert、updateById这些方法直接就有。只有在多表联查或像上面那种复杂条件查询时才需要在 XML 里手写 SQL。public interface BoardOrderMapper extends BaseMapperBoardOrder { Integer checkRoomOccupied(Param(roomId) Long roomId, Param(startDate) LocalDate startDate, Param(endDate) LocalDate endDate); }XML 文件里对应的语句就是前面那段重叠查询。这里Param注解的作用是给参数命名XML 里#{roomId}才能正确取到值。如果漏了ParamMyBatis 会报Parameter roomId not found这是用了 MyBatis Plus 还手写 SQL 时最容易踩的坑。提示MyBatis Plus 的逻辑删除功能如果配了删除操作其实是 update 一个deleted字段不是真删。你写统计 SQL 时如果数据对不上先去确认有没有开逻辑删除。5. 前端 Vue 页面联调登录态、路由守卫与接口对接5.1 axios 封装与请求拦截器401 跳转的统一处理前端这侧src/api目录下的 request.js 做了 axios 的二次封装。这个封装解决两个问题每个请求自动带上 token遇到 401 自动跳回登录页。import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default servicebaseURL: /api配合之前 vue.config.js 里的代理请求/api/login最终打到后端http://localhost:8080/login。token 放在请求头的Authorization字段里后端拦截器从这个字段取值解析。timeout 设 10 秒超过这个时间请求直接失败避免接口卡死时页面一直转圈。响应拦截器里response.data被提前返回所以页面代码拿到的直接是后端 JSON 里的数据体。这里有个小坑如果后端返回的文件流或 Blob 数据也走这个拦截器会被强行解析成字符串导致下载文件损坏。普遍的做法是单独写一个不带拦截器的请求实例来处理文件下载。5.2 路由守卫控制页面权限管理员与普通用户router/index.js里用 Vue Router 的全局前置守卫来控制页面访问权限router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.path /login) { next() return } if (!token) { next(/login) return } if (to.meta.role to.meta.role ! role) { next(/403) return } next() })逻辑很简单没登录一律去登录页进入带meta.role的路由时校验角色管理员的页面用户进不去用户的页面管理员也没必要进。路由配置里meta可以这样写{ path: /admin/rooms, component: RoomManage, meta: { role: ADMIN } }/admin/rooms是管理员管理寄养间的页面普通用户登录后访问它会被弹到 403 页面。前端只做隐性展示真正的安全边界还是后端拦截器前端路由守卫只是用户体验层面的控制。这一点答辩时一定要说清楚不然老师一句前端调接口就能绕过权限就把你问住了。5.3 Element UI 表单与接口对接寄养订单提交流程用户下单页面是典型的三段式表单选择宠物、选择寄养间、选择日期。日期选择器用 Element UI 的 el-date-picker设置typedaterange之后拿到的是一个数组需要拆成 startDate 和 endDate 再提交。submitOrder() { const [startDate, endDate] this.dateRange const orderData { userId: this.userId, petId: this.selectedPet, roomId: this.selectedRoom, startDate: this.formatDate(startDate), endDate: this.formatDate(endDate) } boardingApi.createOrder(orderData).then(res { if (res.code 200) { this.$message.success(下单成功) this.$router.push(/user/orders) } else { this.$message.error(res.msg) } }) }this.$message是 Element UI 的轻提示组件不需要额外引入弹窗组件库。联调时注意一点后端返回的 JSON 结构里 code 是多少、成功信息挂在哪个字段必须和后端 Result 类的定义对齐。前后端联调对不上大部分问题就出在字段名大小写或 code 值含义不一致上。日期格式化也很容易出错JavaScript 的Date对象转成YYYY-MM-DD字符串需要手动补零formatDate(date) { const y date.getFullYear() const m String(date.getMonth() 1).padStart(2, 0) const d String(date.getDate()).padStart(2, 0) return ${y}-${m}-${d} }不补零的话2024-5-1这种格式传给 Java 的LocalDate会解析报错。前后端联调 90% 的时间都花在这种看似小但影响全局的格式问题上。5.4 404 与拦截器拦截不到的请求排错从 Network 面板开始前端页面报错时我一般先按 F12 打开开发者工具看 Network 面板里请求的状态码和响应内容。常见的三种情况第一种请求根本没发出那就是前端代码逻辑问题看 Console 报错第二种请求发出但状态码是 404检查后端接口路径和前端请求路径是否一致特别注意/api前缀是不是被代理重写掉了第三种状态码 401先看请求头里有没有 token再检查拦截器白名单配置。注意Network 面板里如果看到 CORS error不要急着在后端加CrossOrigin。本地联调时前端代理已经解决了跨域加了 CORS 反而可能出现重复响应头的警告。只有前端和后端部署在不同域名的生产环境时CORS 才有必要。6. 避坑指南从导入到答辩的五个实战踩坑记录6.1 启动报错端口被占用导致后端起不来现象后端启动到一半报Web server failed to start. Port 8080 was already in use。原因本机某个程序占用了 8080 端口常见的是之前跑过的 SpringBoot 应用没关干净或者是其他开发工具占了端口。解决杀掉占用进程或者改application.yml里的端口。杀进程用netstat -ano | findstr 8080看 PID然后taskkill /PID 进程号 /F。改端口的话注意前端 vue.config.js 里的 proxy target 也要同步改。6.2 npm install 装不上依赖或者 node-sass 报错现象前端执行npm install时报错常见关键词是node-sass、python2、binding。原因Vue 2 项目里普遍依赖 node-sass它需要下载二进制文件网络不通时经常失败或者 Node 版本太高node-sass 编译不兼容。解决最快的路径是换镜像源。npm config set registry https://registry.npmmirror.com之后重新 install。如果 node-sass 还是报错改环境变量SASS_BINARY_SITEhttps://npmmirror.com/mirrors/node-sass/让绑定文件走镜像下载。实在不行看看项目里能不能换成sass替代方案但那需要改代码里的引入方式。6.3 数据库导入报错Unknown character set 或版本不兼容现象用 Navicat 或命令行导入pet_boarding.sql时报Unknown character set或 SQL 语法错误。原因SQL 文件开头可能写着utf8mb4但你的 MySQL 版本太老不支持或者是 SQL 里用了 MySQL 8.0 的新语法。解决先确认 MySQL 版本5.7 以上基本都支持 utf8mb4。如果是语法错误用文本编辑器打开 SQL 文件看有没有CREATE TABLE ... COLLATE之类带版本特征的语句逐个排查。最稳妥的办法是用 Navicat 手动建一个空库然后把 SQL 文件里的建表和插入语句逐段执行错了就定位到具体行。6.4 登录接口报 406 或 JSON 解析失败现象前端登录时 Response 返回 406或者后端控制台报HttpMessageNotReadableException。原因RequestBody User user接收 JSON 时前端传过去的字段名和后端实体类属性名不一致或者前端没设置Content-Type: application/json。解决axios 的 post 方法默认会自动序列化对象为 JSON 并设置 Content-Type但如果你用qs.stringify或form-data提交后端用RequestBody就接不到。检查请求头和后端实体类字段。406 更常见的原因是返回类型不匹配比如接口返回 String但前端期望 JSON改响应类型即可。6.5 答辩前演示数据丢失重启后订单全没了现象前一天录入的测试数据第二天重新启动项目后发现部分订单不见了但用户数据还在。原因十有八九是用了 H2 或内存数据库或者 MySQL 服务没起来导致后端自动切换到了备库更常见的是你导入的 SQL 脚本只建了表没把测试数据 insert 进去。解决演示前固定一个流程——启动 MySQL用 Navicat 执行一遍完整 SQL 脚本再启动后端和前端。我个人的习惯是把演示数据单独存一份 SQL里面写好 3 个用户、5 只宠物、3 个寄养间、4 个不同状态的订单每次答辩前一键导入保证演示时点和数据都是可控的。提示接单、完成订单这些操作会改变 status 字段演示前重新插入一份初始化数据比你去数据库里手动改状态要快得多。血泪经验不要问我怎么知道的。7. 进阶打磨把毕设从能跑变成有亮点的三个改造方向7.1 把登录密码从 MD5 升级为 BCrypt这个改造不复杂但答辩时讲出来非常有分量。MD5 加盐实现起来也简单但 BCrypt 自带随机盐每次加密结果都不同安全性高一个等级。Spring Security 的 crypto 包里直接有这个工具import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); // 注册时加密 String encoded encoder.encode(123456); // 登录时校验 boolean match encoder.matches(123456, dbUser.getPassword());改造点就两个位置注册接口的密码存储和登录接口的密码校验。数据库里已有 MD5 值的历史数据没法直接迁移演示前重新走一遍注册流程生成 BCrypt 密文就行。答辩时主动说一句原项目用的是 MD5我升级成了 BCrypt老师的印象分立刻不一样。7.2 订单状态操作加操作日志现在的系统里订单状态变更是直接 update谁在什么时间改的没有记录。加一个order_log表每次状态变化插入一条记录这是很常规的后端增强CREATE TABLE order_log ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) DEFAULT NULL, operator_id bigint(20) DEFAULT NULL, action varchar(50) DEFAULT NULL, remark varchar(255) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;在receiveOrder、completeOrder、cancelOrder等方法里状态更新后插入日志OrderLog log new OrderLog(); log.setOrderId(orderId); log.setOperatorId(currentUserId); log.setAction(RECEIVE); log.setRemark(管理员接单); orderLogMapper.insert(log);有了操作日志答辩时你能展示的不只是 CRUD而是可追溯的订单流转。老师问这个状态怎么从 1 变成 2 的你直接拉日志表给他看这个说服力比嘴上讲强太多。7.3 后端接口加参数校验Valid 的用法现在的代码大量if (xxx null) return Result.error(...)式的手动校验。用Valid注解可以统一交给框架处理public class BoardOrder { NotNull(message 房间ID不能为空) private Long roomId; NotNull(message 宠物ID不能为空) private Long petId; NotNull(message 开始日期不能为空) private LocalDate startDate; }Controller 里加ValidPostMapping(/api/order/create) public Result createOrder(RequestBody Valid BoardOrder order) { return orderService.createOrder(order); }配合全局异常处理器捕获MethodArgumentNotValidException前端就能拿到统一的错误提示。这类改造是纯粹加量的不动业务逻辑花半小时改完写在论文里是参数校验的规范化设计写在简历里是接口健壮性优化性价比很高。7.4 部署验证本地跑通后怎么确认系统是完整的改造完成之后最后走一遍完整流程验证我的固定步骤是清空数据库重新导入脚本 → 注册一个新用户 → 添加宠物资料 → 选择一个寄养间下单 → 退出登录 → 用管理员账号登录 → 接单确认 → 用户支付确认 → 完成订单 → 用户评价。这一套流程走完所有表都有数据所有状态都流转过一遍就说明系统从数据库到后端到前端是通的。从那以后我每次拿到陌生项目都强制先走一遍当管理员看完整流程再考虑改代码。顺序反了的话你会在这个 bug 到底是我改出来的还是原来就有的这个问题上浪费大量时间。希望这篇拆解能帮你把项目跑起来也帮你把里面的门道看明白。本文还有配套的精品资源点击获取