AttachmentDownloads 安装与使用说明

AttachmentDownloads 是面向 Typecho 1.3.0 的非图片附件下载保护与统计插件。插件将正文中的附件锚点运行时改写到下载网关,在通过验证码或短期授权后签发下载会话,并统计有效下载、每日 UV 和初始来源。

image.png

插件版权地址:https://www.144d.com

功能概览

  • 自动识别正文中的本地非图片附件链接,并改写为带随机公开键的下载入口。
  • 提供关闭、每次验证、单附件授权窗口、全站授权窗口和风险触发五种验证码模式。
  • 支持 PHP Stream、Nginx X-Accel-Redirect 和 Apache X-Sendfile 三种文件交付方式。
  • 统计有效下载、每日 UV 和初始来源,提供趋势、排行、详情及 CSV 导出。
  • 不记录原始 IP、完整 User-Agent、完整 Referer 或访客 Cookie 原值。

安装前准备

  • Typecho 1.3.0 或更高版本。
  • PHP 7.4 或更高版本。
  • MySQL/MariaDB、PostgreSQL 或 SQLite。
  • 正文自动改写需要 PHP DOM 扩展。
  • 内置图片验证码需要 PHP GD 扩展。
  • 默认 php_stream 驱动需要 PHP 能读取上传目录。

安装前建议备份站点数据库和 usr/uploads/。插件启用时只创建独立数据表,不修改 Typecho 核心表结构,但源文件保护需要调整 Web 服务器配置。

安装

1. 安装并启用插件

  1. 将插件目录放到 Typecho 的 usr/plugins/ 下。
  2. 确认最终目录为 usr/plugins/AttachmentDownloads/,入口文件为 usr/plugins/AttachmentDownloads/Plugin.php。目录名区分大小写,不能自行改名。
  3. 登录 Typecho 后台,进入“控制台 -> 插件”,找到“AttachmentDownloads”并启用。
  4. 启用成功后,后台会出现“下载统计”菜单。

如果启用失败,请先确认 PHP 版本、数据库建表权限和服务器错误日志。不要在插件目录名不正确时继续配置路由或服务器规则。

2. 完成首次配置

  1. 在插件列表中打开 AttachmentDownloads 设置。
  2. 核对“受保护下载扩展名”。默认值为 zip, rar, 7z, pdf, doc, docx, xls, xlsx, ppt, pptx, apk, txt, csv, epub
  3. 选择验证码模式。默认“单附件授权窗口”会让同一访客完成验证后,在 15 分钟内重复下载同一附件时免于再次验证。
  4. 选择文件交付驱动,并确认“上传根目录”指向 Typecho 的真实上传目录。生产环境使用 Nginx 时推荐 x_accel;不确定时可先使用默认的 php_stream
  5. 保存设置后进入“下载统计 -> 设置与工具”,点击“开始重建索引”。按页面提示继续分批执行,直到全部附件扫描完成。

插件会自动同步启用后新增、修改或删除的 Typecho 附件。重建索引主要用于首次安装、迁移历史文章、批量导入附件或修复索引不一致。

3. 配置源文件保护

下载网关只能保护通过它发起的请求。要防止访客绕过验证码直接访问 /usr/uploads/...,还必须在 Web 服务器上拒绝受保护扩展名的公开静态访问。

Nginx

  1. 参考 nginx.conf.example,将配置人工合并到当前站点的 server 块。
  2. 使用 x_accel 时,将示例中的 alias 改为真实上传目录,并确保“内部 URI 前缀”与插件设置完全一致。
  3. Typecho 安装在子目录时,为内部路径和公开上传路径的 location 同时加上对应的站点路径前缀。
  4. 让公开上传路径规则中的扩展名与插件的“受保护下载扩展名”保持一致,然后检查配置并平滑重载 Nginx。

Apache

  1. 参考 uploads.htaccess.example,将规则合并到真实的 usr/uploads/.htaccess。不要覆盖该目录已有规则。
  2. 使用默认 php_stream 时,只需保证 PHP 对上传目录有读取权限。
  3. 使用 x_sendfile 时,还需安装并启用 mod_xsendfile,再参考 apache-xsendfile.conf.example 在 Apache 主配置或 VirtualHost 中设置 XSendFilePath
  4. .htaccess 中的扩展名与插件设置保持一致,然后检查配置并重载 Apache。

