news 2026/10/7 12:53:13

宠物店猫咖管理系统后端实战:Spring Boot+MyBatis从排班混乱到数据打通

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
宠物店猫咖管理系统后端实战:Spring Boot+MyBatis从排班混乱到数据打通

简介:这份源码面向Java后端初学者与需要课程设计、毕业设计参考的开发者,提供一套宠物店猫咖管理系统的后端实现方案,可用于学习Java Web项目结构、MVC分层与数据库交互等核心技能。压缩包共38个文件,约52KB,以19个Java源文件承载业务逻辑,8个XML配置文件负责框架与参数配置,另有5个class文件、2个properties属性文件、1个JSP页面及iml、gitignore等工程辅助文件,整体结构紧凑,便于快速导入IDE运行与二次开发。系统围绕宠物信息管理、客户管理、预约服务、库存管理与销售统计等模块展开,覆盖资料录入查询、消费记录跟踪、服务资源安排与销售报表分析等典型场景。目前已有433人学习下载,适合作为中小型管理系统的入门范本,帮助读者理解后端分层设计、配置组织方式与功能模块拆分思路。

1. 宠物店猫咖管理系统后端:从排班混乱到数据打通的落地路径

一家同时经营宠物洗护、猫咖计时和活体零售的门店,最容易崩在排班上。上午十点猫咖满座、洗护间排队、收银台还在手工记会员卡余额,三个岗位用三套本子,晚上对账对到怀疑人生。宠物店猫咖管理系统后端设计要解决的,就是把会员、宠物档案、服务工单、猫咖台位、库存和营收这几条线收进同一套数据模型里,让前台一次操作能同时扣台位时长、生成洗护工单、记会员消费。这套后端适合谁做?一是接门店定制外包的 Java 开发者,二是想给自己店做一套自用系统的店主兼程序员,三是拿它当 Spring Boot 综合练手项目的在校生。源码层面的价值不在界面多花哨,而在业务表怎么拆、状态怎么流转、并发台位怎么不超卖。下面按我实际搭过的一套结构,从选型一路讲到能跑起来的最小闭环。

2. 后端技术选型与数据模型:为什么是 Spring Boot 加 MyBatis

2.1 选型理由:别一上来就上微服务

宠物店猫咖这类单店或三五家连锁的场景,日订单量撑死几百单,QPS 峰值出现在周末下午,也就几十。这种量级上 Spring Cloud 那一套注册中心、网关、配置中心,纯属给自己找运维负担。我一般会选 Spring Boot 单体 + MyBatis + MySQL,理由很实在:部署就一个 jar,门店一台小主机或一台云服务器就能跑;MyBatis 对复杂查询和手写 SQL 友好,宠物店的报表统计(比如某只猫这个月洗了几次、某会员卡剩余次数)用 XML 写比 JPA 拼条件清爽得多。

技术栈落到具体版本,我常用的是 JDK 17 + Spring Boot 3.x + MyBatis-Plus + MySQL 8 + Redis。Redis 只干两件事:猫咖台位计时用的临时状态、登录 token。别拿它当主存储,门店断电重启后 Redis 数据丢了,台位时长还得能从数据库恢复,所以计时结果要定期落库。

提示:如果团队只有你一个人,别碰分库分表和消息队列。先把单表索引和事务边界理清楚,比什么架构都值钱。

2.2 核心表结构:六张表撑起一个店

数据模型是这套后端的骨架,拆错了后面全是补丁。我踩过的坑是早期把「服务」和「商品」混在一张表,结果洗护要记宠物、零售要记库存,字段互相打架。后来拆成下面这几张核心表,逻辑就顺了。

表名作用关键字段
member会员账户id, phone, balance, card_times, level
pet宠物档案id, member_id, name, breed, weight, note
service_item服务/商品定义id, type(洗护/零售/猫咖), name, price, duration
order_main订单主表id, member_id, total, status, pay_type, created_at
order_item订单明细id, order_id, item_id, pet_id, qty, price
cafe_seat猫咖台位id, seat_no, status, start_time, order_id

这里有个设计取舍:猫咖计时到底算订单还是算台位状态?我的做法是两者都要。cafe_seat 记录当前占用和开始时间,用于实时展示和超时提醒;结算时再生成一条 order_main,把时长换算成金额写进 order_item。这样即使计时中途系统重启,也能从 start_time 重新算,不会丢单。

