基于SpringBoot+Vue的宠物咖啡馆平台管理系统设计与实现
1. 项目背景与需求分析:宠物咖啡馆到底需要一个什么样的系统
毕业设计选课题的时候,我盯着题目列表翻了好几页,最后锁定了“基于SpringBoot+Vue的宠物咖啡馆平台管理系统”这个方向。原因很简单:宠物经济和咖啡馆消费都是年轻人高频接触的场景,把它俩结合起来做系统,业务逻辑不会太复杂,但又覆盖了用户、商品、订单、预约、领养寄养等完整的业务链条,非常适合用来练一遍全栈开发的完整流程。
先说清楚这套系统到底解决什么问题。一家宠物咖啡馆,日常运营其实比普通咖啡馆麻烦得多。普通咖啡店只需要管好商品和收银,宠物咖啡馆还要额外管理店里的“员工”——那些猫猫狗狗。每只宠物的健康状况、疫苗记录、是否可领养、是否处于寄养状态,这些都需要登记。同时客户也会提前预约到店体验、提交领养申请、购买宠物零食和咖啡饮品。这些数据如果全靠Excel和微信群来维护,店铺一忙起来必然乱套。这个系统的核心价值就是把“顾客消费”和“宠物管理”两条线统一到一个平台上。
系统的适用人群也很明确。如果你是Java后端方向的在校生,想做一个拿得出手的毕设项目,那这个课题很合适;如果你是自己开店想搞一套内部管理工具,这套架构同样可以直接改造落地。整个系统采用SpringBoot+Vue前后端分离架构,后端提供RESTful API,前端用Vue框架做单页面应用,数据库用MySQL,持久层用MyBatis。下面我会从需求拆解、技术选型、数据库设计、核心实现到填坑经验,把整个过程完整复盘一遍。
1.1 角色梳理与核心业务闭环
任何管理系统第一步都是先搞清楚“谁在用”和“用户要完成什么事”。这套宠物咖啡馆平台我划分了三种角色:
- 系统管理员:管理门店的宠物档案、审核领养/寄养申请、发布公告和轮播图、查看经营数据。
- 门店店员:处理到店订单、更新宠物在店状态(在店/离店/寄养中)、维护商品库存。
- 普通用户(会员):注册登录后查看宠物信息、在线点单、预约到店、提交宠物领养或寄养申请。
从业务闭环来看,用户在小程序或网页端看到一只想领养的猫,先提交领养申请,管理员在后台审核通过,用户到店办理交接,宠物状态从“待领养”变为“已领养”,这一条链路要能在系统里完整走通。同样地,用户下单咖啡,订单状态要经历待支付、制作中、已完成三个状态;用户提交寄养预约,店员确认后生成寄养记录,寄养到期自动提醒续费。把主流程画清楚后,建表结构、写接口、做页面才会有依据,否则就是想到哪写到哪。
1.2 功能模块如何划分前后端
这套系统的功能清单,我是从运营角度倒推出来的。前端用户端必须有:首页轮播展示在店宠物、宠物详情页(包含疫苗信息和领养入口)、饮品零食菜单、购物车、个人中心、领养申请表、寄养预约表单。后台管理端则分为:宠物档案管理、商品管理、订单管理、预约审核、会员管理、公告管理和数据看板。
这个功能划分对应到前端就是两套Vue应用。用户端和后台管理端我用了不同的路由布局,用户端走的是门户风格,导航栏放“首页”、“宠物领养”、“菜单点单”、“公告”;管理端是侧边栏布局,所有功能入口收进一个菜单树里。前后端通过接口约定对接,把所有请求路径、参数和返回结构列成一个接口文档,这一步在后期联调中帮我省了大量时间。
2. 技术选型思路:这套前后端分离的架构是怎么定下来的
技术选型这块我是站在“能毕业、能上线、好维护”三个角度综合考虑的。SpringBoot+MyBatis+MySQL是Java后端岗位面试中最常被问到的组合,Vue则是前端框架里学习曲线最平缓的之一。这套组合的另一大优势是社区资料极多,几乎你遇到的每个报错都能在网上找到对应答案,对新手非常友好。
2.1 SpringBoot+MyBatis,Java后端为什么这么稳
SpringBoot的核心价值在于“自动配置”。以前用SSH或者SSM,光配置XML就要折腾一整天,SpringBoot通过starter机制把常用的依赖打包,配好数据源和扫描路径就能直接写业务代码。我这套系统用的是SpringBoot 2.7.x版本,配合MyBatis 2.x的starter依赖,几乎没有写过复杂的XML配置。
MyBatis作为持久层框架,最方便的一点是SQL自己掌控,不像是JPA那样自动生成SQL导致性能调优困难。对于宠物咖啡馆这种业务,查询条件非常灵活,比如“按品种查宠物+按状态筛选+按更新时间排序”,MyBatis动态SQL可以很优雅地拼出对应语句。还有一点,MyBatis的嵌套查询和结果映射足够应付绝大多数场景,学习成本比JPA低不少。
2.2 Vue前端与Element-UI的配合
前端我选了Vue2 + Element-UI这个经典组合。虽然Vue3+Element Plus已经是新趋势,但是Vue2的教程量和踩坑经验积累更丰富,作为毕设项目,稳定压倒一切。Element-UI提供的表格、表单、弹窗、分页组件几乎覆盖了后台管理系统90%的界面需求,写页面时不用从零去调样式,把时间花在业务逻辑上。
对于前端项目结构和数据交互,我重点做了三件事:用Vue Router配置路由守卫,实现未登录跳转;用Axios统一封装请求拦截器和响应拦截器,请求时自动携带token,响应时统一处理错误码;用Vuex管理全局用户信息和购物车状态。这三件事做扎实之后,后面开发每个页面的效率提升明显,不用反复写重复代码。
2.3 数据库选型与整体架构分层
数据库直接选MySQL 8.0,这也是当前的主流版本。相比5.7,8.0在窗口函数、JSON支持、默认字符集utf8mb4方面都更完善。因为咖啡店商品名、宠物品种名可能包含emoji表情,数据库统一用utf8mb4编码,避免存储emoji时报错。
系统整体架构按照经典的三层结构来分:
- Controller层:接收前端请求,参数校验,调用Service接口。
- Service层:业务逻辑,事务控制,例如创建订单时要同时扣减库存和生成订单明细。
- Mapper层:MyBatis接口,负责数据库操作。
这个分层的好处是各层职责单一,出了问题能快速定位到对应层。比如前端传参格式不对,问题基本在Controller层;扣库存失败,问题在Service层;SQL报错,直接去查Mapper的XML文件。
3. 数据库设计:从业务表到字段,一张一张拆给你看
数据库设计是整个项目的地基。表设计不合理,后期写功能会处处别扭。我是按照“用户-宠物-商品-订单-预约”五条主线来建表的,一共设计了10张核心表。下面挑几张重点表展开说说设计思路。
3.1 用户与权限模块
用户表(user)是最基础的,字段包括:id、username、password、nickname、phone、avatar、role、balance、create_time。角色字段我直接用了字符串类型,取值有ADMIN、STAFF、USER。虽然用RBAC表结构可以做到更细粒度的权限控制,但毕设场景下角色的数量固定,角色字段放用户表里是最简单的方案,查询也快。
密码存储一定不能用明文。我用了Spring Security自带的BCryptPasswordEncoder对密码做哈希加密,即使数据库泄露也不会直接暴露明文密码。这里要注意的是,BCrypt每次生成的哈希值都不同,所以登录校验要用matches()方法比对,而不是简单地equals。
还有一个容易忽略的表——会员充值记录表(recharge_record)。宠物咖啡馆的会员经常办卡充值,系统需要记录每次充值的金额、赠送金额,以及充值后余额的变化。我设计这张表时加了一个type字段区分“充值”和“消费”,这样查账时直接按用户分组就能看到资金流水。
3.2 宠物档案与领养寄养模块
宠物表(pet)的字段最能体现行业特性:name、species(猫/狗/其他)、breed(品种)、age、gender、health_status(健康状态)、vaccine_status(疫苗状态)、status(在店/待领养/寄养中/已领养)、image、description、create_time。其中health_status我用数字表示,0代表健康,1代表观察期,2代表治疗中,这个枚举对应关系写在系统常量里,避免散落在代码各处。
领养申请表(adoption_application)是整个宠物模块里最重要的业务表。字段包括:id、pet_id、user_id、real_name、phone、address、reason(领养理由)、experience(养宠经验)、status(待审核/通过/拒绝)、create_time。我加了一个experience字段,这对管理员审核非常有用,能筛掉一部分不具备养宠条件的人。审核通过后,宠物状态同步从“待领养”改为“已领养”,这个状态变更需要放在同一个事务里。
寄养预约表(boarding_appointment)字段包括:pet_id、user_id、start_date、end_date、daily_price、total_price、remark、status。寄养费用的计算逻辑是服务端算的,前端提交日期后返回预估总价给用户确认。为了防止重复预约同一时间段,我在数据库层对pet_id和日期区间做了约束,查询时判断新预约的开始日期是否小于已有预约的结束日期,且结束日期大于已有预约的开始日期,代表时间冲突。
3.3 商品订单与预约模块
商品表(product)比较简单,主要字段有:name、category(饮品/甜点/宠物零食)、price、stock、image、status(上架/下架)。订单表(orders)我拆了两张,避免单表字段过多:订单主表和订单明细表。订单主表存储订单编号、用户ID、总金额、支付状态、订单状态、创建时间;订单明细表存储每个商品的ID、数量、单价、小计。这样做的好处是统计营业额时直接走主表group by,查某个订单包含哪些商品时走明细表,查询效率更高。
到店预约表(visit_reservation)用来管理“顾客预约到店撸猫”这个场景。特别是周末,门店人流量大,不控制预约会导致店里宠物压力过大。字段包括:user_id、reserve_date、time_slot(上午/下午/晚上)、people_count、status。门店后台可以根据每天的预约人数调整接待上限,这个小小设计在跟店长交流时被点赞了,说明它真正贴合了实际运营需求。
4. 核心功能实现:登录鉴权、宠物管理、预约流程的编码思路
功能和表设计好以后,编码阶段的重点就变成了“怎么把功能流畅跑通”。这一节我会讲几个核心模块的具体实现方案,代码不会完整贴出,但关键实现思路和核心代码片段会给到,方便大家复现。
4.1 基于JWT的登录鉴权
前后端分离架构下,Session方案天然不合适,因为跨域请求默认不带Cookie。我采用的是JWT(JSON Web Token)方案,登录成功后后端生成一个带过期时间的token返回给前端,前端存储到localStorage,之后每次请求都在Authorization头里带上。
JWT工具类主要做三件事:生成token、解析token、校验token。生成时往payload里放入用户ID、用户名、角色,过期时间设为24小时。后端写了一个HandlerInterceptor拦截器,对除了登录、注册、首页公开接口之外的路径统一做token校验。这里有个关键细节:放行路径的配置。我当时用错误的通配符导致所有接口都被拦截,排查了半天才发现是路径匹配规则写错了。
核心拦截器代码大致如下:
public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if ("OPTIONS".equals(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { throw new BusinessException(401, "未登录或登录已过期"); } // 解析token失败会抛出异常,由全局异常处理器统一返回 Integer userId = JwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; } }4.2 宠物档案管理与图片上传
宠物档案管理的后端核心是文件上传和列表查询。图片上传我实现了一个统一的FileUploadController,接收MultipartFile文件,做类型校验(只允许jpg、png、webp),限制大小在5MB以内,存储到服务器的本地目录,同时把访问路径映射成/static/**静态资源。之所以没用OSS等云存储,是因为毕设项目直接部署到一台云服务器,本地存储足够且省成本。
宠物列表页面的查询是典型的联表+筛选场景。前端传species、status、keyword参数,后端用MyBatis动态SQL生成条件。这里我踩过一个坑,动态SQL里如果参数值传空了,会出现SQL语法错误,后来统一在Service层做判断,只有非空参数才放入查询条件,代码反而更直观好维护。
宠物状态流转中还有一个核心方法:领养审核通过时,要把宠物状态从“待领养”改为“已领养”,同时更新申请表状态。两个操作放到同一个事务方法里,代码如下:
@Transactional public void approveAdoption(Integer applicationId) { AdoptionApplication app = adoptionApplicationMapper.selectById(applicationId); if (app == null || !"PENDING".equals(app.getStatus())) { throw new BusinessException(400, "申请不存在或已处理"); } app.setStatus("APPROVED"); adoptionApplicationMapper.updateById(app); Pet pet = petMapper.selectById(app.getPetId()); pet.setStatus("ADOPTED"); petMapper.updateById(pet); }4.3 寄养预约与订单状态流转
寄养预约的核心逻辑是“日期冲突检测”。用户提交预约之前,后端要先查该宠物在这个时间段是否已有有效预约:
public boolean isDateConflict(Integer petId, LocalDate startDate, LocalDate endDate) { List<BoardingAppointment> list = boardingAppointmentMapper.selectByPetId(petId); for (BoardingAppointment app : list) { if (!"CANCELLED".equals(app.getStatus())) { boolean conflict = startDate.isBefore(app.getEndDate()) && endDate.isAfter(app.getStartDate()); if (conflict) { return true; } } } return false; }订单状态流转我用一个状态机思想来控制,在Service层写了一个OrderStateHandler。订单状态包括PENDING_PAYMENT(待支付)、PREPARING(制作中)、COMPLETED(已完成)、CANCELLED(已取消)。前台用户点击支付后,状态从待支付变成制作中;后台店员确认出餐后,状态变成已完成。每一次状态变更都校验前置状态是否合法,避免“已完成订单被取消”这类数据异常。
订单生成流程里还有一环需要注意,就是减库存。用户在购物车页面看到的是实时库存,但并发下单时可能两个人同时买了最后一个库存。解决思路两种:乐观锁或悲观锁。毕设项目使用悲观锁比较简单直接,在查询商品时加select for update锁定行,等订单事务提交后再释放。虽然并发量不大的场景下性能影响微乎其微,但这是面试时非常加分的细节。
4.4 数据看板与统计报表
后台管理端的首页数据看板用了ECharts做图表展示:近7天营业额折线图、饮品销量排行饼图、每日预约人数柱状图。统计数据通过SQL的聚合函数直接查询,比如近7天营业额:
SELECT DATE(create_time) AS day, SUM(total_price) AS total FROM orders WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND status != 'CANCELLED' GROUP BY DATE(create_time) ORDER BY day;这里有一个数据库设计层面才能体现的优势,就是因为订单明细分离了,统计时可以直接从订单主表取数,不需要连明细表,查询效率很高。数据看板的接口建议单独包一层,不要和业务接口写在一起,这样前端只需要调一个/dashboard/data接口就能拿到所有图表数据,减少请求次数。
5. 常见问题与排查技巧实录
这套系统从开发到联调再到部署,踩过的坑不少。我把最有代表性的问题整理成了一份速查表,这些坑几乎90%的SpringBoot+Vue项目都会遇到。
5.1 MyBatis相关坑
第一个高频问题是Mapper接口和XML文件绑定失败。报错一般是Invalid bound statement (not found)。原因大多是XML文件没放进target目录,或者mapper-locations路径配置不对。我当时的解决方式是在application.yml里显式配置路径:
mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.petcafe.entity第二个高频坑是关于参数传递的。当Mapper接口方法有多个参数时,必须使用@Param注解,否则MyBatis无法识别参数名。这是因为Java编译后,如果没开启-parameters参数,方法里的参数名就变成arg0、param1这种,非常容易踩。我的习惯是接口方法参数统一用@Param标注,虽然多写几个字,但能省去大量排错时间。
第三个问题与分页有关。很多同学用PageHelper分页时,会发现最后一页查询结果不对,或者在统计条数时多带了一条SQL。这是因为PageHelper分页插件会拦截下一次查询,如果一个方法里先查询A表再查询B表,分页就会错误地作用在B表上。所以使用PageHelper时务必在第一个查询前调用startPage方法,并且不要在分页查询的Service方法里嵌套其他查询。
5.2 跨域与前端联调问题
前后端分离开发时,前端跑在localhost:8080,后端跑在localhost:9090,浏览器的同源策略拦截了跨域请求。Vue的axios报错一般是“Network Error”或者“CORS policy”相关信息。
处理方式可以有两种:前端通过Vue CLI的devServer配置代理,或者后端开启CORS。我两种都试过,开发环境用代理更可靠,因为不会暴露后端端口,部署后也不会因为跨域配置遗漏出问题。Vue的vue.config.js配置如下:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }如果后端直接开CORS,注意一定要放行OPTIONS预检请求。我自己写的JWT拦截器一开始没有放行OPTIONS,导致前端在带着自定义Authorization头请求时预检失败,接口一直报跨域错误,调试了一下午才定位到是拦截器的问题。
5.3 数据库和部署问题速查表
| 问题现象 | 常见原因 | 解决方案 |
|---|---|---|
| 启动时报Access denied for user | 数据库账号密码错误或权限不足 | 检查yml配置,执行GRANT授权命令 |
| 中文乱码 | 连接字符集未指定 | jdbcUrl加characterEncoding=utf8 |
| 时间差8小时 | 时区未配置 | jdbcUrl加serverTimezone=Asia/Shanghai |
| 图片上传后访问404 | 静态资源映射没配 | 配置addResourceHandlers指向上传目录 |
| npm install失败 | 依赖版本冲突或网络问题 | 使用npm淘宝镜像源或调整依赖版本 |
| Vue白屏没报错 | 路由模式history刷新404 | 改用hash模式或配置nginx try_files |
这里特别说一下打包部署。前端npm run build后产生dist目录,我直接放到Nginx的html目录下,然后用Nginx反向代理转发/api请求到后端服务。Nginx配置中注意把history模式的try_files配置写好,否则刷新页面会404。后端打包成jar后,用systemd或nohup命令守护进程运行即可,一般云服务器2核4G配置跑这套系统完全够用。
5.4 Java环境和版本兼容性问题
最后提一个非常容易导致返工的坑:JDK版本和SpringBoot版本的兼容性。如果你用的是SpringBoot 3.x,最低JDK版本要求是17,很多同学本机还是JDK8,直接启动就报错。我选的是SpringBoot 2.7.x配JDK8,这套组合是最稳定的搭配,网上资料也最全。
SpringBoot版本和MyBatis starter的搭配也要注意。我用的是mybatis-spring-boot-starter 2.3.x,兼容SpringBoot 2.x,但用到SpringBoot 3上必须换成mybatis-spring-boot-starter 3.x。同理,如果javax.servlet包在新版本的SpringBoot里会变成jakarta.servlet,很多老代码会直接编译不过。建议毕设直接复刻我这套版本组合:SpringBoot 2.7.18 + JDK8 + MyBatis 2.3.x + Vue2.6 + Element-UI 2.15,全程无坑。
6. 项目迭代方向与我的经验总结
系统基础功能开发完后,我的实际体会是:一个“完整”的项目,不光是功能能跑通,更重要的是业务要讲得通。所谓“讲得通”,就是你设计的每一张表、每一个字段、每一个状态变更,都能对应到真实的运营场景。面试官问你“为什么订单状态要有待支付这一步”,你不能只说“为了做支付”,而要说“门店高峰期可能几个订单同时进来,待支付状态可以给用户预留结算时间,也给店员一个明确的订单处理边界”。这种业务深度是项目能否打动评委或面试官的关键。
从扩展角度看,这套系统还有很多可以延伸的方向。比如引入微信小程序端,做扫码点单;增加消息通知功能,寄养到期自动模板消息提醒;接入在线支付,用微信支付或支付宝沙箱环境让用户在线结算;用Redis给宠物详情页做缓存,降低数据库压力。这些方向每一个都值得单独扩展,但核心架构和表设计保持不变,说明这套系统具备良好的扩展性。
如果大家要在这个项目基础上继续开发,我的建议是优先做一个“消息通知”功能。宠物咖啡馆的用户粘性很大程度靠情感连接,用户在系统里提交了领养申请,如果审核结果能通过站内信或短信通知实时触达,这个系统就不再是一个“用完即走”的工具,而是一个有温度的服务平台。这个功能改动不大,但对系统完整度和用户体验的提升非常显著。
最后说点实在的。我做这个项目最深的感受是,全栈项目最能锻炼人的地方不是写代码本身,而是做决策的能力。选型要兼顾新旧、表结构要兼顾业务和性能、接口设计要兼顾前端易用性和后端合理性,每个决策背后都有取舍。把这个过程完整走一遍,远比背面试八股文有价值。希望这套宠物咖啡馆平台的设计与实现思路,能为正在选型或开发类似项目的小伙伴提供一个可参考的起点。