插件不会自动创建、覆盖或删除 Nginx、Apache 或 .htaccess 配置。只启用插件而不配置源文件保护,无法阻止访客绕过下载网关直接请求源文件。

4. 验证安装结果

  1. 打开“下载统计 -> 设置与工具”,执行“抽样检测直链保护”。
  2. 受保护的非图片源文件应返回 403404;图片应保持正常访问。
  3. 打开一篇包含受保护附件的文章,确认附件链接进入 /download/.../ 下载入口。
  4. 完成验证码并下载文件,确认文件内容正常、Range 下载可用,并在“下载统计 -> 概览”中看到统计变化。
  5. 清理 Typecho 整页缓存和 CDN 页面缓存,使已有文章重新渲染为下载网关链接。验证码页、下载入口和文件响应都不应被 CDN 缓存。

使用说明

在文章中提供附件下载

文章作者继续使用 Typecho 原有的附件上传和插入方式即可,不需要短代码:

  1. 在编辑文章时上传附件,并把附件以普通 <a href="..."> 链接插入正文。
  2. 发布或预览文章时,插件会在运行时识别已索引且扩展名受保护的本地附件,并把链接改写到下载网关。
  3. 访客点击链接后按当前验证码策略完成验证。插件随后创建短期下载会话、启动文件下载,并自动返回附件所属文章。

图片会通过 MIME 和扩展名双重排除。插件只改写 HTML 中的 <a href>,不会处理图片、音视频 src、CSS URL 或由 JavaScript 动态生成的链接。

若附件使用已配置的 CDN 域名,请在“CDN 基础地址”中每行填写一个完整基础地址。未配置的外站链接不会被当作本站附件改写。

查看统计和复制下载入口

  • “下载统计 -> 概览”:查看今日、近 7 天、近 30 天或自定义日期范围的下载、每日 UV、趋势、Top 10 和来源分布,并导出日统计 CSV。
  • “下载统计 -> 附件排行”:按时间、文件名、扩展名和删除状态筛选附件,导出排行,或复制某个附件的公开下载入口。
  • “下载统计 -> 附件详情”:查看单个附件的元数据、趋势和来源,复制或打开下载入口,重建该附件索引,导出 CSV,或按确认提示重置该附件统计。
  • “下载统计 -> 设置与工具”:检查最终生效配置和服务器环境,继续重建附件索引,清理过期挑战、会话、限流及 UV 明细。

附件的公开下载入口可以放在正文之外,例如导航页或自定义主题页面。公开键只能降低通过 CID 枚举附件的风险,并不是登录权限或 DRM;拿到入口的访客仍可按当前验证策略下载文件。

关键设置

设置作用与建议
受保护下载扩展名决定哪些非图片附件会被改写和统计。修改后必须同步更新 Web 服务器的源文件拒绝规则。
CDN 基础地址每行一个允许参与匹配的 CDN 基础地址;普通外站链接不会被改写。
验证码模式“关闭”不显示验证码;“每次验证”每次都验证;“单附件授权窗口”仅复用同一附件授权;“全站授权窗口”在窗口期内复用全部附件授权;“风险触发”会对新访客、异常 UA、外部来源和直接访问要求验证。
验证授权窗口控制“单附件授权窗口”和“全站授权窗口”的免重复验证时间,默认 15 分钟。
下载会话控制下载令牌有效期和最多请求数。Range 分段与网络重试共享请求数上限。
会话绑定可将短期下载会话绑定到 IP 网段哈希或浏览器标识哈希;启用前应考虑移动网络和代理切换。
统计排除默认不统计管理员下载,便于站点维护时避免污染数据。
尊重 Do Not Track启用后不建立长期访客 Cookie;下载仍会统计,但跨浏览器会话的 UV 去重不再稳定。
文件交付驱动php_stream 兼容性最好;x_accel 适用于 Nginx;x_sendfile 适用于已启用 mod_xsendfile 的 Apache。
上传根目录必须是服务器上的真实文件系统目录,而不是 URL。PHP 或发送模块需要具备读取权限。
Nginx internal URI 前缀仅供 x_accel 使用,必须与 Nginx internal location 完全一致。

