1. 项目定位与技术选型:为什么这个系统值得做
宠物服务预约这个方向,说实话在近几年已经不算什么新鲜概念了。但真正有意思的点在于:市面上大多数同类系统要么做得太轻(只是一个预约表单),要么做得太重(复杂到门店根本用不起来)。我接手这个项目时,第一件事不是急着写代码,而是把整个业务链路重新梳理了一遍——宠物主人需要什么、门店需要什么、这个系统的边界应该划在哪里。
从标题就能看出来,这套系统的核心是两件事:服务预约和宠物用品。前者是典型的预约类业务,涉及服务项目、技师、时段、订单状态流转;后者是零售类业务,涉及商品库存、购物车、订单结算。两条业务线看似独立,实际共享同一个用户体系、订单体系和支付入口,这决定了后端必须有一个统一的订单服务来承接两条业务线的差异化诉求。
技术选型上,标题已经写得很明确:Java + SpringBoot + 微信小程序。为什么是这套组合?我个人的判断是三点:第一,SpringBoot 的生态成熟度摆在那里,从 JPA 到 MyBatis-Plus 再到 Spring Security,周边组件基本覆盖了中小型项目的全部诉求,不需要像用 Node.js 那样到处拼装第三方库;第二,Java 的类型系统和事务管理在订单、支付这类对数据一致性要求高的场景下更让人放心;第三,微信小程序作为 C 端入口几乎是国内宠物门店的标配,用户不需要下载 App,扫一扫就能用,获客成本低。
提示:如果你的项目时间紧、目标是快速演示完整业务闭环,SpringBoot 后端 + 小程序前端的组合是最稳妥的。如果需要做复杂的数据分析和推荐系统,再考虑引入微服务或大数据组件,现阶段不要过度设计。
这套系统的目标用户也很清晰:计划开发毕业设计或课程项目的在校生、想接私活的小团队、以及宠物门店的经营者。如果你是后者,看完这篇文章你至少能搞清楚一套预约系统到底应该包含哪些模块、数据怎么设计、部署需要什么成本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计:从订单模型到库存扣减的完整思路
老规矩,写后端先设计数据库。我在这个项目里一共设计了 10 张核心表,分三个模块:用户与权限、服务预约、商品交易。下面把关键表逐一说明,并解释每个设计决策背后的理由。
2.1 用户相关表设计
用户体系这块,小程序端用户和门店管理员在业务上是两类完全不同的角色。小程序端用户通过微信登录静默获取 openid,不需要注册账号密码;门店管理员则通过账号密码登录后台管理端。所以我设计了两张表:pet_user(C 端用户表)和 staff(员工/管理员表)。
pet_user 表的关键字段包括:openid(微信唯一标识)、nickname、avatar、phone、pet_count(宠物数量)。这里有一个很多初学者容易忽略的点:openid 必须加唯一索引。一个微信用户对应一个 openid,但同一用户可能在不同小程序下有不同 openid,同一小程序下也可能用不同手机号登录——只有 openid 才能在小程序维度内唯一标识一个用户。
pet_user 的附加信息我拆了一张 pet 表(宠物档案表),专门存宠物信息:pet_name、pet_type(猫/狗/其他)、pet_breed、pet_birthday、weight、gender。为什么单独建表而不是直接塞在用户表里?因为预约服务时要按宠物类型匹配项目,比如"猫咪洗澡"和"狗狗洗澡"价格不同,如果用户档案里只存一个宠物,用户养了第二只猫时系统就废了。拆开建表,后续可以针对每只宠物做预约历史和健康记录,业务扩展性完全不一样。
2.2 服务预约模块表设计
服务预约是核心模块,涉及的表包括:service_item(服务项目表)、service_appointment(预约订单表)、service_schedule(排班时段表)。
service_item 表的关键字段:service_name、service_type(洗澡/美容/寄养/医疗等)、service_price、service_duration(服务时长,单位分钟)、pet_type(适配宠物类型)、service_img、service_status(上架/下架)。设计时建议对 pet_type 字段加一层约束,因为不同宠物类型的服务项目往往是独立的,不区分的话后续查询和库存计算都会出问题。
service_appointment 是整张业务的核心表,字段设计如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| appointment_id | bigint | 主键 |
| user_id | bigint | 用户ID |
| pet_id | bigint | 宠物档案ID |
| service_id | bigint | 服务项目ID |
| staff_id | bigint | 服务技师ID(门店可分配) |
| appointment_date | date | 预约日期 |
| appointment_time | varchar | 预约时段(如 10:00-11:00) |
| order_status | int | 0待支付 / 1待服务 / 2服务中 / 3已完成 / 4已取消 |
| remark | varchar | 备注 |
注意 appointment_time 我用了 varchar 而不是 datetime。为什么?因为这里存的是"时段"而不是"时刻",用户选择的时间段本来就是一个区间,用字符串存储更直观,也方便前端直接回显。判断时段是否冲突时再对字符串做区间比较即可。当然你也可以用开始时间和结束时间两个 datetime 字段,但那样前后端联调时反而多一步格式化工作,对中小型项目没必要。
service_schedule 表用于管理可预约时段,核心字段:schedule_date(日期)、time_slot(时段)、staff_id(可服务技师)、capacity(最大可预约数)、booked_count(已预约数)。预约时先判断 booked_count < capacity,满足才能生成订单,否则提示"该时段已约满"。
2.3 商品交易模块表设计
商品模块是独立的交易闭环:goods(商品表)、shopping_cart(购物车表)、goods_order(商品订单表)、goods_order_detail(订单明细表)。
goods 表关键字段:goods_name、goods_desc、goods_type(宠物食品/玩具/用品/药品)、goods_price、goods_stock(库存)、goods_img、sales_count(销量)、goods_status。这里重点说 goods_stock:库存字段是 int 类型,永远不要在数据库里做减法。正确做法是:查询时用 select ... where goods_stock >= ?,扣减时用 update goods set goods_stock = goods_stock - ? where goods_id = ? and goods_stock >= ?。这样可以避免并发超卖问题。
goods_order 和 goods_order_detail 是一对多关系。订单主表存总价、状态、用户ID、收货信息;明细表存具体商品ID、商品名称、单价、数量。明细表为什么要冗余商品名称和单价?因为商品信息是会变的——今天卖 35 元的狗粮,明天降价到 30 元,历史订单里的明细必须保持不变。这是电商订单设计的基本常识,但很多新手会忽略。
2.4 预约冲突与并发控制的实现要点
预约系统最容易出的问题就是同一时段被多人约满。我在项目里用了一种非常朴素但可靠的方案:在 service_appointment 表上建立唯一索引 (appointment_date, appointment_time, staff_id)。这样即使两个用户同时对同一技师发起了同一时段的预约,数据库层面也会拦截重复插入,从根上掐断冲突。
有同学可能会问:如果门店不止一个技师,一个时段可以同时服务多个用户怎么办?那就把 unique 索引去掉,改用 service_schedule.booked_count 做乐观锁更新。操作流程是:先查 booked_count < capacity,然后执行 update service_schedule set booked_count = booked_count + 1 where schedule_id = ? and booked_count < capacity,如果影响行数为 1 说明预约成功,否则提示已满。这个方案在低并发场景下完全够用,没必要上 Redis 分布式锁。
3. 核心接口设计:从微信登录到订单流转
数据库设计完后,下一步是接口设计。我按业务模块整理了核心接口清单,并给出关键接口的代码示例。
3.1 微信登录接口
微信小程序的登录流程是典型的 code2Ses
