ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Java的宠物店猫咖管理系统后端设计源码:Spring Boot与MyBatis实战

基于Java的宠物店猫咖管理系统后端设计源码:Spring Boot与MyBatis实战 简介这份源码面向Java后端初学者与需要课程设计、毕业设计参考的开发者提供一套宠物店猫咖管理系统的后端实现方案可帮助理解业务系统从建模到落地的完整思路。压缩包共38个文件、约52KB以19个Java源文件承载核心业务逻辑8个XML配置文件负责参数与框架配置另有5个class、2个properties属性文件、1个JSP页面及iml、gitignore等工程辅助文件结构紧凑、便于快速导入IDE运行调试。系统围绕宠物信息管理、客户档案、预约服务、库存监控与销售统计等模块展开覆盖录入、修改、查询、删除等常规操作并涉及MVC分层与数据库连接等企业级开发要点。目前已有433人学习下载适合作为中小型管理系统的骨架参考读者可据此梳理实体设计、接口划分与配置组织方式并在此基础上扩展支付、提醒等第三方能力。1. 从一张排班表说起宠物店猫咖管理系统后端到底在管什么一家 200 平米的猫咖周末同时在线 40 只猫、30 位客人、6 名店员还要处理寄养、洗护、会员卡、猫粮零售。老板用 Excel 排班用微信群接预约月底对账靠翻聊天记录——这不是段子是我见过三家店的真实状态。基于 Java 语言开发的宠物店猫咖管理系统后端设计源码要解决的正是这种「业务量不大但维度极碎」的场景把猫、人、位、钱四条线收进一个后端服务里让前台点单、店长排班、老板看报表都走同一套数据。它适合谁一是做 Java 课程设计、需要真实业务建模练手的学生二是想给自家店做数字化、又不想被 SaaS 年费绑住的小店主三是接手外包、需要一套能跑通的后端骨架快速改的开发者。后端选 Java不是因为时髦而是这类系统天然要处理事务预约冲突、库存扣减、权限店员/店长/会员、定时任务寄养到期提醒Spring Boot 生态把这些都做成了开箱即用的模块。这一章先把业务边界划清楚后面才谈得上建表和写接口。2. 后端骨架怎么搭从 Spring Boot 分层到猫咖业务建模2.1 为什么是 Spring Boot MyBatis 而不是别的组合猫咖系统的核心矛盾是「实体关系多、单表数据量小」。一只猫关联品种、健康记录、寄养订单、洗护记录一个会员关联余额、消费流水、预约历史。这种场景下JPA 的自动建表和懒加载容易在关联查询时产生 N1而 MyBatis 让你把 SQL 攥在自己手里出问题能直接看执行计划。我一般会选 Spring Boot 3.x MyBatis-Plus前者管依赖注入和 Web 层后者省掉 80% 的单表 CRUD 样板代码。分层上坚持 Controller → Service → Mapper 三层别为了省事把业务逻辑写进 Controller。猫咖里「预约一只猫」这个动作要同时校验猫的档期、店员排班、会员余额这些判断必须落在 Service 层Controller 只做参数接收和结果包装。下面是最小可运行的依赖配置!-- pom.xml 关键依赖版本按 Spring Boot 3.2.x 对齐 -- dependencies !-- Web 层REST 接口与参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus单表 CRUD 免写 XML复杂查询仍可手写 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 参数校验NotNull 这类注解靠它生效 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies逻辑说明spring-boot-starter-web提供内嵌 Tomcat 和 Jackson 序列化MyBatis-Plus 的BaseMapper让每个 Mapper 接口自动拥有 insert/selectById 等方法validation 保证Valid注解在 Controller 参数上真正拦截非法请求。参数上MyBatis-Plus 版本要和 Spring Boot 大版本匹配3.5.5 对应 Boot 3.x用 3.4.x 会在启动时报NoClassDefFoundError。2.2 猫咖核心表怎么设计五张表撑起全部业务别一上来就画二十张表。猫咖的最小闭环是「猫—位—人—单—钱」对应五张核心表就够跑通预约和寄养。下面是我常用的建表骨架字段做了精简但保留了关键约束-- 猫咪档案表一只猫一行status 控制是否可预约 CREATE TABLE cat ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL COMMENT 猫名, breed VARCHAR(32) COMMENT 品种, health TINYINT DEFAULT 1 COMMENT 1健康 0观察中, status TINYINT DEFAULT 1 COMMENT 1在店 2寄养中 3已领养, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 座位/包间表猫咖按位收费位是稀缺资源 CREATE TABLE seat ( id BIGINT PRIMARY KEY AUTO_INCREMENT, no VARCHAR(16) NOT NULL UNIQUE COMMENT 位号, capacity TINYINT DEFAULT 2 COMMENT 可坐人数, type TINYINT DEFAULT 1 COMMENT 1散座 2包间 ); -- 预约订单表核心表唯一索引防重复预约 CREATE TABLE booking ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, seat_id BIGINT NOT NULL, cat_id BIGINT COMMENT 指定陪玩猫可空, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status TINYINT DEFAULT 1 COMMENT 1待确认 2已确认 3已完成 4已取消, UNIQUE KEY uk_seat_time (seat_id, start_time) );逻辑说明booking表上的uk_seat_time唯一索引是防超卖的第一道闸同一座位同一开始时间只能有一条记录。但光靠它不够——如果两个请求的开始时间差 1 分钟索引拦不住所以 Service 层还要做区间重叠校验。cat.status和health分开是因为「寄养中」的猫仍可能健康两个维度不能合并。参数上start_time和end_time用 DATETIME 而非 TIMESTAMP避免时区转换带来的玄学问题。2.3 预约接口怎么写一个带事务和冲突校验的完整例子预约是猫咖后端最容易翻车的地方。下面这个 Service 方法把「查冲突 → 扣余额 → 写订单」放进一个事务任何一步失败都回滚Service public class BookingService { Autowired private BookingMapper bookingMapper; Autowired private MemberMapper memberMapper; Transactional(rollbackFor Exception.class) public Long createBooking(BookingDTO dto) { // 1. 区间重叠校验同座位、状态未取消、时间有交集 int conflict bookingMapper.countConflict( dto.getSeatId(), dto.getStartTime(), dto.getEndTime()); if (conflict 0) { throw new BizException(该时段座位已被预约); } // 2. 扣减会员余额update 带余额充足条件防并发超扣 int rows memberMapper.deductBalance(dto.getMemberId(), dto.getFee()); if (rows 0) { throw new BizException(余额不足); } // 3. 落订单 Booking b new Booking(); b.setMemberId(dto.getMemberId()); b.setSeatId(dto.getSeatId()); b.setStartTime(dto.getStartTime()); b.setEndTime(dto.getEndTime()); b.setStatus(1); bookingMapper.insert(b); return b.getId(); } }逻辑说明countConflict的 SQL 用start_time #{end} AND end_time #{start}判断区间重叠这是标准写法别用BETWEEN它处理不了跨天。deductBalance的 SQL 是UPDATE member SET balance balance - #{fee} WHERE id #{id} AND balance #{fee}把余额判断塞进 WHERE靠数据库行锁保证并发安全比先查后扣可靠得多。参数上Transactional的rollbackFor Exception.class必须显式写否则受检异常不会触发回滚这是血泪经验。3. 权限与状态流转猫咖后端里最容易埋雷的两块3.1 三种角色怎么隔离店员、店长、会员的接口边界猫咖系统至少三种角色会员只能看自己的预约和余额店员能核销预约、登记寄养店长能改价、看报表、管猫档案。常见做法是用 Spring Security JWT但课程设计级别不必上全套一个拦截器加注解就够。我一般会自定义RequireRole注解在拦截器里解析 token 拿到角色比对接口要求的角色。// 自定义注解标在 Controller 方法上 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String[] value(); // 允许的角色如 {MANAGER} } // 拦截器核心逻辑 public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) { if (!(handler instanceof HandlerMethod hm)) return true; RequireRole anno hm.getMethodAnnotation(RequireRole.class); if (anno null) return true; // 没标注解默认放行 String role JwtUtil.getRole(req.getHeader(Authorization)); if (role null || !Arrays.asList(anno.value()).contains(role)) { resp.setStatus(403); return false; } return true; }逻辑说明把权限判断放在拦截器而非每个方法里是为了避免漏标。JwtUtil.getRole从 token 的 payload 里取角色字段token 本身用 HMAC 签名防篡改。参数上角色用字符串数组而非枚举方便后续加「实习店员」这类新角色时不用改注解定义。注意别把角色写死在 token 里就完事——店长降级为店员时旧 token 仍带 MANAGER所以关键操作如改价要在 Service 层再查一次数据库里的实时角色。3.2 订单状态机为什么不能随便 UPDATE status预约订单有「待确认→已确认→已完成」和「待确认→已取消」两条路径。新手常写一个updateStatus(id, status)接口前端传什么就改什么结果出现「已完成的订单被改成待确认」这种脏数据。正确做法是把合法流转写成状态机非法流转直接拒绝。当前状态允许流转到触发动作1 待确认2 已确认店员确认1 待确认4 已取消会员/店员取消2 已确认3 已完成到店核销2 已确认4 已取消提前取消可能扣违约金3 已完成无终态4 已取消无终态实现上用一个MapInteger, SetInteger定义合法流转更新前先校验private static final MapInteger, SetInteger FLOW Map.of( 1, Set.of(2, 4), 2, Set.of(3, 4), 3, Set.of(), 4, Set.of() ); public void changeStatus(Long id, int target) { Booking b bookingMapper.selectById(id); if (!FLOW.get(b.getStatus()).contains(target)) { throw new BizException(非法状态流转); } // 带原状态条件的更新防并发下两个请求同时通过校验 int rows bookingMapper.updateStatus(id, b.getStatus(), target); if (rows 0) throw new BizException(状态已变更请刷新); }逻辑说明updateStatus的 SQL 带WHERE id #{id} AND status #{oldStatus}这是乐观锁思路两个请求同时改同一订单时只有一个能成功。参数上状态码用整数而非字符串省存储也快但要在代码里用常量类维护别到处写魔法数字。4. 避坑与排查猫咖后端上线前必须过的五道坎4.1 预约时间跨天导致冲突校验失效现象会员预约 23:00 到次日 01:00系统显示预约成功但另一个会员预约次日 00:30 同一座位也成功了。原因冲突 SQL 用了DATE(start_time) DATE(#{start})这种按天比较的写法跨天时两条记录落在不同日期校验漏掉。解决统一用start_time #{end} AND end_time #{start}的区间判断并且前端传参用完整的yyyy-MM-dd HH:mm:ss别只传日期。4.2 余额扣减在并发下变成负数现象会员余额 100 元两个请求同时各扣 80 元最后余额 -60。原因先select查余额再update扣减两个请求都查到 100都认为够扣。解决把判断塞进 UPDATE 的 WHERE 条件AND balance #{fee}靠数据库行锁串行化rows 0就抛余额不足。这是最经典也最容易忽略的一条。4.3 寄养到期提醒定时任务重复执行现象一只猫的寄养到期提醒发了三遍。原因定时任务用Scheduled单机跑没问题但部署两个实例后每个实例都触发一次。解决要么用数据库行锁SELECT ... FOR UPDATE抢任务要么引入 Redis 分布式锁key 用「任务名日期」。课程设计单机可忽略但要知道这个边界在哪。4.4 MyBatis 驼峰映射没开导致字段全 null现象查询返回的对象里createTime全是 null数据库字段明明是create_time。原因没开驼峰映射。解决在application.yml里加mybatis-plus.configuration.map-underscore-to-camel-case: true。这个配置默认在 MyBatis-Plus 里是开的但如果你手动配了ConfigurationBean可能被覆盖掉。4.5 事务里调用外部接口导致连接池耗尽现象高峰期接口大面积超时日志显示获取数据库连接失败。原因在Transactional方法里调了短信发送、支付回调这类耗时外部接口事务一直不提交连接被占住。解决把外部调用挪到事务提交之后用TransactionSynchronizationManager.registerSynchronization的afterCommit回调或者干脆拆成两个方法。5. 让这套后端真正能用的三个进阶技巧第一个技巧是给猫档案加「可预约时段」字段。猫不是全天候能陪玩的有的猫下午要睡觉有的猫对小孩敏感。与其在预约时硬性拒绝不如在cat表加一个available_slotsJSON 字段存可预约时段Service 层解析后和请求时段求交集。这样店长改猫的作息不用改代码改数据就行。第二个技巧是用数据库视图做报表。老板要看「本月每只猫的陪玩次数」「每个店员的核销单量」这些统计别在 Java 里循环查直接建视图CREATE VIEW v_cat_booking_stat AS SELECT c.id, c.name, COUNT(b.id) AS booking_cnt, SUM(b.fee) AS total_fee FROM cat c LEFT JOIN booking b ON b.cat_id c.id AND b.status 3 GROUP BY c.id, c.name;逻辑说明视图把聚合逻辑下沉到数据库Java 侧一个selectList就拿到结果。参数上status 3只统计已完成的订单避免把取消单算进营收。注意视图在数据量大时会慢猫咖这种量级完全够用但要知道它的上限。第三个技巧是接口幂等。会员点「确认预约」时网络卡顿用户连点三次可能生成三条订单。常见做法是前端传一个requestId后端用 Redis 存requestId → 结果重复请求直接返回缓存结果。没有 Redis 就用数据库唯一索引兜底把requestId建成唯一键插入冲突就返回已有订单。技巧解决什么问题代价可预约时段字段猫的作息差异需要前端配合解析 JSON数据库视图报表统计查询慢数据量极大时需改物化视图接口幂等重复提交多一次 Redis 或唯一索引开销我自己踩得最深的一次是给一家猫咖做寄养模块时把「寄养天数」按自然日算结果客户周五晚上送猫、周日早上接走系统算了 3 天客户当场翻脸。后来改成按 24 小时滚动计算并在订单上明确写清计费规则。做这类系统技术只是骨架真正决定能不能用的是对业务细节的较真。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进