小程序自助约期(免登录写入口)· 设计草案
状态:已实施(2026-10-10)
§7 拍板点已裁定(见「拍板记录」),§8 落地清单已全部落地:服务端 36102cf、小程序 f7ccbbf。本文保留完整口径作实现依据。 2026-10-16 更新:顾客侧自助取消已实装(拍板 2 前提失效,见拍板记录补记)——POST /api/v1/kiosk/appointment-cancel + 小程序「我的」页取消按钮。
1. 目标与范围
小程序(app/,Taro)顾客自助约期:选日期 → 选时段 → 选项目(可跳过)→ 提交,落一条 status=0 待到店预约;到店后前台在管理端照常「到店接待 → 发起接诊」。
MVP 不做:支付、短信/验证码、顾客侧取消、同时段冲突校验、周期性排班、跨门店。
2. 与 Kiosk 口径对齐
顾客选服务 Kiosk(设计文档)已实装免登录闭环,自助约期沿用同一套口径,不开新模式:
| 口径 | Kiosk(已实装) | 自助约期(本草案) |
|---|---|---|
| 免登录区 | /api/v1/kiosk/**(SecurityConfig 既有 permitAll) | 同一个 matcher,新增路径落在其中,安全配置零改动 |
| 结构 | 浏览只读 + 唯一写入口 POST /kiosk/orders | 浏览复用 + 唯一写入口 POST /kiosk/appointments |
| 建档 | 按手机号幂等建档(缺省名「到店客人」) | 同一口径,复用同一 customers 行 |
| 支付 | 不做,到店结算 | 不做,到店接待后照常 |
| 写范围 | 1 条顾客档案 + N 条 status=0 订单 | 0~1 条顾客档案 + 1 条 status=0 预约 |
| 不暴露 | 任何查询/删除能力 | 同样不暴露(响应只含本次预约自身) |
3. API 面(拍板后新增,仅此一个写入口)
POST /api/v1/kiosk/appointments(免登录)
| 项 | 说明 |
|---|---|
| Request | { phone, name?, itemId?, startTime };phone 11 位格式校验;startTime yyyy-MM-dd HH:mm 整点且必须晚于当前时刻;itemId 可空(到店再定,须存在且上架);name 可空(缺省「到店客人」) |
| 服务端补齐 | duration=60、staffId=null(到店分配)、status=0 待到店、remark 记「小程序自助」(见 §4 来源口径) |
| Response | { customerId, customerName, appointmentId, startTime, itemName?, status: 0 } |
| 400 | 手机号格式 / 时间格式或非整点 / 过去时刻 / 越界时段(见 §4)/ 卡项不存在或已下架 / 当日已有未完成预约(频控) |
浏览零新增:项目列表直接用既有 GET /api/v1/kiosk/items。
4. 安全边界(拍板重点)
免登录写入口的固有风险与对策(如实,不含未实现的控制):
- 灌单/滥用:同手机号当日
status IN (0,1)的预约限 1 条,超出报 400「当日已有预约,请到店或致电改约」。实现走既有 customers(按手机号查)+ appointments(按顾客按日数)查询,不加表不加索引(演示规模够用)。 - 时段硬约束:只接受当日之后的整点
09:00–17:00(对齐排班网格 9:00–18:00 的最后一格起始),从源头限制垃圾数据形态。 - 手机号即身份:演示环境信任手机号,无短信验证——与 kiosk 下单同一口径(kiosk 文档已注明「如暴露公网需另加验证码或屏 token」,本入口同此边界);验证码/频控网关为规划项。
- 不暴露横向数据:响应只含本次预约与顾客自身名字;不提供任何按手机号反查他人的能力;
staffId不下发。 - 来源口径:
remark记「小程序自助」做软标记,不加表结构(不加 source 列)。管理端列表/排班网格里「未指派」即含此类,演示可接受;若后续要硬区分再加source字段(规划)。
5. 管理端影响:零改动
预约照常出现在 /appointment/query 与 /appointment/schedule;接待(0→1)、取消(0→9)、删除(仅 9)流程不变。排班网格「一格多条取第一条 +N」的既有口径天然兜住冲突场景(本来就不做同时段冲突校验,演示边界)。
6. 小程序端页面(拍板后实现)
pages/booking:日期条(今天起 7 天)→ 时段网格(9:00–17:00 整点,全部展示为可约——服务端不做占用查询,冲突靠到店接待时人工调和)→ 项目选择(复用GET /api/v1/kiosk/items,可跳过;项目带时长则在名称旁显示「·N分钟」——kiosk 项载荷 2026-10-19 起含durationMinutes,无值不显示)→ 手机号 + 姓名提交。- 提交成功展示预约号与时段;400 文案原样透出(如「当日已有预约…」)。
- 首页「预约横幅」(已实装的占位)跳转本页,占位标记随之撤掉。
- H5 直接联调;微信小程序需把 API 域名加业务域名白名单(部署口径,见换马甲构建指南同源约束)。
7. 拍板记录(2026-10-10 裁定)
| # | 问题 | 裁定 | 依据 |
|---|---|---|---|
| 1 | 频控「1 条/日/手机号」是否合适 | 1 条/日/手机号(status IN (0,1) 计数,超出 400) | 免登录无验证码,防滥用优先;放宽只是改一个计数阈值的事,真实复约场景出现再调 |
| 2 | 顾客能否自助取消 | 不做(仅管理端 0→9 取消)→ 2026-10-16 代决翻案(用户可推翻) | 原依据「取消不核验身份,无法阻止恶意取消他人约」已失效:2026-10-14/15 手机号+一次性验证码双因子与 openid 绑定落地,身份可核验。已实装 POST /api/v1/kiosk/appointment-cancel(仅 sms_verified 模式,一次性验证码,只取消本人 status=0 且未过期的预约,非本人/已受理/已过期均 400,同 card_lookup_logs 审计;取消后时段自动释放、当日频控解除),小程序「我的」页查预约结果行内「取消」。管理端取消保留并行 |
| 3 | 时段是否固定 60 分钟 | 固定 60 分钟 | 服务端校验与排班网格展示都简化;管理端建约仍可 30/60/90,两条口径互不影响 |
| 4 | 是否加 source 列 | 不加,remark 记「小程序自助」软标记 | 演示规模软标记够用;加列要动 schema 与既有查询,确有报表需求再迁移 |
8. 落地清单(已完成 2026-10-10)
| # | 事项 | 验收 |
|---|---|---|
| A1 ✅ | server:POST /api/v1/kiosk/appointments(校验 + 频控 + 幂等建档 + 落约),commit 36102cf | curl 矩阵 12 例全过:正常约(含无卡项 itemName=null)/ 跨日同手机号归同顾客 / 当日第二条 400 / 过去时刻 400 / 非整点 400 / 越界时段 400 / 格式错 400 / 下架卡项 400 / 无 token 仍可约(免登录区);落库核验 duration=60、staffId=null、status=0、remark「小程序自助」 |
| A2 ✅ | app:pages/booking 页 + 首页横幅接线 + bookAppointment 服务函数,commit f7ccbbf | H5(Playwright)全流程走通:横幅入口(规划占位已撤)→ 选日期/时段/项目/手机号 → 成功回执(预约号/时段/项目名)→ 同手机号同日再约透出 400「当日已有预约,请到店或致电改约」;build:h5 + build:weapp 绿 |
| A3 ✅ | 文档转正:本草案标已实施 + api.md/data-model.md 补口径 + features.md 预约行更新 | docs build 过,状态行与源码一致 |
9. 后续候选取舍(2026-10-10 裁定)
| 候选 | 裁定 | 依据 |
|---|---|---|
| 管理端给「小程序自助」来源加显式筛选 | 不做 | 软标记已在管理端预约列表 remark 列明文可见;拍板 4 已定不加 source 列,加筛选是半个 source 概念。确有报表需求时随 source 列一起迁移 |
| 小程序登录 / 我的卡项 / 我的预约页 | 不做(卡点=顾客身份核验) | 免登录手机号直查会造成横向数据暴露(同 §4 边界);验证码或密码超出演示 MVP 口径(§1)。保持规划占位 |
| 我的卡项(手机查卡) | 已实装(2026-10-14 裁定取代上一行该项;本行其余两项维持不做) | 不做裸手机号查卡(隐私红线),改 openid / 短信验证码双因子,按 sinomed.phone-lookup.mode 三态开关(默认 off)并落 card_lookup_logs 审计;首页「我的卡项」区块为入口联动 + 本机摘要回显(称呼/持卡数/查询日期,7 天有效,明细与余次不落本机)。口径见 kiosk.md |