交付驱动、上传根目录和 Nginx internal URI 前缀可以通过 config.inc.php 常量或同名环境变量覆盖。最终优先级为:常量、环境变量、插件设置、安全默认值。后台“设置与工具”会显示当前生效值及其来源。

默认行为

  • 保护扩展名:zip, rar, 7z, pdf, doc, docx, xls, xlsx, ppt, pptx, apk, txt, csv, epub
  • 图片通过 MIME 和扩展名双重排除,不改写、不统计。
  • 兼容迁移文章中由 <span class="attachment"> 标记的旧附件;历史绝对域名只用于提取本地路径,普通外站链接不会被改写。
  • 验证码模式:同一访客、同一附件验证后 15 分钟免重复验证。
  • 验证码挑战有效期:5 分钟。
  • 下载会话有效期:10 分钟,最多接受 32 个 GET/HEAD/Range 请求。
  • 验证码挑战、失败尝试和下载会话创建同时按匿名访客及网络摘要限流。
  • 同一下载会话只有首个有效 GET 计数;HEAD、非法 Range、后续 Range 和重试不重复计数。
  • 下载会话创建后通过同源中转页启动文件下载,并自动返回附件所属文章;没有父文章时返回站点首页。
  • 每日 UV:同一访客在站点时区的同一天内,对同一附件只计一次。
  • UV 去重明细保留 90 天,日聚合和累计统计长期保留。

交付驱动

PHP Stream

默认安全回退,适用于本地 Apache 和未安装发送模块的环境。支持:

  • 完整 200 下载。
  • 单段 Range 206
  • 越界或多段 Range 416
  • HEAD 元数据请求。
  • 固定分块流式读取,不把整个附件载入内存。

Nginx X-Accel-Redirect

生产环境推荐使用。先将插件交付驱动改为 x_accel,再按 nginx.conf.example 配置内部目录。Nginx 的 alias 必须指向真实上传目录,内部 URI 前缀必须与插件最终生效值一致。

可在 config.inc.php 中使用部署覆盖,避免从本地复制数据库后误用 Windows 路径:

define('ATTACHMENT_DOWNLOAD_DELIVERY_DRIVER', 'x_accel');
define('ATTACHMENT_DOWNLOAD_UPLOAD_ROOT', '/var/www/typecho/usr/uploads');
define('ATTACHMENT_DOWNLOAD_INTERNAL_URI_PREFIX', '/_attachment_download_internal/');

也可以使用同名环境变量。优先级为常量、环境变量、插件设置、安全默认值。

Apache X-Sendfile

安装 mod_xsendfile 后可使用 x_sendfile。按 apache-xsendfile.conf.example 允许真实上传目录。源文件拒绝规则仍需单独配置。

源文件保护

Nginx 模板同时包含:

  • 仅能由 X-Accel-Redirect 使用的 internal 路径。
  • 对当前非图片下载扩展名返回 404 的公开上传路径规则。

Apache 的 uploads.htaccess.example 应放到真实 usr/uploads/ 目录并按站点扩展名调整。插件不会自动创建、覆盖或删除任何服务器配置。

受保护扩展名与服务器规则必须保持一致。若把 mp3mp4 等内嵌媒体加入保护集合,主题中的 <audio src><video src><source src> 将无法直接播放;后台环境检测会提示这类冲突。

后台功能

  • 概览:今日、近 7 天、近 30 天、累计下载,每日 UV、趋势、Top 10 和来源分布。
  • 附件排行:按周期、文件名、扩展名和删除状态筛选,查看周期下载、周期每日 UV 和累计下载。
  • 附件详情:元数据、按日趋势、来源排行、公开下载入口、CSV 和单附件统计重置。
  • 设置与工具:最终生效配置来源、环境检测、服务器规则、分批索引、短期数据清理和数据删除。
  • CSV:导出日统计、附件排行和来源聚合,不导出访客哈希、网络哈希、挑战哈希或会话哈希。

主题 API

根据附件 CID 获取下载入口:

$url = \TypechoPlugin\AttachmentDownloads\Plugin::downloadUrl($attachmentCid);
if ($url !== null) {
    echo htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
}

获取累计下载量:

$downloads = \TypechoPlugin\AttachmentDownloads\Plugin::totalDownloads($attachmentCid);

获取指定周期的下载次数和每日 UV 合计:

$period = \TypechoPlugin\AttachmentDownloads\Plugin::downloadPeriod($attachmentCid, '30d');

获取安全元数据:

$metadata = \TypechoPlugin\AttachmentDownloads\Plugin::downloadMetadata($attachmentCid);

元数据只包含 CID、文件名、扩展名、MIME、大小和累计下载,不返回物理路径或源文件 URL。

配合本站主题的效果:

image.png

数据与隐私

  • 浏览器保存至少 192 bit 随机第一方 Cookie,设置 HttpOnlySameSite=Lax,HTTPS 下同时设置 Secure
  • 数据库只保存带插件密钥的 HMAC,不保存 Cookie 原值、原始 IP、完整 User-Agent 或完整 Referer。
  • IP 网段和 User-Agent 仅在启用会话绑定或限流时以 HMAC 参与短期状态。
  • 外部来源只保存规范化小写域名,站内来源优先保存内容 CID。
  • 启用 Do Not Track 后,已有长期访客 Cookie 会降级为浏览器会话 Cookie;下载仍可统计,但跨浏览器会话 UV 不再保持稳定。

下载链接不继承文章密码权限,也不要求登录。随机公开键用于降低 CID 枚举风险,但不是 DRM 或用户权限系统;获得下载入口的人仍可通过验证下载附件。

停用与删除

  • 停用插件会移除路由、Action、菜单和运行时钩子。插件使用独立配置备份规避 Typecho 停用时清理标准插件配置的行为,下次启用会恢复原设置。
  • 停用不会移除 Nginx、Apache 或 .htaccess 规则。服务器保护仍开启时,受保护附件会暂时无法下载。
  • “清空全部统计”保留附件索引,只重置累计和聚合统计。
  • “永久删除插件数据与配置”不可恢复;执行后应立即停用插件,停用过程会清除独立配置备份,重新启用会创建空表和默认配置。

永久卸载前应先恢复源文件公开规则,确认站点附件访问策略,再删除插件数据。

测试

测试使用临时 SQLite 文件,不读取或写入站点数据库:

php usr/plugins/AttachmentDownloads/tests/run.php

覆盖范围包括建表、附件索引、跨数据库 upsert、DOM 链接改写、路径穿越与双重编码拒绝、每日 UV 去重、来源聚合、Range 和响应头安全。

故障排查

正文链接没有改写

确认 DOM 扩展已启用、附件索引已完成,并清理整页缓存。插件只改写 <a href>,不会改写图片、音视频 src、CSS URL 或脚本生成的链接。迁移文章若没有 Typecho 原生附件记录,需要保留 <span class="attachment"> 标记,并确保 URL 路径对应的文件存在于“上传根目录”下。

验证码显示失败

确认 GD 扩展已启用,并检查 /action/attachment-download?do=captcha 是否被缓存或安全规则拦截。验证码与下载响应必须保持 no-store

提示“请求来源无效”

清理验证码页和 CDN 缓存后重新打开下载入口。插件使用当前请求的同源相对地址提交验证码,并同时校验浏览器 Fetch Metadata、当前请求源和 Typecho 站点地址;反向代理必须正确转发 HostX-Forwarded-Proto

Nginx 下载返回空内容或 404

核对 internal URIalias、上传根目录和附件 URL 路径前缀。alias 应指向 usr/uploads/,并以斜杠结尾。

Apache 下载被源文件规则一起阻止

拒绝规则只影响 HTTP 静态访问,不应影响 PHP 文件读取。确认 PHP 用户对上传目录有读取权限;使用 X-Sendfile 时还要配置 XSendFilePath

插件下载地址

AttachmentDownloads1.0.0.zip

其他

如果大家有什么其他插件需求,也可以留言,包括但不限于typecho等PHP博客。