# 浙工大屏峰校区网球场预约 本文件是公开网页及程序接口说明。请以获取本文件时使用的同一 origin 调用接口;生产站点为 https://tennis.jobo.asia 。所有日期和时间均按 Asia/Shanghai(北京时间)解释。 人用页面:`/` 查询场地并预约;`/bookings` 按预约手机号查询、支付和取消。 机器接口无需本站登录、API Key 或 Cookie。GET 返回 JSON;POST 请求使用 `Content-Type: application/json` 并接收 JSON。浏览器跨域读取和调用公开接口已开放。 ## 1. 查询空场 `GET /api/public/availability?date=YYYY-MM-DD` `date` 可省略,默认北京时间今天。只允许今天或明天。相同日期共用 15 秒服务器缓存,并合并并发查询;`cached` 表示本次是否命中缓存。刷新不能强制绕过缓存。学校不可用时返回 503,不代表没有空场。 响应中的 `venues` 是三处场馆。每个场馆有 `id`、`name`、`periods`、`courts` 和 `available_slots`;`courts[].slots[]` 是完整时段表,`available_slots[]` 只含可约格子。格子包含 `start`、`end`、`available`、`price`、`status`。仅将 `available: true` 作为预约候选;这仍只是查询时的快照,提交时会重新查场次。 场馆 ID:`17` 东区室外网球场;`13` 西区室外网球场;`12` 西区雨棚网球场。预约时 `court` 应原样使用 `available_slots[].court` 或 `courts[].name`,不要猜测场地名称或 ID。`price` 可能是数字、数字字符串或 null。 示例响应(仅示意,不是实时库存): ```json { "date":"YYYY-MM-DD","timezone":"Asia/Shanghai","updated_at":"2026-11-12T12:00:00+08:00", "cached":false,"has_available":true, "venues":[{"id":"13","name":"西区室外网球场","has_available":true, "periods":[{"start":"18:00","end":"19:00"}], "courts":[{"name":"1号场地","slots":[{"start":"18:00","end":"19:00","available":true,"price":20,"status":"可预约"}]}], "available_slots":[{"court":"1号场地","start":"18:00","end":"19:00","available":true,"price":20,"status":"可预约"}] }] } ``` ## 2. 即时预约 `POST /api/public/bookings` ```json {"date":"YYYY-MM-DD","venue":"13","court":"1号场地","start":"18:00","end":"19:00","name":"预约人姓名","phone":"13900000000"} ``` 将示例日期、姓名和手机号替换为真实值。`date` 只能是北京时间今天或明天;`start`/`end` 为 08:00–22:00 范围内的整点,必须是该片场地连续且仍可预约的时段。`venue`、`court`、`start`、`end` 应来自最近一次空场查询。提交会立即调用学校系统,不会创建计划任务。服务端使用私有学校账号和主预约人资料;调用者只提供自己的 `name` 和中国大陆手机号 `phone`,不传学校账号或密码。 成功 HTTP 200 返回本站预约 `id`、`state`、`date`、`venue`、`court`、`start`、`end`、`price`、`payment_status`、`payment_url`、`can_cancel` 等字段。`state=booked` 表示已查到学校订单;`state=pending_payment` 表示学校订单待支付,此时 `payment_url` 如有值,是可交给用户打开的支付宝链接;付款仍须由用户完成。待支付但链接为空时,可稍后用手机号查询以重新获取。`state=failed` 表示学校明确拒绝提交且复查未找到订单,不能付款;刷新场地后才可重新选择空闲时段。`state=uncertain` 表示已发送提交但未确认订单;**不要再次提交相同场次**,应先查询该手机号的预约并核实结果。 ## 3. 按手机号查询本站预约 `POST /api/public/bookings/query` ```json {"phone":"13900000000"} ``` 返回 `{"bookings":[...]}`。每条记录含本站预约 `id`、姓名、场馆、日期、时段、价格、`state`、`payment_status`、`payment_url`、`can_cancel` 和创建/更新时间。只列出本站用这个手机号创建的订单,不列出学校账号中的其他订单。查询会刷新已记录订单的学校支付状态,并尝试找回先前 `uncertain` 的提交结果。手机号放在 JSON 请求体中,不要放进 URL 或日志。 ## 4. 取消预约 / 申请退款 先按手机号查询,取得目标记录的 `id`,确认场馆、日期、时段及 `can_cancel: true`。 `POST /api/public/bookings/{id}/cancel` ```json {"phone":"13900000000"} ``` 未付款或免费订单经学校接受后返回 `state=cancelled`。已付款订单调用学校退款申请接口,返回 `state=refund_requested`;这只表示申请已提交,**不表示退款已到账**。学校不允许取消/退款时返回 409。不要根据提交响应推断资金已退回。 ## 错误与调用边界 错误响应为 `{"detail":"错误说明"}`;400 表示日期或时段不合法,422 表示字段格式不合法,404 表示该手机号下无此预约,409 表示场次冲突或学校不允许取消,429 表示调用过频,503 表示学校账号或上游暂不可用。429/503 可能带 `Retry-After`,应遵循该值。查单结果不能确认时不要自动重复下单。 手机号未经短信验证。知道某人的手机号就可能查询其本站预约,并取得取消所需的 `id`;调用方不能把手机号视作已验证身份,也不应向无关人员展示订单或支付链接。