专注小程序 / APP 开发,用代码改变生活

获取方案
全国分站 主站首页 北京分站 石家庄分站 唐山分站 保定分站 廊坊分站 沧州分站 郑州分站

从零开发游泳馆小程序:预约与核销实战

小程序开发 2026-10-12 2 阅读 0 点赞 原创

去年夏天,我帮本地一家游泳馆做了一套小程序,核心解决三件事:分时段预约、扫码核销、会员次卡管理。上线三个月,前台排队时间从平均8分钟降到1分钟以内。这篇文章把踩过的坑和关键实现整理出来,给准备做同类项目的朋友参考。

一、先想清楚业务模型

游泳馆和普通健身房不同,它的瓶颈在泳池承载量。一个50米标准池,同时段超过120人体验就会急剧下降。所以产品设计的第一原则是:预约必须按场次切分,而不是按天。

  • 场次划分:早场 06:30-09:00、上午场 09:30-12:00、下午场 14:00-17:00、晚场 18:00-21:00
  • 每场次独立库存,独立核销
  • 会员次卡按“次”扣减,而非按时间

如果你一上来就做“全天畅游”,后面一定会被高峰期投诉淹没。

二、技术选型

个人开发者做这类项目,我推荐微信小程序原生 + 云开发。理由很直接:

  • 不用买服务器、不用备案,云函数直接跑业务逻辑
  • 云数据库自带权限控制,前台只能读、云函数才能写
  • 支付走微信支付云调用,省去证书配置的麻烦

如果后期要接抖音小程序或支付宝,再考虑用 uni-app 重构。前期别过度设计。

三、数据库设计

核心集合只有四个,保持简单:

// sessions 场次
{
  _id: "s_20240801_am",
  date: "2024-08-01",
  startTime: "09:30",
  endTime: "12:00",
  capacity: 120,
  booked: 87,
  status: "open"
}

// orders 订单
{
  _id: "o_xxx",
  openid: "oXXXX",
  sessionId: "s_20240801_am",
  type: "single", // single | card
  amount: 3800,
  status: "paid", // paid | used | refunded
  code: "8F3K2A"
}

// cards 次卡
{
  openid: "oXXXX",
  total: 30,
  remain: 22,
  expireAt: "2024-12-31"
}

// checkins 核销记录
{
  orderId: "o_xxx",
  staffOpenid: "oYYYY",
  checkinAt: Date.now()
}

四、预约库存的并发问题

这是最容易翻车的地方。用户A和用户B同时下单最后一个名额,如果先查再写,必然超卖。

正确做法是在云函数里用原子自增 + 条件更新:

const db = cloud.database()
const _ = db.command

// 先原子占位,只有 booked < capacity 才成功
const res = await db.collection('sessions')
  .where({
    _id: sessionId,
    booked: _.lt(capacity),
    status: 'open'
  })
  .update({
    data: { booked: _.inc(1) }
  })

if (res.stats.updated === 0) {
  return { code: -1, msg: '该场次已满' }
}

// 占位成功后再创建订单
// 若后续支付失败,在超时任务里回滚 booked: _.inc(-1)

注意一定要配一个15分钟未支付自动回滚的定时云函数,否则库存会被恶意占单吃光。

五、核销码的设计

前台核销要快,扫码到出结果最好在1秒内。我的方案是:

  • 订单支付成功后生成6位短码,同时存一份到 orders.code
  • 核销端(员工版小程序页)调用 wx.scanCode 拿到码后,直接调云函数校验
  • 校验逻辑:订单存在 → 状态为 paid → 场次为当天 → 未核销过
exports.main = async (event) => {
  const { code, staffOpenid } = event
  const order = await db.collection('orders')
    .where({ code, status: 'paid' }).get()

  if (!order.data.length) return { ok: false, msg: '无效码' }

  const o = order.data[0]
  // 当天有效校验,防止提前核销
  const today = new Date().toISOString().slice(0, 10)
  const session = await db.collection('sessions').doc(o.sessionId).get()
  if (session.data.date !== today) return { ok: false, msg: '非当日场次' }

  await db.collection('orders').doc(o._id)
    .update({ data: { status: 'used', usedAt: Date.now() } })
  await db.collection('checkins').add({
    data: { orderId: o._id, staffOpenid, checkinAt: Date.now() }
  })
  return { ok: true, name: o.userName }
}

六、几个容易忽略的细节

  • 退票规则要写死在代码里:开场前2小时可退,之后只能改签。别指望前台手动处理,会乱。
  • 未成年人陪同:1.2米以下儿童通常免票但需占名额,库存计算要把陪同人数算进去。
  • 次卡扣减用事务:云开发支持 db.startTransaction(),扣次和写核销记录必须一起成功。
  • 数据看板给老板看:今日各场次预约率、核销率、次卡剩余总次数。这三个指标能直接指导排班。

七、上线后的效果

这套小程序开发周期约三周,成本主要是云开发资源费(月均不到50元)。上线后最明显的改变是:

前台从“接电话+登记本”变成“扫一下就行”,高峰期投诉量下降约70%。

如果你也想做游泳馆小程序,建议先从单店、单泳池、四个场次跑通闭环,别一上来就做连锁多门店。业务跑顺了,再抽象多租户结构也不迟。

私教预约小程序开发实战指南预售自提小程序开发实战指南

评论 (0)

还没有评论,快来抢沙发吧~

好想法,值得被认真交付

从小程序、APP 到全栈网站,一站式把想法变成可落地的产品

微信咨询

微信扫码,直接沟通需求