2.3 用 MyBatis-Plus 生成基础 CRUD

建表之后,实体和 Mapper 不用手写。MyBatis-Plus 的代码生成器能省掉大量重复劳动,配置一次就能把六张表的增删改查全生成出来。

// CodeGenerator.java —— 一次性生成实体、Mapper、Service public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create( "jdbc:mysql://localhost:3306/petcafe?useUnicode=true&characterEncoding=utf8", "root", "your_password") .globalConfig(builder -> builder .author("dev") .outputDir(System.getProperty("user.dir") + "/src/main/java") .disableOpenDir()) .packageConfig(builder -> builder .parent("com.petcafe") .entity("entity") .mapper("mapper") .service("service")) .strategyConfig(builder -> builder .addInclude("member", "pet", "service_item", "order_main", "order_item", "cafe_seat") .entityBuilder().enableLombok() .mapperBuilder().enableBaseResultMap()) .execute(); } }

逻辑说明:addInclude里列出要生成的表,避免把无关表也扫进来。enableLombok让实体自动带 getter/setter,省掉几百行样板代码。参数上,outputDir指向你的源码根目录,parent是基础包名,改这两处就能适配自己的工程。生成完先别急着写业务,把生成的实体字段和数据库列对一遍,尤其是created_at这类下划线命名,确认驼峰映射开了没有,否则查出来全是 null,这种玄学问题排查起来很费时间。

3. 会员、宠物与订单:三条业务线的接口实现

3.1 会员与宠物档案的绑定关系

会员和宠物是一对多,一个会员可以带好几只猫来洗护。接口设计上,新增宠物时必须校验 member_id 存在,删除会员时不能级联删宠物(历史订单还要引用),只能标记会员失效。这是血泪经验:早期做了物理删除,结果老订单查不到宠物信息,对账直接翻车。

@PostMapping("/pet/add") public Result addPet(@RequestBody Pet pet) { Member m = memberMapper.selectById(pet.getMemberId()); if (m == null || m.getStatus() == 0) { return Result.fail("会员不存在或已停用"); } pet.setCreatedAt(LocalDateTime.now()); petMapper.insert(pet); return Result.ok(pet.getId()); }

逻辑说明:先查会员再插宠物,保证外键语义。status == 0表示会员已停用,停用会员不允许再挂新宠物。参数上pet.getMemberId()由前端传入,别信前端传的会员等级或余额,那些字段一律后端查库覆盖,防止被改包。

3.2 下单接口:一次事务里干完四件事

下单是整套后端最核心也最容易出并发问题的地方。一次洗护下单,要同时:扣会员卡次数或余额、生成订单主表、写订单明细、如果是猫咖还要占台位。这四步必须在一个事务里,任何一步失败全部回滚。

