Skip to content

小程序自助约期(免登录写入口)· 设计草案 ​

状态:已实施(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 36102cfcurl 矩阵 12 例全过:正常约(含无卡项 itemName=null)/ 跨日同手机号归同顾客 / 当日第二条 400 / 过去时刻 400 / 非整点 400 / 越界时段 400 / 格式错 400 / 下架卡项 400 / 无 token 仍可约(免登录区);落库核验 duration=60、staffId=null、status=0、remark「小程序自助」
A2 ✅app:pages/booking 页 + 首页横幅接线 + bookAppointment 服务函数,commit f7ccbbfH5(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

Last updated: