6.5 KiB
6.5 KiB
现状结论(影响方案的既有事实)
- 下载系统集中在
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 |
修改文件:
src/Admin/Schema/SchemaPermissions.php:新增downloadsection(version 1.0.0,option_keyaksrt_download_config,单组 basic 三字段,含中文标签/help 说明"完整下载才计次、到期或耗尽后 410 需重新获取")。src/Admin/SchemaProvider.phpvalidate():仿照 payment/withdrawal 特例先例(:269-292),对permissions/download增加中文错误消息校验块(空值/非法字符/过长/保留字)——422 错误经前端message.error('保存失败: …')弹窗呈现,满足"后台弹窗提示"。src/Settings/Settings.php:STANDALONE_OPTION_KEYS追加aksrt_download_config(导入导出完整性 + 缓存失效覆盖)。assets/admin/src/schema/sections/permissions/index.js:镜像新增download分区(defaults 与 PHP 完全一致)。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)
- 动态前缀:删掉
const ENDPOINT;新增私有endpoint()读配置(无效/被污染时回落download);register_rewrite()用preg_quote($endpoint)拼正则(:66-72);URL 拼接(:226)同源。 - 别名变更刷新路由:仿
Auth\Router版本标记法——init尾部挂maybe_flush_rewrite()(priority 99):比较AKSRT_VERSION + 当前别名与 optionaksrt_dl_rewrite_version,不一致则flush_rewrite_rules(false)并回写。导入/重置/直接改库都覆盖,无双路兼容逻辑。 - 动态 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)。 - 完整下载才扣次:
- 将扣次(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()(改动前后行为一致)。
- 将扣次(UPDATE used_count)与文章计数
- 密钥冲突重试:
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,也不做表结构变更——与"不写迁移"一致),以插入重试落实需求的防冲突意图。
四、验证
- 新增 Node 契约测试
tests/widget-system/download-config-contract.test.cjs:断言双端 schema 的字段/默认值/option_key 一致、菜单子项注册、Download.php 关键契约(动态 endpoint、流式完成后才条件 UPDATE 计次、HEAD 跳过、410 不自动续签、无 ENDPOINT/TOKEN_TTL 常量残留)。 node --test tests/widget-system/download-config-contract.test.cjs+ 跑现有相关契约测试确认无回归。php -l全部改动的 PHP 文件;php tools/audit-admin-contracts.php通过双端合同审计。cd assets/admin && npm ci && npm run build,确认 build 产物完整(PHP 端有 chunk 缺失探测)。- 视觉不做真实验收(按约定跳过 Docker 对比环境)。