@Transactional(rollbackFor = Exception.class) public Long createOrder(OrderDTO dto) { // 1. 悲观锁锁定会员,防止并发扣款 Member member = memberMapper.selectForUpdate(dto.getMemberId()); if (member.getCardTimes() < dto.getNeedTimes()) { throw new BizException("会员卡次数不足"); } // 2. 扣次数 member.setCardTimes(member.getCardTimes() - dto.getNeedTimes()); memberMapper.updateById(member); // 3. 写订单主表 OrderMain order = new OrderMain(); order.setMemberId(member.getId()); order.setTotal(dto.getTotal()); order.setStatus(1); // 1=已支付 orderMapper.insert(order); // 4. 写明细 for (OrderItemDTO it : dto.getItems()) { OrderItem oi = new OrderItem(); oi.setOrderId(order.getId()); oi.setItemId(it.getItemId()); oi.setPetId(it.getPetId()); oi.setQty(it.getQty()); oi.setPrice(it.getPrice()); orderItemMapper.insert(oi); } return order.getId(); }

逻辑说明:selectForUpdate走的是数据库行锁,同一会员并发下单时后一个请求会等前一个提交,避免次数被扣成负数。rollbackFor = Exception.class保证任何异常都回滚,默认只回滚运行时异常,受检异常不回滚,这个坑很多人踩过。参数上needTimes是本次消耗的卡次数,total是金额,两者分开是因为有的项目按次卡、有的按储值,别混成一个字段。

3.3 猫咖台位计时:Redis 加定时落库

猫咖按小时收费,台位状态要实时。我用 Redis 存seat:no:{seatNo}的占用开始时间戳,前台开台时写入,结账时读出算时长。同时每五分钟有个定时任务把占用中的台位同步到 cafe_seat 表,防止 Redis 丢数据。

// 开台 public void openSeat(String seatNo, Long orderId) { String key = "seat:no:" + seatNo; Boolean ok = redis.opsForValue() .setIfAbsent(key, String.valueOf(System.currentTimeMillis())); if (Boolean.FALSE.equals(ok)) { throw new BizException("台位已被占用"); } cafeSeatMapper.updateStatus(seatNo, 1, orderId); }

逻辑说明:setIfAbsent就是 SETNX,保证同一台位只能被一个人开,天然防超卖。参数上存的是毫秒时间戳,结账时(now - start) / 3600000向上取整算小时。注意别用set覆盖写,那样并发开台会互相顶掉,台位就乱了。

4. 并发与数据一致性:台位超卖和余额扣负怎么防

4.1 台位超卖的三种防法对比

猫咖周末满座,两个前台同时点同一个台位开台,如果不做控制就会一台两卖。常见做法有三种,我实际都用过,对比如下。

方案实现优点缺点
数据库唯一索引cafe_seat 加占用唯一约束简单可靠冲突时抛异常要捕获
Redis SETNX开台前抢锁快,适合高并发依赖 Redis 可用性
悲观锁 select for update锁行再改状态一致性强并发高时排队

单店场景我推荐 Redis SETNX 加数据库兜底:Redis 挡掉绝大部分并发,数据库唯一索引作为最后一道防线。别只靠应用层判断「查一下没占用就开」,那中间有时间窗口,必翻车。

4.2 余额扣负的排查思路

有次上线后发现某个会员余额变成负数,查了半天是退款接口没加锁。退款时先读余额再写回,两个退款请求同时进来,都读到 100,各自加 50 写回,结果少了 50 的账。解决办法和下单一样,退款也走selectForUpdate锁会员行。排查这类问题有个笨办法但很有效:把所有涉及金额变动的接口列出来,逐个检查有没有在事务里先锁行。漏一个就是一个隐患。

注意:金额字段一律用 DECIMAL 或分为单位的整数,别用 float/double,浮点误差累积起来对账能差出几块钱,查起来毫无头绪。

4.3 事务失效的几个隐蔽场景

@Transactional不是万能的,下面几种情况它不生效,我全踩过:同类内部方法直接调用(this 调用不走代理)、方法不是 public、异常被 catch 吞掉没抛出、数据库引擎是 MyISAM 不支持事务。最隐蔽的是第一种,你把下单逻辑抽成一个 private 方法在同类里调,事务注解形同虚设。解决要么把方法挪到另一个 Bean,要么注入自己代理调用。

5. 避坑与排查:上线后最容易翻车的五个点

5.1 时间字段时区不一致

现象:前台显示开台时间是下午三点,数据库里存的是早上七点,差八小时。原因:JVM 时区和 MySQL 时区没对齐,一个 UTC 一个东八区。解决:连接串加serverTimezone=Asia/Shanghai,JVM 启动参数加-Duser.timezone=Asia/Shanghai,两边统一。这个坑不报错,只是数据悄悄错,对账时才发现。

5.2 分页查询总数不准

现象:订单列表第一页显示 10 条,总数显示 8 条。原因:MyBatis-Plus 分页插件没配,或者 count 语句被自定义 SQL 覆盖了。解决:注册PaginationInnerInterceptor,自定义查询时确认 count 逻辑和 list 逻辑的 where 条件一致。别小看这个,前台看到总数比列表还少会直接投诉。

5.3 定时任务重复执行

现象:台位落库任务在集群部署时跑了两遍,数据被写重。原因:多实例都触发了定时任务。解决:单店单实例无所谓,一旦多实例,用 Redis 分布式锁或数据库唯一约束保证同一时刻只有一个实例执行。别用@Scheduled裸跑就上多节点。

5.4 会员卡次数并发扣减

现象:一张 10 次卡,两个前台同时核销,最后扣了 11 次。原因:读改写没加锁。解决:见 3.2 的selectForUpdate,或者用update member set card_times = card_times - 1 where id = ? and card_times >= 1这种带条件的原子更新,判断影响行数是否为 1。

5.5 日志里打印了敏感信息

现象:日志文件里能看到完整手机号和会员余额。原因:调试时随手log.info打了整个对象。解决:用@JsonIgnore或自定义 toString 屏蔽敏感字段,日志级别上线调成 INFO 以上。这个不算功能 bug,但门店数据泄露是实打实的风险。

6. 从能跑到好用:接口压测与灰度上线的具体做法

系统能跑通只是及格线,门店真用起来还得扛住周末高峰。我一般会做两件事:压测和灰度。

压测不用上 JMeter 那么重,用 wrk 或 ab 对下单接口打一轮就够。命令是wrk -t4 -c50 -d30s --latency http://localhost:8080/api/order/create,重点看 P99 延迟和错误率。单店场景 P99 控制在 200ms 以内、错误率为零就算过关。压测时把日志级别调高,否则 IO 会拖慢结果,测出来的数不准。

灰度上线我的习惯是先在门店打烊后部署,第二天早班只让一个前台用新系统,老系统并行留着。观察一天没问题再全量切。别在周末下午高峰期上线,那是拿生意做实验。

再给一个验证数据一致性的小技巧:写个对账脚本,每天凌晨跑一次,比对会员卡次数变动总和与订单消耗次数总和是否相等,不等就告警。这个脚本帮我抓到过两次并发扣减的漏网之鱼,比事后人工翻账本高效得多。

-- 对账:会员卡消耗次数 vs 订单实际核销次数 SELECT m.id, m.card_times AS remain, (SELECT COUNT(*) FROM order_item oi JOIN order_main o ON oi.order_id = o.id WHERE o.member_id = m.id AND o.status = 1) AS used FROM member m WHERE m.card_times < 0;

这条 SQL 专门揪出余额或次数为负的会员,跑出来有结果就说明并发控制有漏洞,赶紧回去查锁。

我自己的习惯是每接一个门店系统,先把并发扣减和对账脚本这两块做扎实,界面丑点没关系,账不能错。宠物店老板最在意的就是钱和卡次数对不对得上,这两样稳了,其他都好谈。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 12:52:59

AI短剧人机协同实战指南:效率分层与情绪颗粒度

1. 短剧赛道的真实生存图谱&#xff1a;不是“AI vs 真人”&#xff0c;而是“效率分层”正在重构整个生产链“AI会取代真人短剧吗&#xff1f;”——这个问题本身&#xff0c;就暴露了大众对短剧产业最典型的认知偏差。我从2021年第一批竖屏短剧上线起就深度参与过7个平台的短…

作者头像 李华
网站建设 2026/10/7 12:52:58

Java后端实战:37文件猫咖管理系统源码解析与避坑指南

简介&#xff1a;这份源码面向Java后端初学者与需要课程设计、毕业设计参考的开发者&#xff0c;提供一套宠物店猫咖管理系统的完整后端实现&#xff0c;帮助理解业务系统从建模到落地的整体思路。压缩包共38个文件、约52KB&#xff0c;以19个Java源文件承载宠物信息、客户、预…

作者头像 李华
网站建设 2026/10/7 12:52:36

洁莱雅客服态度怎么样,服务专业不专业

时光的刻度&#xff0c;往往藏在那些细微的改变里。从2007年到今天&#xff0c;浙江洁莱雅工贸有限公司在浙江永康这片中国五金名城的土地上&#xff0c; 手机&#xff1a;18067625399 官网地址&#xff1a;https://www.chinajielaiya.com/ 已经走过了近二十年的杯壶制造历程。…

作者头像 李华
网站建设 2026/10/7 12:51:26

DQN生成恶意流量样本:强化学习数据增强实战

简介&#xff1a;这份资源面向计算机相关专业正在做课程设计、期末大作业或需要项目实战练习的学习者&#xff0c;提供一套基于DQN强化学习与机器学习相结合的恶意流量检测模型完整实现方案&#xff0c;帮助读者理解强化学习如何辅助特征选择与检测策略优化&#xff0c;并快速搭…

作者头像 李华
网站建设 2026/10/7 12:51:22

FPGA数字信号处理实战:FIR Compiler IP核配置与仿真避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/7 12:51:21

基于六维力传感器与导纳控制的机械臂零力拖动示教

搞机器人这几年&#xff0c;有一件事始终特别磨人&#xff1a;示教。传统的示教器一个个点位去对、去记、去改&#xff0c;干过的都懂&#xff0c;那叫一个痛苦。后来我转向了拖动示教方案&#xff0c;用六维力传感器配合导纳控制&#xff0c;让机械臂真正“听话”——人直接握…

作者头像 李华