我最近基于旧版 Typecho Watermark 插件重构了一个 2.0.0 版本。

独立仓库位于:https://github.com/NHPT/Watermark

社区仓库位于:https://github.com/typecho-fans/plugins/tree/master/Watermark

在把 Typecho 升级到 1.3 版本后,我发现插件在这个版本存在一些问题,并且我发现插件只支持动态水印,可以通过访问解码后的地址获取无水印图片,且缺少很多我想要的功能。我觉得应该增加一个保护原图的功能:让公开目录里只保留水印成品,把原图保存到 Web 根目录外。在修复、优化的过程中,我发现改动挺大了,不如重构一个版本把。

实际效果可以在我的博客看到。

优缺点

我之前使用过的水印插件有两类:

  • 直接覆盖原图。优点是公开目录里确实只剩水印图;缺点是如果没有可靠备份,原图可能无法恢复,而且容易出现重复加水印。
  • 动态水印。优点是改动小,关闭插件后影响少;缺点是原图仍然公开,不能阻止别人直接访问上传目录里的原图。

Watermark 2.0.0 采用的是折中但更稳的方式。它的优点是:

  • 支持动态模式和原图保护模式;
  • 支持图片/文字水印、单点/平铺、旋转、透明度、尺寸过滤、路径排除、静态缓存;
  • 原图不再放在 Web 可访问目录;
  • 公开路径继续可用,不需要改文章内容;
  • 支持新上传图片自动处理;
  • 支持历史图片批量迁移;
  • 支持从私有目录恢复公开原图;
  • 通过元数据避免重复加水印;
  • 目录、权限和 open_basedir 问题会在后台提示;
  • 批量任务支持自动分批、暂停继续、失败重试。

它的缺点也需要提前说清楚:

  • 必须有 GD 扩展;
  • 服务器必须允许 PHP 写入私有原图目录;
  • 首次迁移大量历史图片需要时间;
  • 私有原图目录必须长期备份;

如果只是想让文章图片“显示时带水印”,动态模式够用;如果想避免公开路径直接暴露无水印原图,就应该使用原图保护模式。

核心亮点

1. 原图保护模式

启用原图保护后,插件会把无水印原图保存到站点根目录之外,例如:/path/to/.typecho-watermark/

公开目录 usr/uploads 中保留的是水印成品。文章里的图片路径不用改,访客访问到的仍然是原来的公开路径,但文件内容已经是带水印版本。私有目录非常重要。它保存的是唯一可恢复的无水印版本,不能删除、清空、覆盖,也不应该放在临时目录里。

2. 历史图片迁移

新上传和替换的图片可以立即处理,但老站点通常已经有大量历史图片。Watermark 2.0.0 提供两种处理方式:

  • 正文中的历史图片首次访问时自动懒迁移;
  • 后台执行“迁移或重新生成现有图片”,集中扫描并处理 usr/uploads

后台批量迁移不限于正文引用图片,也会处理上传目录里的其他图片,因此更适合老站点升级。

3. 自动分批处理

图片处理不能一次请求跑完,尤其是站点图片很多时,很容易超时。所以插件采用“前端自动连续请求 + 服务端分批处理”的方式,每批最多处理 20 张。管理员点击一次即可开始,页面会自动续批,并显示:

  • 候选图片数量;
  • 已完成;
  • 待处理;
  • 失败;
  • 当前处理阶段。

任务支持暂停、继续、失败项重试和关闭弹窗后续传。首次扫描和每批处理时也会显示 loading 和阶段说明,避免长时间白屏。

4. 防止重复加水印

重复加水印是水印插件很常见的问题。Watermark 2.0.0 会记录原图、水印图和当前配置的 SHA-256 指纹。只要图片已经是当前配置生成的水印图,就不会重复处理;如果水印配置变了,会从私有原图重新生成,而不是拿已经带水印的公开图继续叠加。

5. 可恢复、可迁移

启用原图保护后,如果以后想退出,可以在后台执行“恢复公开原图”。插件会从私有目录把无水印原图恢复到 usr/uploads。如果需要更换私有原图目录,插件提供“安全迁移私有目录”,会分批复制、校验 SHA-256,全部通过后才切换配置,旧目录会保留用于回滚。这两个功能是我认为很重要的安全边界:插件不能只会“保护”,还必须能让用户安全退出。

如何使用

推荐从 Release 下载:https://github.com/NHPT/Watermark/releases

安装步骤:

  1. 下载 Watermark.zip
  2. 解压到 Typecho 的 usr/plugins/,最终目录应为 usr/plugins/Watermark/
  3. 在 Typecho 后台启用插件。
  4. 进入插件设置,选择水印类型、位置、透明度、平铺、旋转等参数。
  5. 如果只需要展示水印,保持动态模式即可。
  6. 如果要保护原图,切换到“原图保护模式”,并确认私有目录校验成功。
  7. 对已有站点执行“迁移或重新生成现有图片”。

从 1.x 升级到 2.0.0 时,建议先截图或记录旧设置。旧版本没有自动备份和恢复配置功能,而 Typecho 停用插件时会删除普通插件配置。停用或删除插件不会自动恢复原图,也不会删除私有目录。

设计流程

这次重构是从一个实际问题开始的:文章里看到的是水印图,但公开上传路径仍然能访问原图。所以我先把目标拆成几个问题:

  1. 原图应该放在哪里,才能避免 Web 直接访问?
  2. 公开路径如何继续兼容已有文章?
  3. 历史图片如何迁移?
  4. 如何避免重复加水印?
  5. 用户如何恢复原图?
  6. 目录权限、open_basedir、批量超时这些部署问题如何处理?

最终代码大致分成三块:

  • Protection.php:负责私有原图存储、元数据、状态识别、恢复和目录迁移。
  • Plugin.php:负责 Typecho 钩子、设置页、上传/替换/删除处理、内容图片改写。
  • Action.php:负责动态图片输出、首次访问懒迁移、后台批量迁移、恢复和目录迁移任务。

渲染器 class.php 也做了增强,支持只保存不输出、失败回滚、确认水印确实被绘制,避免批量任务中把坏结果写入公开目录。

开发完成后做过 PHP 7.4 到 8.5、GD 生命周期、原图保护、恢复、目录迁移、open_basedir、批量任务、暂停续传、失败重试和大批量图片场景测试。

适合谁

我建议下面这类 Typecho 用户使用 Watermark 2.0.0:

  • 站点有较多原创图片;
  • 不希望别人直接拿到无水印原图;
  • 希望公开图片路径保持不变;
  • 希望历史图片可以批量迁移;
  • 希望以后还能恢复原图;
  • 希望插件停用、升级、重装后还能找回原图关系。
文章目录