Files
wordpress-theme-aksrt/.zcode/plans/plan-sess_72ae2697-306c-467a-8269-53caeb827381.md
T
2026-08-27 03:27:26 +08:00

6.5 KiB
Raw Blame History

现状结论(影响方案的既有事实)

  • 下载系统集中在 src/Content/Download.php:路由前缀硬编码为常量 ENDPOINT='aksrt-dl'(同时用于 rewrite 正则 :68 和 URL 拼接 :226)、TTL 硬编码 TOKEN_TTL=600(:12)、max_uses 固定写死 1(:422)、expires_in 返回固定值(:242)。
  • 扣次在"点击兑换"时发生(Download.php:286-292),无 HEAD 特判、无断开检测,需按需求重构。
  • 过期/耗尽已是 fail_410()(HTTP 410 + wp_die 提示"回到资源页重新获取"),不自动生成新链接 —— 该行为现状已满足,保持不变。
  • aksest_dl_token 表(src/Core/DBMigratorPart2.php:133-150)的 token 列已是 PRIMARY KEY(即唯一索引),重复密钥不可能入库;需补的是 issue_token 插入失败时的换号重试。
  • 设置体系是双端 schema:PHP 权威在 src/Admin/Schema/*.php,Vue 镜像在 assets/admin/src/schema/sections/**,菜单在 assets/admin/src/constants/sections/permissions.js,REST 用泛型路由自动生效。"功能&权限"= SPA 内 permissions 域。
  • Auth\Router::maybe_flush_rewrite_rules()(:78-84) 已有"版本标记式 flush_rewrite_rules()"先例,可直接套用。

一、新增设置分区(permissions 域 → "下载配置")

存储为独立 option aksrt_download_config(沿用 captcha/moderation 的 standalone 模式),三个字段:

key 类型 默认 校验
url_alias input download 必填;trim 后非空;仅允许 [A-Za-z0-9_-](禁中文/空格// \ ? # & 等);≤48 字符;拒绝保留字黑名单(wp-json、wp-admin、wp-content、wp-includes、admin、login、logout、register、lostpassword、feed、search、author、page、comments、sitemap、sign 等,防路由冲突/404)
default_max_uses number 1 min 1 / max 100
ttl_minutes number 10 min 1 / max 1440

修改文件:

  1. src/Admin/Schema/SchemaPermissions.php:新增 download section(version 1.0.0,option_key aksrt_download_config,单组 basic 三字段,含中文标签/help 说明"完整下载才计次、到期或耗尽后 410 需重新获取")。
  2. src/Admin/SchemaProvider.php validate():仿照 payment/withdrawal 特例先例(:269-292),对 permissions/download 增加中文错误消息校验块(空值/非法字符/过长/保留字)——422 错误经前端 message.error('保存失败: …') 弹窗呈现,满足"后台弹窗提示"。
  3. src/Settings/Settings.php:STANDALONE_OPTION_KEYS 追加 aksrt_download_config(导入导出完整性 + 缓存失效覆盖)。
  4. assets/admin/src/schema/sections/permissions/index.js:镜像新增 download 分区(defaults 与 PHP 完全一致)。
  5. assets/admin/src/constants/sections/permissions.js:追加子页 schemaPage({ key:'permissions-download', route:'/permissions/download', label:'下载配置', section:'download' }),置于附件配置之后。泛型 REST 路由与 SchemaPage 无需改动。

二、Download.php 改造(src/Content/Download.php)

  1. 动态前缀:删掉 const ENDPOINT;新增私有 endpoint() 读配置(无效/被污染时回落 download);register_rewrite() 用 preg_quote($endpoint) 拼正则(:66-72);URL 拼接(:226)同源。
  2. 别名变更刷新路由:仿 Auth\Router 版本标记法——init 尾部挂 maybe_flush_rewrite()(priority 99):比较 AKSRT_VERSION + 当前别名 与 option aksrt_dl_rewrite_version,不一致则 flush_rewrite_rules(false) 并回写。导入/重置/直接改库都覆盖,无双路兼容逻辑。
  3. 动态 TTL / 次数:删掉 TOKEN_TTL 常量引用;issue_token() 写入 max_uses = 配置.default_max_uses、expires_at = now + 配置.ttl_minutes*60;rest_request 的 expires_in 改为动态秒数。配置读取做 clamp 兜底(别名非法回落 download、次数 1..100、分钟 1..1440)。
  4. 完整下载才扣次:
    • 将扣次(UPDATE used_count)与文章计数 _aksrt_res_download_count(:286-300) 移到交付成功之后的新方法 consume_token();UPDATE 加原子条件 WHERE token=%s AND used_count < max_uses,并发下计数封顶不超发。
    • stream_local_file() 改为返回是否完整送达:输出循环中 connection_aborted() 为真立即中断并返回 false;收尾再核验一次。仅当完整送达且非 HEAD 才 consume_token()——网络波动中断不消耗次数,用户可重新点同一链接续下。
    • HEAD 探测:REQUEST_METHOD === 'HEAD' 时照常走校验/输出 headers 但不扣次。
    • 外链 302 源(远端主机无法感知"是否下完"):把 302 交接视为一次交付,重定向发出后扣次(HEAD 时同样跳过)。此为唯一可实现语义,计划中明示。
    • 过期/耗尽仍走现有 fail_410()(改动前后行为一致)。
  5. 密钥冲突重试:issue_token() 插入失败时用全新随机串重试至多 3 次(PRIMARY KEY 已保证唯一,此为极低概率兜底;表缺失仍在入口提前返回 WP_Error 不进循环)。

三、清理与文档

  • 删除不再使用的 Download::ENDPOINT / Download::TOKEN_TTL 常量及全部残留引用(实现时全库 grep 复核;已知其余命中仅为 CSS 类名巧合,不动)。
  • 更新 CLAUDE.md:132 文档行:rewrite 前缀改为"可配置别名(默认 download)",并补充"完整送达才计次"契约。
  • 不写任何迁移/双路:现有表结构不动,存量过期 token 由每日 cron 自然清理。
  • 关于唯一索引:PRIMARY KEY(token) 本身即唯一索引且已存在(不加冗余 UNIQUE KEY,也不做表结构变更——与"不写迁移"一致),以插入重试落实需求的防冲突意图。

四、验证

  1. 新增 Node 契约测试 tests/widget-system/download-config-contract.test.cjs:断言双端 schema 的字段/默认值/option_key 一致、菜单子项注册、Download.php 关键契约(动态 endpoint、流式完成后才条件 UPDATE 计次、HEAD 跳过、410 不自动续签、无 ENDPOINT/TOKEN_TTL 常量残留)。
  2. node --test tests/widget-system/download-config-contract.test.cjs + 跑现有相关契约测试确认无回归。
  3. php -l 全部改动的 PHP 文件;php tools/audit-admin-contracts.php 通过双端合同审计。
  4. cd assets/admin && npm ci && npm run build,确认 build 产物完整(PHP 端有 chunk 缺失探测)。
  5. 视觉不做真实验收(按约定跳过 Docker 对比环境)。