6.8 KiB
指定日期下载接口(开发者)— 设计文档
日期:2026-07-29 定位:面向开发者的 HTTP 接口,不进前端。提供"指定一个具体日期,下载该日应到 / 实到数据"的能力,用于补下历史数据。 前置:中通跨月导航已实现并验证(见
feat(zto): cross-month calendar navigation)。
一、背景与目标
现状:下载日期由各站 offset 偏移(0=今天,1=昨天…,存 state.db,上限 MAX_DATE_OFFSET=30)决定,周期调度与手动触发都用 offset。无法指定一个具体日期。
目标:新增"指定日期"入口(开发者用),传一个 YYYY-MM-DD 日期,下载该日应到 / 实到数据。不替换 offset 机制,与之并存:传 date 用 date,不传走 offset。
二、范围
| 站点 | 支持指定日期 | 说明 |
|---|---|---|
| 顺心 / 中通 / 韵达 / 安能 | ✅ | 应到、实到均支持 |
| 百世 | ❌ | 固定下载当天,传 date 返回 400 |
三、接口契约
复用 POST /tasks,body 新增可选字段 date(与 force 并列):
{ "site": "中通", "kind": "expected", "date": "2026-06-14" }
date: Optional[str] = None,格式YYYY-MM-DD。- 优先级:传
date则本次用 date;不传则走站点 offset 配置(默认行为完全不变)。 date与force可共存(指定日期 + 强制重下)。
合法性校验(仅在传了 date 时执行,失败返回 400)
- 格式:
datetime.strptime(date, "%Y-%m-%d")解析成功,否则 400。 - 范围:
今天 - 90 天 ≤ date ≤ 今天。date > 今天→ 400(未来日期;日历未来格子invalid物理上点不动,且不应下未来数据)。date < 今天 - 90 天→ 400(回溯上限 90 天)。
- 百世:site=百世 且传 date → 400(固定当天)。
合法性校验落在
POST /tasks(server.pycreate_task),入队前拦截,非法请求不产生任务。
四、透传链路(与现有 force 完全对称)
POST /tasks {site, kind, force, date}
→ task_queue.put((tid, {site, kind, force, date}))
→ dispatch_task(ctx, task_spec) # 读 task_spec["date"]
→ handler(ctx, force, date) # _web_handler / 安能 lambda / _site_undelivered_handler
→ impl(page, force, date) # 各站 download_impl
_web_handler:handler(ctx, force=False, date=None),透传download_func(pg, force, date);顺心双账号透传(pages, foreground, force, date)。_site_undelivered_handler(未到):连下 expected + actual,两个子任务共用同一个 date。- 安能 lambda:
(ctx, force=False, date=None) → anneng_xxx_download(force=force, date=date)。 - 周期调度
_enqueue_fetch投递的 task_spec 只有{site, kind}(不带 date)→ 恒走 offset,无需改动。
五、各站 impl 改造(核心)
统一模式:target = parse(date) if date else (today − offset)。
中通(zto)—— 复用跨月算法
把 date 折算成 effective offset,复用现有 target_time = today_time − offset*86400000 与跨月翻页(_zto_flip_to_target_month),零额外 UI 逻辑:
def zto_expected_download_impl(page, force=False, date=None):
...
offset = state_store.get_offset("中通")
if date:
target_date = datetime.strptime(date, "%Y-%m-%d").date()
offset = (datetime.now().date() - target_date).days
# 后续 today_time / target_time / 跨月翻页 逻辑完全不变
zto_actual_download_impl 同理(用 ("中通","actual") offset)。expected / actual 两个 impl 都加 date=None 形参,zto_expected_download / zto_actual_download 公开入口同步加形参并透传。
韵达 / 顺心 / 安能 —— date 直接当 target
这三站 offset→日期是 target = today − timedelta(days=offset) 后填字符串到日期控件(非日历格子),指定日期只需替换 target 来源:
if date:
target = datetime.strptime(date, "%Y-%m-%d")
else:
target = today - timedelta(days=offset)
后接的"填起始/截止日期字符串"逻辑完全不变。各站 expected / actual 入口与 impl 都加 date=None 形参。
- 韵达:
yunda_expected_download(_impl)/yunda_actual_download(_impl)。 - 顺心:
shunxin_expected_download(_impl)/shunxin_actual_download(_impl)(双账号入口透传 date 到各账号 impl)。 - 安能:
anneng_expected_download/anneng_actual_download。
百世(baishi)—— 签名兼容
baishi_download_undelivered_data(page, date=None) 加 date=None 形参(忽略),仅为对齐 _web_handler 的统一透传签名;百世任务实际不会带 date(server 已拦截)。
六、业务日期快照
_record_business_date(site, kind, date=None):有 date 则业务日期 = date,否则维持现状 today − offset。dispatch_task 把 task_spec["date"] 透传进去,保证状态盘 / 报告显示的"是哪天的数据"准确(不被 offset 算错)。
七、改动文件清单
| 文件 | 改动 |
|---|---|
inbound_verify/cli/server.py |
TaskRequest.date + create_task 合法性校验 + task_spec 透传 date |
inbound_verify/runtime.py |
_web_handler / _site_undelivered_handler / 安能 lambda 透传 date;dispatch_task 读 date 透传给 handler 与 _record_business_date;_record_business_date 加 date |
inbound_verify/sites/zto.py |
expected/actual 入口+impl 加 date;date→effective offset 复用跨月 |
inbound_verify/sites/yunda.py |
expected/actual 入口+impl 加 date;date→target |
inbound_verify/sites/shunxin.py |
同上(双账号透传 date) |
inbound_verify/sites/anneng.py |
expected/actual 加 date;date→target |
inbound_verify/sites/baishi.py |
加 date=None 形参兼容(忽略) |
八、验证计划
- 接口校验(curl/python urllib):
- 合法 date(过去某日)→ 202,任务成功。
- 未来日期 / 超 90 天 / 格式错 → 400。
- 百世 + date → 400。
- 不传 date → 走 offset(行为不变)。
- 各站实测(指定一个过去日期触发任务):
- 中通:跨月日期(已知 OK,复用已验证的跨月导航)。
- 顺心 / 韵达 / 安能:实测其日期控件是否接受任意过去日期字符串;若控件是日历选择器需翻月,则按中通同法扩展(本轮发现则记录、必要时追加改动)。
- 业务日期快照:下载后
GET /status的*_business_date== 指定 date。 - 改完跑 Black +
py_compile。
九、非目标(YAGNI)
- 前端 UI(checkbox / 日期选择器)——开发者接口,不进前端。
- 周期调度指定日期——周期恒走 offset。
- 批量日期 / 日期范围下载——单次单日。