ARTICLE DETAIL

资讯详情

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

基于Spring Boot的宠物咖啡馆平台系统设计与实战复盘

基于Spring Boot的宠物咖啡馆平台系统设计与实战复盘 做毕设选题那会儿我翻遍了各类“Java 毕业设计论文题目参考”发现十个里七个是图书馆管理系统、在线商城、学生选课系统剩下三个是“XX管理系统”的变体。不是说这些题目不行而是答辩时评委看多了提问自然会往刁钻方向走。最后我选了“基于 Spring Boot 的宠物咖啡馆平台的设计与实现”一方面是因为宠物咖啡馆这个业态近几年在线下确实火另一方面是它的业务复杂度刚好够用——不再是单纯的增删改查而是把宠物档案、饮品商品、座位预约、领养审核这些非标场景揉在了一起可以顺理成章地用到缓存、状态机、并发控制、权限隔离这些技术点。这篇文章我会完整复盘整个项目的设计思路、技术选型、数据库建模、核心代码实现以及我在实际开发中踩过的坑。如果你是正在做同类毕设的本科生或者打算把“宠物主题门店系统”作为练习项目的开发者这篇文章应该能帮你少走不少弯路。整体内容我会按我的实战顺序来写从选题定调到上线答辩全程都是可落地的干货。1. 选题定调宠物咖啡馆平台的真实业务边界在哪里1.1 宠物和餐饮的复合业态决定了系统形态宠物咖啡馆跟普通咖啡馆最大的区别是它的“SKU”里不仅有咖啡、甜品还有活体宠物和相关服务。走进一家正经的宠物咖啡馆消费者的动线大概是这样的门口先看今日在店宠物列表挑一只合眼缘的预约互动时段进店后点一杯咖啡、买一包宠物零食互动过程中如果对某只猫或狗特别有感情可以现场提交领养申请门店员工除了做咖啡还要记录每只宠物的喂食、清洁、健康状态店长则要盯着库存、订单、预约情况和领养审核。从这个动线能提炼出系统的几个核心角色普通用户C端注册登录、浏览宠物、点单、预约座位、提交领养申请、查看订单门店员工处理订单、核销预约、维护宠物日常档案、补充商品库存管理员宠物档案管理、商品管理、订单总览、领养审核、用户管理、数据统计这样划分后系统就不是单薄的一层“商城”了而是“宠物档案 预约 零售 审核”四条业务线并行。这个形态恰好能支撑起一个完整的 Web 应用该有的模块设计也方便在答辩时讲清楚“每个角色能做什么、数据是怎么流转的”。1.2 毕设的展示策略先想清楚评委想看什么做毕设和做商业项目有个显著区别商业项目追求快速上线毕设追求的是“思路完整 技术点可证明”。我自己把评委最常关注的几个问题提前列了出来系统解决了什么实际问题有哪些角色每个角色完成什么闭环数据库设计是否合理表之间的关联和状态流转是否有逻辑核心模块有没有技术含量比如并发控制、权限校验、缓存设计。项目是不是你自己写的现在很多评委喜欢问代码细节比如某张表的字段为什么这么定。所以我在设计的时候就刻意增加了几个“可讲点”宠物领养的状态机约束、预约座位的防并发方案、订单模块的乐观锁扣库存、基于 JWT Redis 的登录态管理。这些点未必有多高深但足够在答辩时把一个普通 CRUD 项目讲出“系统设计感”。2. 技术选型的取舍逻辑为什么这个组合最稳妥2.1 Spring Boot 版本与持久层框架的搭配技术选型我纠结过一段时间核心是版本搭配问题。最初想直接用 Spring Boot 3.x毕竟它已经是很成熟的版本了但后来发现很多教学资料和已有开源项目还停留在 Boot 2.x 时代加上部分第三方 starter 的兼容性在 Boot 3 下有变化最后我选了Spring Boot 2.7.x JDK 8/11的组合。理由很现实毕设时间有限遇到兼容性问题排查成本太高Boot 2.7 是 2.x 系列的最终维护版本稳定性和资料丰富度都最理想。持久层我选的是 MyBatis Plus 3.5.x而不是原生 MyBatis 或 Spring Data JPA。MyBatis Plus 在毕设场景下的优势非常突出内置LambdaQueryWrapper和LambdaUpdateWrapper写条件查询不用拼字符串提供代码生成器从数据库表一键生成实体、Mapper、Service、Controller 的骨架代码。这意味着你能把更多时间留给核心业务逻辑而不是浪费在重复的 CRUD 代码上。需要注意的是用 MyBatis Plus 不等于抛弃 SQL 能力。比如订单统计、报表查询这类复杂聚合我会在 Mapper 里写自定义 XML SQL因为QueryWrapper处理 group by 的代码可读性实在一般不如原生的SELECT DATE(create_time), COUNT(*) ... GROUP BY直观。2.2 单体应用还是微服务多端对接时的接口边界技术选型时还有一个高频追问既然宠物咖啡馆以后可能有小程序、App、门店收银端那接口要不要拆成微服务我的答案是明确不做微服务整个系统保持“一个 Spring Boot 工程 前端分离”的单体架构。这里要解释一下单体与微服务的边界。毕设项目规模在几千行代码量级数据表大概十几张微服务的拆分只会带来几个问题服务间调用需要引入 Feign、注册中心要搭 Nacos 或 Eureka、分布式事务几乎无法规避、部署环境变复杂。而 Spring Boot 的单体应用启动就是一个进程所有模块共享同一个数据源事务控制由 Spring 统一管理开发体验和调试效率都高得多。至于“对外提供接口给第三方时应该放在哪里”——我当时的做法是在单体内部按业务域拆包比如controller/user/、controller/admin/、controller/open/。把面向第三方或未来端侧的接口单独放在open包下统一加上/open/api前缀走独立的鉴权逻辑比如 Access Key 签名这样即使以后要拆服务这部分接口也能整体搬走。这个思路可以在答辩时展开聊比“我用了微服务”更能体现架构意识。2.3 鉴权、缓存、接口文档与监控的配套选型配套工具这块我列一个清单是我实际使用的方案功能选型理由登录鉴权JWT Spring Boot Starter Redis无状态、多端共享登录态、可手动登出缓存Redis首页热点数据、token 黑名单、预约时段计数接口文档Knife4jSwagger 增强版自动生成接口页面演示时方便快速查看监控Spring Boot Admin可视化查看服务状态、内存和日志级别调整工具库Hutool日期转换、Bean 拷贝、生成随机文件名等常用能力数据库连接Druid 连接池官方监控页面可看 SQL 执行情况为什么缓存我坚持用 Redis 而不是 ConcurrentHashMap虽然本地 Map 也能缓存但要处理过期时间、并发淘汰逻辑而且重启就丢。Redis 做缓存是面试和答辩加分项它还顺便解决了 JWT 登出时的 token 失效问题——登录后把 token 存进 Redis 并设置过期时间登出时删掉即可。3. 数据库设计订单、宠物档案与状态机的核心逻辑3.1 核心表结构与字段设计数据库设计是整个项目最花时间、也最值得好好写的地方。我把核心表列出来配套说明每张表的用途表名用途关键字段user用户表id、phone、password、nickname、avatar、roleUSER/EMPLOYEE/ADMIN、statuspet宠物档案表id、name、species猫/狗等、breed、age、gender、description、status、cover_imagepet_health_record宠物健康记录表id、pet_id、record_date、temperature、vaccine_date、physical_condition、remarkproduct商品表咖啡/宠物零食/周边id、name、category、price、stock、image、statuscart_item购物车表id、user_id、product_id、quantityorders订单表id、order_no、user_id、total_amount、status、pay_timeorder_item订单明细表id、order_id、product_id、product_name、product_image、price、quantityreservation座位/宠物互动预约表id、user_id、pet_id、reserve_date、start_time、end_time、statusadoption_apply领养申请表id、user_id、pet_id、reason、experience、status、review_remarkannouncement公告表id、title、content、create_time这里有一个细节值得注意订单表为什么要加order_no唯一索引因为订单号在对外展示、支付回调、线下核销时都要用不能让同一个订单号出现两次。我生成的规则是yyyyMMddHHmmss 用户ID后四位 4位随机数虽然极端情况下可能重复但加了唯一索引兜底后插入冲突时重试一次即可保证百分百不重复。3.2 宠物状态流转用状态机约束业务边界宠物状态是整个系统里最有“逻辑味道”的部分因为领养、下架、暂停互动这些操作不是随意发生的。我定义了彩票级的业务状态集合说实话后期在整个业务里起的效果比预想的大宠物的status字段我设计为0在店休养中新到店或身体不适暂不开放互动1互动中正常开放用户预约和展览2领养申请中已有用户提交申请宠物暂时锁定3已领养宠物已离开咖啡店状态流转规则我放在了 Service 层统一封装不允许 Controller 里直接改pet.status。比如用户提交领养申请时会先执行一个update pet set status 2 where id ? and status 1如果影响行数为 0说明宠物已被锁定或不在互动状态直接提示“该宠物暂不可申请领养”。管理员审核通过后状态才变更为 3同时把申请人的 ID 回写到宠物的adopted_by字段记录归属。这个设计在答辩时可以强调你并没有把所有业务规则散落在各个接口里而是收敛到状态机层避免出现“订单都支付了商品库存还是扣少了”这类状态错乱问题。3.3 订单流程的字段设计与金额处理订单模块我借鉴了常规电商的简化流程状态字段0待支付1已支付/制作中2已完成已取餐3已取消4退款金额字段必须用BigDecimal千万不要用double或float。道理很简单0.1 0.2在浮点运算里并不精确等于0.3涉及钱哪怕差一分都是bug。我在数据库里用的是DECIMAL(10,2)Java 实体里用BigDecimal计算时用setScale(2, RoundingMode.HALF_UP)做四舍五入。购物车结算也全部走事务先校验库存再扣减然后生成订单和明细最后清空购物车。订单创建这个操作必须加Transactional因为涉及多张表的写入任何一步失败都要整体回滚否则会出现“订单明细有了订单总表还是空的”这种脏数据。我在开发初期就掉进过这个坑当时少写了一个try-catch回滚直接导致测试库出现了孤儿订单。4. 核心功能实现从用户端到管理端的闭环流程4.1 登录鉴权与角色权限控制登录模块我用的是 JWT Redis 双保险方案。流程是用户提交手机号和密码后端校验成功后生成 token把用户 ID 和角色放进载荷同时把 token 存进 Redis设置过期时间为 24 小时。之后所有请求携带Authorization: Bearer token由拦截器统一解析。这里我加了两个实用的细节第一是角色权限注解。自定义一个RequireRole注解支持ADMIN、EMPLOYEE、USER三种角色拦截器中读取 token 里的角色做匹配不匹配就返回 403。比如领养审核接口就标了RequireRole(ADMIN)普通用户即使能猜到接口地址也调用不了。第二是登出逻辑。前端登出时除了删除本地 token还要请求后端把 token 从 Redis 删除这样可以让 token 立即失效。如果没有这个步骤用户在有效期内的 token 都是可用的那 JWT 的“无状态”就变成了安全隐患。代码骨架大概是这样的public boolean logout(String token) { // token 形如 Bearer xxxxx String realToken token.replace(Bearer , ); String key login:token: realToken; // 直接删除后续访问拦截器会读取不到 key 而拒绝 return redisTemplate.delete(key); }4.2 点单、预约与管理端接口的设计逻辑用户端下单流程是标准操作我简化成五个步骤加入购物车 → 确认结算 → 创建订单状态为待支付→ 模拟支付→ 扣减库存并更新状态。因为是毕设不会真的接微信支付或支付宝我用了“模拟支付”方式页面调后端/pay/mock接口后端把订单状态从 0 更新为 1同时执行库存扣减。如果有精力也可以接支付宝沙箱环境能增加的亮点是支付回调 验签逻辑。但实测沙箱文档对新手不算友好完全可以用模拟支付代替不影响整体评分。预约模块的核心是防止同一个座位或同一只宠物在同一时间段被重复预约。最简单的方案是对reservation表加“唯一索引 插入前检查”但更稳妥的是在创建预约的方法上加Transactional先用select ... for update锁定目标宠物记录确认当前状态可预约及时间不重叠后再插入。我在实现时用的是 MyBatis Plus 加自定义 SQLSELECT * FROM reservation WHERE pet_id #{petId} AND reserve_date #{date} AND status IN (1, 2) FOR UPDATE锁定后如果查到冲突预约直接抛出业务异常回滚。这样即使两个用户同时提交同一只宠物也不会被重复预约。管理端这一侧我按“宠物管理、商品管理、订单管理、用户管理、领养审核、数据统计”六个菜单做的。数据统计页用了 ECharts 展示近 7 日订单量折线图和宠物互动次数排行榜数据来源是 Mapper 里的聚合查询SELECT DATE(create_time) AS day, COUNT(*) AS cnt FROM orders WHERE create_time #{startTime} GROUP BY DATE(create_time) ORDER BY day DESC这类统计报表不需要提前建什么数仓MySQL 的聚合函数完全够用但这个功能在演示时非常出效果。4.3 领养申请审核的流程实现领养审核是宠物咖啡馆系统区别于普通商城的特色功能。用户提交领养申请时填写申请理由、养宠经验、居住环境等信息后端创建adoption_apply记录状态为 0待审核同时把宠物状态从 1 改为 2锁定。管理员看到待审核列表后可以选择通过或拒绝通过更新申请表状态为 1已通过宠物状态改为 3已领养写入adopted_by 申请人ID。拒绝更新申请表状态为 2已拒绝填入拒绝原因宠物状态回滚为 1恢复可互动、可被其他人申请。这个流程最关键是状态回滚。我一开始只做了“通过”的逻辑忘记写“拒绝后恢复宠物状态”结果测试时发现被拒绝的宠物从此消失在互动列表里折腾了半天才定位到是状态没回滚。这也是为什么我后来反复强调状态机要集中管理——否则每一个网络分支都可能漏改状态。5. 开发中真正踩过的坑5.1 并发修改订单与宠物状态的脏读问题做毕设最容易忽略的就是并发因为本地开发只有一个用户在测试永远不会触发并发场景。但答辩时评委可能会问“两个用户同时领养同一只宠物怎么办”我当时没有直接答“我加了锁”而是在代码里真的加了乐观锁方案Version private Integer version;MyBatis Plus 的乐观锁插件会在更新时自动拼接WHERE version ?并把version 1写入更新语句如果影响行数为 0说明数据已被别人改过需要重试或提示失败。在宠物领养审核中我会先查出宠物当前版本号再执行状态变更更新避免两个管理员同时审核通过同一个申请导致宠物被两个人“领养”。另外下单扣库存时我也用了条件更新保证库存不会变成负数boolean success productService.update( new LambdaUpdateWrapperProduct() .eq(Product::getId, productId) .gt(Product::getStock, quantity) .setSql(stock stock - quantity) );这种写法把“校验库存大于零”和“扣减库存”合并成一条原子 SQL不加任何锁也能防止超卖。5.2 图片上传与静态资源映射的坑图片上传是几乎所有 Web 项目都会踩的坑宠物咖啡馆系统里更是有大量宠物照片、商品图片、用户头像需要处理。我最初把图片直接保存到项目 resources 目录下后来发现两个问题第一通过java -jar运行时路径和开发时路径不一样上传后图片秒变404第二资源目录下的文件在打包后是只读的重启后可能丢失。我的最终方案是上传时把图片保存到服务器本地的一个独立目录比如/data/pet-cafe/upload/文件名用 UUID 重命名避免中文和特殊字符问题通过自定义配置把该目录映射为虚拟路径/upload/**这样访问/upload/pet/123.jpg就能直接读取磁盘文件数据库里只存相对路径/upload/pet/123.jpg不存完整域名方便以后迁移到对象存储另外还要注意 Nginx 或 Spring Boot 默认上传大小限制。spring.servlet.multipart.max-file-size默认是 1MB宠物照片动不动就好几 MB必须手动调大不然前端图片上传会莫名失败还很难定位。5.3 前后端联调时的跨域与时间格式问题我这几年做项目学到的最重要的一件事是后端接口写好了和前端联调时才是真正的“开工”。宠物咖啡馆平台我采用的是前后端分离前端 Vue 开发时跑在 5173 端口后端在 8080 端口直接请求必然跨域。解决方式我用了两种后端启用统一跨域配置表示允许来自前端开发服务器的请求并允许携带内容类型与认证信息生产部署时用 Nginx 反向代理把/api请求转发到后端的 8080 端口避免跨域我还遇到过一个典型问题数据库存的是datetime返回给前端变成了“2025-06-01T12:00:00”这种形式时区还是 UTC。后端在实体类的日期字段上加了JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)同时数据库连接串里加了serverTimezoneAsia/Shanghai问题才真正解决。这个问题如果不提前处理前端展示时间会整整差 8 个小时用户看到的“预约记录到店时间”永远不对。6. 部署上线与答辩准备的实践经验6.1 从本地打包到服务器部署的注意点毕设评审一般只看演示效果但如果你能把系统真正部署到云服务器上交互体验和稳定性会上升一个档次。我采用的部署方式是前后端同机部署后端mvn clean package -DskipTests打出一个可执行 jar放到服务器/opt/pet-cafe/目录下用nohup java -jar pet-cafe.jar --spring.profiles.activeprod 启动前端Vue 项目执行npm run build生成dist目录把dist里的静态文件整体放到/usr/share/nginx/html/Nginx 配置好/api反向代理这样用户访问 80 端口看到页面、后端挂在/api全程只有一个域名和一个入口数据库服务器上装 MySQL 8.x把本地的建表 SQL 导入特别注意账号权限否则启动时报Access denied for user很难一眼看出这里要提醒的是application-prod.yml里的数据库密码、Redis 密码不要用明文写死写在代码仓库里虽然毕设无所谓但是如果能用环境变量替代在答辩时会显得更专业${DB_PASSWORD}这种占位符由服务器环境变量注入。6.2 演示数据的准备与答辩问题的应对演示环节我踩过一次比较大的亏第一次模拟答辩时我发现宠物列表里一只猫都点不开因为测试数据是在本地数据库随便瞎填的图片路径全是乱写的演示效果极其尴尬。后来我专门做了一套“演示数据专属包”把所有宠物、商品都配了真实可访问的图片 URL订单和预约数据也按过去 7 天分布造了一批保证折线图和排行榜有分析价值。另外我会提前整理一个“答辩问题预热清单”把评委最可能追问的技术点都准备好为什么用 Spring Boot 不用 SSM——生态、自动配置、内嵌容器最重要的是社区资料丰富遇到问题能快速找到解决方案MyBatis Plus 和 MyBatis 有什么区别—— MP 本质是对 MyBatis 的增强没有侵入性内置通用 CRUD 方法但复杂 SQL 还是走自定义 XML如果用户量和数据量增长架构怎么优化—— 数据库读写分离、Redis 缓存热点数据、静态资源走对象存储必要时按业务域拆分微服务登录为什么选 JWT —— 无状态适合前后端分离配合 Redis 可以主动失效同时支持多端共享会话我还额外为项目配置了 Spring Boot Admin 监控把服务状态、JVM 内存、日志级别切换都可视化出来。虽然本地开发时用处不大但在答辩现场打开这个监控页面时完全可以把项目从“增删改查”拉到“工程化产品”的高度。这个项目从最开始的需求确认到最终部署上线前后大概花了一个半月时间。回头复盘最庆幸的是没有一上来就疯狂写代码而是先花了几天把表结构和状态流梳理清楚。宠物咖啡馆这个题材刚好踩在了传统餐饮系统和情感互动类业务中间既有常规的订单库存逻辑又有领养审核这种偏运营向的功能做起来不枯燥答辩时也很有讲头。如果你也准备做类似的题目最后再给你一个小建议不要只满足于功能跑通在代码里留下几个“你认真设计过”的痕迹比如状态机约束、条件更新防超卖、统一异常处理、参数校验注解。这些细节在答辩时的价值远比你多写两个 CRUD 接口要大得多。
返回列表