宠物寄养系统这个题目在计算机毕业设计的选题表里出现的频率快跟图书管理系统肩并肩了。但说句实在话它的含金量比图书管理高不少因为业务链路是完整的用户注册登录、宠物档案、下单预约、商家接单、寄养过程记录、支付结算、评价反馈一条线下来既有基础CRUD又有状态流转答辩时能讲的东西自然就多。而且这套系统做出来是真能跑的把网页一打开就是一个真实可用的寄养门店管理系统。无论你是毕业设计找方向、课程设计凑项目还是想拿Java后端岗位的求职作品这个题目都算得上稳妥之选。下面我就按实际操作顺序把这个系统从需求拆解、技术选型、数据库设计、核心代码到常见坑位完整拆开讲一遍。1. 先把这个选题吃透宠物寄养系统到底要做什么很多人拿到题目第一反应是这不就是一个增删改查吗然后照着图书管理系统抄一版用户表、宠物表、订单表做完就交差。这种项目答辩时被问两句就露馅了因为业务逻辑经不住推敲。宠物寄养和普通商城最大的区别在于订单不是下单就结束的它有一个持续数天的服务执行周期这个周期里每一步都需要状态记录和操作权限控制。1.1 需求拆解别让寄养两个字限制了你的设计空间核心流程其实只有一条主线用户注册登录后添加宠物档案浏览寄养服务并选择门店提交寄养预约订单完成支付商家接单确认宠物到店开始寄养每天由店员记录喂养和遛弯情况并上传照片寄养到期后提醒用户接回用户确认接回订单完成最后对本次寄养进行评价。这条主线做出来系统就已经能跑了。但如果你想让它更像一个完整的业务系统还需要补几块周边功能宠物档案管理。种类、品种、年龄、体重、是否绝育、疫苗记录、过敏史、特殊习性这些字段决定了寄养时能不能接、怎么喂。寄养套餐设计。比如按半天/整天/过夜区分按宠物大小区分价格还有加遛弯次数、加喂药这种增值项。寄养门店信息。地址、营业时间、联系电话、店面照片给用户选择的时候展示。寄养过程记录。这是这个题目最有辨识度的地方也是区别于普通商城的核心点。店员每天录入喂食情况、遛弯次数、宠物精神状态附上一两张照片用户端可以实时看到。评价体系。订单完成后用户打分和写评语反过来给其他用户做参考。把这些功能画成用例图你会发现角色和功能一下就清晰了。这也是答辩时最有效的一张图。1.2 角色与权限没有账号体系的系统不叫完整系统宠物寄养系统至少要有三类角色普通用户注册登录、管理宠物、浏览门店、下单、查看寄养记录、评价。门店店员/商家接单、拒绝订单、录入寄养记录、处理订单状态。系统管理员管理用户、管理门店、下架违规服务、查看统计报表。权限设计上不用搞太复杂的框架级RBAC可以直接在用户表里加一个role字段用1、2、3区分三类角色。后端写一个注解或者拦截器在需要权限校验的接口上声明该方法仅店员或管理员可访问。这个方案对毕业设计完全够用而且代码量少、逻辑清晰答辩时你还能讲清楚为什么不做成独立权限表——因为角色固定只有三类多表关联纯属增加复杂度。权限一旦划分好后面的订单状态流转就顺理成章了。用户能干什么、店员能改什么状态、管理员能看什么数据边界清清楚楚代码里不会出现混乱的越权操作。1.3 适合谁拿来做毕业设计/课设我接触过不少选这个题目的学生总结下来有三类人特别适合第一类是Java后端方向的本科生学完了Spring Boot、MySQL、MyBatis正需要一个能覆盖主流技术栈的综合项目。这个题目技术难度中等偏上既不会让你无从下手也不会简单到没东西可写。第二类是准备找Java开发实习或秋招的同学。比起烂大街的商城、网盘、博客系统宠物寄养有业务特色面试时讲起来记忆点强很多。尤其是寄养记录这种带状态流转的业务面试官会下意识觉得你做过真实业务。第三类是课程设计需要凑学分的同学。这个题目资料多、实现方案成熟网上能找到很多参考但要提醒一句参考可以别直接抄答辩一问这个字段为什么这么设计就尴尬了。2. 技术选型与工程搭建Spring Boot 打底组件怎么搭配最省心技术选型是毕业设计的第一步也是很多人纠结最久的一步。Spring框架本身已经够复杂了Spring Boot的好处就是把这些繁琐的配置都自动搞定你只需要关注业务本身。2.1 为什么是Spring Boot框架选择的底层逻辑Spring Boot的核心理念是约定大于配置和自动装配。以前用Spring写一个Web项目要手动配置数据源、事务管理器、视图解析器写一堆XMLSpring Boot把这些封装成starter引入依赖后开箱即用main方法启动就是一个完整的Web服务。对毕业设计来说这个优势是决定性的。你不需要在配置上花两周时间可以集中精力写业务代码。另一方面Spring Boot是目前国内中小型公司Java后端的主流选择你简历上写基于Spring Boot开发面试官不会觉得奇怪问起来你也能答得上来。需要注意的是Spring Boot本身是一个生态底座它解决的是项目怎么跑起来的问题。具体用MyBatis还是JPA、用Redis还是本地缓存、用Spring Security还是拦截器这些才是真正体现你设计能力的地方。2.2 版本怎么选Spring Boot 2.7 还是 3.x别盲目追新我见过太多人开局就在版本上踩坑。选版本要看你的JDK环境而不是哪个版本最新就用哪个。如果你电脑装的是JDK 8那老老实实用Spring Boot 2.7.x这是兼容JDK 8的最后一个系列网上教程也最多。如果你的JDK已经到17或21那用Spring Boot 3.x更合适3.x不再支持JDK 8最低要求JDK 17。如果你打算用JDK 21Spring Boot 3.5开始支持虚拟线程Virtual Threads这是Java 21的一个亮点可以在高并发场景下人畜无害地提升吞吐量。毕业设计里用不上虚拟线程但如果答辩时你能说一句底层环境支持虚拟线程后续如果有高并发场景可以直接开启这会是加分项。还有一个关键提醒Spring Boot 3.x对Spring Security的配置方式和2.x完全不同。2.x时代大家都在用WebSecurityConfigurerAdapter3.x把它直接移除了改成了SecurityFilterChain的Bean配置。如果你在网上找的教程是Spring Boot 2.x Security的写法在3.x项目里会直接找不到类。安全框架这种依赖全局的组件版本不同歩真的是灾难。2.3 配套组件怎么搭ORM、缓存、存储、鉴权一网打尽一个性价比最高的后端组合是这样的Spring Web负责HTTP接口MyBatis-Plus负责ORM和CRUDMySQL 8.0负责数据存储Redis负责缓存和验证码JWT 拦截器负责登录鉴权MinIO或本地磁盘负责图片存储Knife4j负责自动生成接口文档。为什么选MyBatis-Plus而不是Spring Data JPA因为MyBatis-Plus对熟悉SQL的人极其友好单表CRUD不需要写SQL复杂查询用LambdaQueryWrapper或者XML写SQL学习曲线很平缓。JPA虽然写起来很面向对象但关联查询复杂后SQL怎么生成你很难控制对经验不足的人容易出莫名其妙的问题。存储这块我建议这样安排开发阶段用本地磁盘配置一个上传目录并注册静态资源映射如果时间充裕把MinIO跑起来把宠物照片存到MinIO桶里业务代码只保存文件路径或对象名。答辩时讲我用对象存储做文件管理业务和存储分离格调和我存本地了完全不一样。Redis在这个项目里的用法有两种一是存短信验证码如果做了手机号注册二是缓存热门的寄养服务列表或门店信息。如果只是象征性引入Redis又说不清楚缓存了什么、缓存的失效策略是什么那我建议干脆缓一缓。引入一个技术却说不出业务价值在答辩时反而是减分项。3. 数据库设计一切业务逻辑的起点数据库表结构决定了业务的上限。表的字段设计不合理后面写代码会发现到处都别扭。我建议在动代码之前先把表结构和状态流转图画出来哪怕用纸画都行。3.1 核心表结构一张图看懂所有数据模型一个完整的宠物寄养系统核心表建议这么设计用户表 user字段类型说明idbigint主键usernamevarchar(32)用户名唯一passwordvarchar(100)BCrypt加密后的密码phonevarchar(20)手机号avatarvarchar(255)头像URLroletinyint1用户 2店员 3管理员create_timedatetime创建时间update_timedatetime更新时间deletedtinyint逻辑删除0否1是宠物表 petid、user_id、name、type猫/狗/其他、breed、gender、age、weight、is_neutered、vaccine_status、allergy_info、remark。关键点是user_id这个外键逻辑关联到user表它的作用是把宠物挂到某个用户名下。门店表 storeid、name、address、phone、cover_img、business_hours、status营业中/休息中。服务项目表 service_itemid、store_id、name、price、unit按半天/天/夜、description。门店和服务项目是一对多这样设计是考虑一个门店可能提供多种寄养服务不同服务价格不同。订单表 orderid、order_no、user_id、pet_id、store_id、service_id、start_date、end_date、amount、pay_status、status、remark。这是整个系统的核心表。寄养记录表 boarding_recordid、order_id、operator_id、record_date、feed_info、walk_info、photo_url、remark、create_time。每条订单下有多条记录店员每日维护。评价表 reviewid、order_id、user_id、content、rating、create_time。一个订单最多一条评价可以在order_id上加唯一约束。3.2 订单状态机设计用数字存储用代码约束流转订单状态是整个系统的灵魂建议用tinyint存储而不是字符串0待支付1已支付/待确认2寄养中到店了店员开始记录3待接回寄养到期4已完成-1已取消为什么用数字不用字符串第一数字占用空间小查询快第二状态流转要用if判断数字比字符串比较更高效、更不容易写错第三展示的时候再通过枚举或者字典映射成中文逻辑和表现分离。状态流转一定要写在校验逻辑里不能每个接口都自由更新。比如取消订单只有status0待支付状态才能取消店员接单只有status1已支付才能流转到2用户确认接回只有status3才能改到4。如果接口可以随意覆盖状态就会出现订单已取消了还能完成这种逻辑漏洞。3.3 字段规范与索引设计的心得一些我踩过坑后总结出来的设计规范金额一律用decimal类型千万别用float/double浮点数精度问题在支付场景会出大事。日期字段区分date和datetime。寄养开始结束日期是date记录创建时间是datetime别混用。所有表都加上create_time、update_time、deleted三个基础字段这是后端开发的基础素养。索引不要乱加。order表加order_no唯一索引、user_id和status的联合索引pet表加user_id索引boarding_record加order_id索引够了。外键不要用物理外键用逻辑关联。物理外键在删除、迁移、分库分表时很痛苦而且容易误操作。答辩时如果老师问你怎么保证数据一致性你可以说用逻辑外键加代码层面的校验比如删除用户前先检查他名下是否有订单。4. 核心功能模块实现从代码层面拆解业务闭环这一部分我会挑几个能讲出话的关键点不是把所有CRUD都贴一遍而是把实现思路和代码边界讲清楚。4.1 登录鉴权怎么做JWT 拦截器不用把问题复杂化登录鉴权有很多方案最常规的是Session和JWT。毕业设计我建议用JWT因为它的无状态特性方便讲解而且前后端分离时处理起来更自然。JWT的结构是header.payload.signature后端用一个密钥对用户信息签名生成token前端存到localStorage里请求时放到Authorization请求头后端拦截器每次解析token并取出用户身份。登录接口核心代码大概是这样的PostMapping(/login) public R login(RequestBody LoginDTO dto) { User user userService.lambdaQuery() .eq(User::getUsername, dto.getUsername()) .one(); if (user null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return R.error(401, 用户名或密码错误); } String token JwtUtil.createToken(user.getId(), user.getRole()); return R.ok().data(token, token); }密码为什么要用BCrypt而不是MD5因为MD5是单向散列相同密码散列结果相同可以被彩虹表暴力破解BCrypt每次生成的盐都不同即使数据库泄露破解成本也高得多。这个点你可以在答辩时说一句老师会觉得你是有安全意识的。拦截器里要做的事是从请求头取token校验合法性然后解析出当前用户放到ThreadLocal上下文里供后续业务代码使用。需要注意登录接口、注册接口、上传接口、静态资源这些路径要加到白名单里不拦截否则前端还没拿到token就被拦了。如果还想做得更正规可以引入Spring Security。但要付出额外成本比如安全过滤器链的配置、密码加密器PasswordEncoder的替换、权限注解的配置。我个人建议是如果对该框架很熟才用否则用拦截器完全能满足需求重点把业务代码写好。4.2 寄养订单流程实现下单、支付、接单、记录、完成一个不落这个流程是项目的主干道我把每一步的要点拆出来下单接口。接收用户传入的petId、storeId、serviceId、startDate、endDate后端要做几件校验宠物是不是当前用户的、门店是否存在且在营业、服务项是否存在、日期不能重叠同一只宠物不能同时有两个生效中的订单。校验通过后生成订单号格式可以是时间戳加随机数比如用当前时间加三位随机数保证唯一即可。金额根据服务单价和天数计算状态置为0待支付。模拟支付。毕业设计不用真的接微信支付宝写一个支付接口把订单状态从0改成1payStatus改成1即可。但要注意幂等性如果用户重复点击支付第二次就应该提示订单已支付请勿重复操作判断一下当前状态不是0就直接挡住。店员接单。店员端看到待确认订单点击接单状态从1变为2。同时可以设置寄养开始日期到了之后自动开始也可以用定时任务把状态从2推进到3寄养到期自动转待接回。寄养记录录入。店员每天为在店宠物录入一条记录包括喂食情况、遛弯次数、今日状态、照片。这些记录实时展示给用户是增强系统真实感的重要功能。录入时校验订单状态必须是2寄养中已经完成或取消的订单不能继续录。用户确认接回并评价。寄养到期后状态变为3待接回用户确认接回后状态变成4已完成。完成后生成评价入口用户填写评分和内容。一个订单只能评价一次用order_id唯一约束去保证。整个流程涉及多次状态更新和多表操作建议在关键节点加上事务注解Transactional。比如下单接口同时插入订单、更新宠物最近寄养时间如果第二步失败第一步也要回滚否则会出现脏数据。这个边界思考能力是答辩现场最容易出彩的地方。4.3 文件上传与图片展示宠物照片的存储方案宠物照片、门店封面、寄养记录图片都需要文件上传功能。整个链路是前端上传MultipartFile到后端接口后端校验文件大小和扩展名生成唯一文件名保存到配置好的目录或MinIO最后把可访问的URL返回给前端。本地存储是最简单的方式代码量很少PostMapping(/upload) public R upload(RequestParam(file) MultipartFile file) { String originalFilename file.getOriginalFilename(); String ext StringUtils.substringAfterLast(originalFilename, .); String fileName UUID.randomUUID().toString().replace(-, ) . ext; file.transferTo(new File(uploadDir, fileName)); return R.ok().data(url, /upload/ fileName); }同时要配置静态资源映射把 /upload/** 这个路径映射到本地磁盘目录Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir /); }如果用到MinIO代码逻辑改为把文件流传到桶里返回桶里的对象名访问时通过预签名URL或者后端代理转换。MinIO的价值在于独立于应用服务器的存储应用多实例部署时图片不会散落各处。虽然毕业设计只有一台机器但你能讲明白这个设计思路说明你考虑过生产环境。文件上传的几个校验别忘了扩展名白名单jpg/png/webp等、文件大小限制比如最大5MB、文件名安全过滤不要用原始文件名直接存防止路径注入。4.4 全局异常处理、统一返回体与日志答辩时的细节分很多同学接口返回的数据格式不统一有的返回User对象有的返回Map有的返回String前端解析时苦不堪言。规范的做法是定义一个统一返回体R所有接口都返回这个结构。Data public class R { private int code; private String msg; private Object data; public static R ok() { return new R(200, ok, null); } public static R ok(Object data) { return new R(200, ok, data); } public static R error(int code, String msg) { return new R(code, msg, null); } }有了统一返回体再加上全局异常处理Controller里就不需要到处写try/catch了。业务异常直接throw new BusinessException(订单已取消),由RestControllerAdvice统一捕获并转换成R返回系统异常也统一记录日志再返回系统繁忙。日志这块别忽略也别乱打。开发环境用控制台输出生产环境建议按天滚动保存。日志格式里带上时间、线程号、日志级别、类名和消息这样查问题的时候能快速定位。项目做完了我会习惯性在关键业务节点打日志比如用户下单成功 orderNoxxx答辩时展示一段清晰的日志跟踪说服力比口头描述强很多。4.5 进阶功能加餐WebSocket实时通知和Redis缓存怎么接入如果你的项目做完核心功能还有余力我建议加两个进阶功能第一个是WebSocket实时通知。场景是用户在小程序/网页上下单后店员端不用刷新页面就能收到新订单提醒通过WebSocket推送一条消息过去。Spring Boot集成WebSocket需要一个starter依赖和几个配置类核心是用Handler注册端点和处理消息会话。你可以做成用户下单后向店员群推送一条新订单待确认订单号xxx这个功能演示效果很直观。第二个是Redis缓存。可以把门店列表、热门服务列表缓存起来设置过期时间比如10分钟。用户频繁浏览首页时直接走缓存不查询数据库。这样用Redis是有业务依据的不是硬凑技术。再往深一点可以用Caffeine做本地缓存Redis做分布式缓存二级缓存组合。但毕业设计能把Redis用明白就已经不错了Caffeine可以作为拓展了解提到了解决策的原理即可。5. 常见问题与排查技巧实录这个部分我整理了做项目过程中遇到频次最高的几类问题每个都是真实踩过的坑直接对号入座排查。5.1 环境与启动类问题项目跑不起来先查这三样启动报错是最磨人的。常见的坑有端口被占用。Spring Boot默认8080如果你的其他服务占了这个端口启动会报BindException。解决办法是改application.yml里的server.port或者把占用端口的进程干掉。MySQL连接失败。如果你报的是Communications link failure大概率是没连上数据库先ping一下MySQL端口、检查连接串用户名密码。如果你是MySQL 8连接url里要带上serverTimezoneAsia/Shanghai否则时区问题会报异常。Maven依赖冲突。多个starter传递依赖时偶尔会引入不同版本的同名类。用idea的Maven面板看依赖树找到冲突项在pom里用exclusion排除即可。MyBatis-Plus有个情况要注意如果你设置了逻辑删除字段但查询时发现删除的数据还能查出来很可能是TableLogic注解没加对或者在配置类里没开启逻辑删除全局配置。5.2 业务逻辑类问题事务失效和状态乱跳才是大头事务失效是后端开发里极其经典的问题。比如你在一个类里的方法A调用了方法B但B上有Transactional注解此时事务是不生效的。原因在于这是同一个Bean内部的调用没有经过代理对象所以注解不会触发。解决办法是让外部调用或者从Spring容器里取出代理对象来调用。这个知识点几乎在我接触过的每一个项目里都会遇到。状态乱跳的问题也常见。如果订单状态更新接口每次都无条件setStatus用户并发点几次请求状态可能从已完成又变回待支付。正确做法是在更新SQL里带上当前状态条件boolean updated orderService.lambdaUpdate() .eq(Order::getId, orderId) .eq(Order::getStatus, 0) .set(Order::getStatus, -1) .update(); if (!updated) { throw new BusinessException(订单状态已变更请刷新后重试); }这种方式叫乐观锁的变体用状态字段作为版本判断条件。类似的也可以用version字段实现原理一样都是通过对比再更新来防止并发覆盖。5.3 前后端联调类问题跨域、Token、图片404三大难题如果你做了前后端分离联调阶段会遇到跨域问题。前端在8081端口后端在8080端口直接请求会被浏览器拦。后端配置一个全局CORS规则允许指定来源访问即可。用CrossOrigin注解也可以但全局配置更规范。Token传不到后端。很多前端同学把token放对了位置但后端拦截器读的是Authorization头两者对不上。建议后端日志先打印请求头联调时一眼定位问题。图片404。本地存储时注意路径差异Windows和Linux的绝对路径写法不一样。存到数据库里的是相对路径/upload/xxx.jpg返回给前端时要拼上域名或IP端口否则前端拿到的是相对路径刷新就裂了。这些联调问题看起来是前端的事但后端最清楚数据来源排查思路理清楚能省下大把时间。5.4 如何让项目在答辩中更出彩四个低成本加分点最后给想让项目看起来更牛的朋友几个低成本方案。一是加统计图表。后端写几个聚合查询接口统计一周/一月订单量、营业额、寄养宠物类型分布前端用ECharts画折线图和饼图。答辩演示时展示这些图表比单纯列表直观太多。二是加定时任务。用Spring Task定时扫描订单表寄养到期当天自动把状态从2改为3或者给用户推送一条您的宠物明天可以接回的站内消息。这个功能用Scheduled注解就能做放在Service里扫描一下数据库成本极低但说出去是个完整闭环。三是做数据初始化脚本。写一个SQL文件插入几个测试用户、宠物、门店、订单数据答辩前重置数据库再执行脚本保证演示数据干净又完整避免紧张时数据乱七八糟。四是提前准备一张系统架构图。不用多高级画清楚前端请求到控制层、服务层、持久层的调用链标出哪些模块用了Redis、哪些用了MinIO答辩老师问你这个架构是怎么设计的时直接指着图讲。我个人做这个题目最大的体会是不要急着写代码先把每张表的字段、每个状态下面的动作、每个接口的输入输出列清楚。你花两天把这个理清楚后面写代码会顺畅到你难以置信。反过来如果上来就闷头建实体类写到一半发现表结构有问题返工的成本足够你崩溃两次。这套先花时间想明白再动手的习惯是从这个项目里最值得带走的东西。如果你也在做类似的Java毕设希望这篇能让你少走几个弯路。项目做完不是终点把它讲明白、说清楚让它能放进简历里撑起一场面试才算真正拿到手。