简介:这份源码面向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 的自动建表和懒加载容易在关联查询时产生 N+1,而 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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis-Plus:单表 CRUD 免写 XML,复杂查询仍可手写 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- 参数校验,@NotNull 这类注解靠它生效 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-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 已取消 | 无 | 终态 |
实现上用一个Map<Integer, Set<Integer>>定义合法流转,更新前先校验:
private static final Map<Integer, Set<Integer>> 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 小时滚动计算,并在订单上明确写清计费规则。做这类系统,技术只是骨架,真正决定能不能用的是对业务细节的较真。希望帮到你。
本文还有配套的精品资源,点击获取