陕西GogoAI自助健身小程序团购套餐模块开发
自助健身团购套餐是场馆引流的核心营销载体,常见包含单次体验卡、多次健身套餐、体测+训练组合套餐,支持限时售卖、限量库存、到期自动失效。和普通电商商品不同,团购购买后会自动生成核销券,关联门禁权限、AI训练权限,同时要和分销、订单、核销链路打通。本文基于陕西GogoAI自助健身小程序落地实践,讲解团购套餐模型、并发库存控制、券生成、退款回滚以及多门店权限隔离的实现方案。
团购套餐模块业务痛点
团购套餐最大的难点在于库存、券、订单三者联动。活动高峰期短时间大量下单,容易出现超卖;套餐支持限制可用门店,不能跨门店核销。用户购买之后发起退款,需要回收券、回滚库存,链路异常会造成库存和券数量不一致。
套餐有独立售卖时段、核销有效期,过期未使用的券自动失效,还需要支持随时退、过期退规则。另外团购商品要接入分销,成交后正常计算佣金,退款时同步冲正佣金记录。前端展示需要缓存套餐列表,但库存实时变更,缓存和数据库的数据一致性不好把控。如果直接复用普通次卡逻辑,套餐活动配置、限购规则很难灵活扩展。
整体业务链路设计
完整流程:后台创建团购套餐,设置价格、原价、库存、限购、售卖时间、可核销门店范围 → 小程序端展示套餐列表 → 用户提交下单,前置校验库存、限购、会员购买次数 → 预扣库存,创建团购订单 → 用户支付成功,自动生成团购核销券,投递事件触发分销分佣、订阅消息通知 → 会员到店出示券码核销,下发门禁和AI训练权限 → 退款或者券过期,回收券状态,返还库存、冲正佣金。
套餐分为单门店专属套餐和连锁通用套餐,通用套餐可以在配置的多个场馆入场核销。库存采用门店独立库存方案,A门店套餐库存和B门店互不干扰。
数据库模型设计
核心包含团购套餐主表、团购订单表、团购券表。套餐表存储活动配置、库存、时间规则;订单记录支付信息;券是最终核销凭证,和之前核销模块复用同一套核销逻辑。
团购套餐DO核心字段如下:
@Data @TableName("fitness_group_package") public class FitnessGroupPackage { @TableId(type = IdType.ASSIGN_ID) private Long id; /** 门店ID,套餐归属门店 */ private String storeId; /** 套餐名称 */ private String packageName; /** 原价 */ private BigDecimal originalPrice; /** 团购售价 */ private BigDecimal salePrice; /** 总库存 */ private Integer stockNum; /** 已售数量 */ private Integer soldNum; /** 单人限购数量 */ private Integer limitBuy; /** 售卖开始时间 */ private LocalDateTime saleStartTime; /** 售卖结束时间 */ private LocalDateTime saleEndTime; /** 券核销有效天数,支付后N天内可用 */ private Integer validDay; /** 套餐状态:上架、下架、已结束 */ private String packageStatus; /** 乐观锁版本号 */ @Version private Integer version; }团购券表复用原有核销券结构,增加packageId关联套餐,区分普通次卡和团购券,核销逻辑统一,减少重复开发。
Java下单与库存扣减核心实现
下单阶段使用Redis做库存预校验,数据库通过条件更新扣减真实库存,防止超卖。同一个订单使用订单号做幂等,支付回调多次触发不会重复生成券。
@Service public class GroupPackageOrderService { @Autowired private FitnessGroupPackageMapper packageMapper; @Autowired private GroupOrderMapper groupOrderMapper; @Autowired private VoucherService voucherService; @Transactional(rollbackFor = Exception.class) public Result<String> createGroupOrder(String orderNo, Long memberId, Long packageId, String storeId, Integer buyCount) { FitnessGroupPackage groupPackage = packageMapper.selectById(packageId); if(groupPackage == null || !"ON_SALE".equals(groupPackage.getPackageStatus())){ return Result.fail(4001,"套餐未上架"); } // 校验库存,条件更新扣减 int updateRow = packageMapper.deductStock(packageId, buyCount, groupPackage.getVersion()); if(updateRow <= 0){ return Result.fail(4002,"库存不足"); } // 创建团购订单 GroupOrder order = new GroupOrder(); order.setOrderNo(orderNo); order.setMemberId(memberId); order.setPackageId(packageId); order.setStoreId(storeId); order.setBuyCount(buyCount); order.setOrderStatus("WAIT_PAY"); groupOrderMapper.insert(order); return Result.success("订单创建成功"); } }支付回调成功之后,异步调用券服务批量生成团购券,投递MQ触发分销分佣和消息推送;超时未支付的订单由定时任务关闭,自动返还套餐库存。
核销范围、限购与过期规则处理
套餐支持配置可核销门店列表,会员拿到团购券之后,只能在配置范围内的门店扫码入场,核销接口会校验门店白名单,不在列表内直接拦截。
限购分为单人总限购、单活动每日限购,下单前查询该会员在本套餐的已购买数量,超出限制直接拒绝。过期逻辑采用定时任务扫描,每天扫描已支付但未核销、超过有效期的团购券,标记为已过期,库存自动回退,同时推送订阅消息告知用户券已失效。
过期券不支持再核销,后台可以配置规则,部分活动支持过期自动退款。
退款逆向流程与数据一致性
用户发起退款是最容易出现数据不一致的环节。退款成功后,执行三步操作:团购券作废、套餐库存回加、分销佣金冲正。整个流程封装为MQ异步任务,避免同步流程过长。
退款操作同样携带幂等编号,防止重复退款多次返还库存。财务对账时,团购订单、券记录、库存变更流水、佣金冲正记录一一关联,方便核对营收数据。
缓存策略和前端展示优化
小程序首页团购列表属于高频访问页面,套餐基础信息存入Redis缓存,减少数据库查询。库存不直接依赖缓存作为最终判断,缓存只做展示,真实库存以数据库条件更新为准,避免缓存延迟导致超卖。套餐上下架、库存变动时主动清理对应缓存key,保证前端及时刷新活动状态。
套餐详情页展示可用门店、核销须知、过期退说明,下单前展示剩余库存,活动结束自动隐藏套餐入口。
落地总结
这套团购模块的设计思路是复用已有核销、分销、消息模块的底层能力,单独抽象套餐活动和库存管控逻辑,不改动原有核心链路。库存使用缓存预检+数据库条件扣减,兼顾并发性能和数据安全,退款、过期统一做库存回滚和佣金冲正。
在陕西多家自助健身场馆上线之后,团购套餐可以快速配置体验引流活动,和分销组合形成裂变活动。后续迭代还可以叠加拼团、限时秒杀等玩法,底层订单、券、库存的核心逻辑可以直接复用,降低迭代成本。