MENU

Typecho 图片上传&处理插件 - PicUp

• 2026 年 03 月 20 日 • 阅读: 37 • 技术,开发,文档

🧩 PicUp for Typecho

多存储后端图片上传&处理插件,支持多种远程存储服务,支持上传前图片处理,多 Profile 通过 JSON 存储,可随时切换。

目录

PicUp for Typecho

✨ 功能特性

  • 🗂️ 多存储后端 — 支持 12+ 种云存储 / 图床,可随时切换
  • 📦 多 Profile 配置 — 同时保存多套配置方案,一键应用切换
  • 🖼️ 图像处理扩展 — 图片压缩、自动转 WebP、添加水印,可逐个开关
  • 🔌 扩展化架构 — 驱动和扩展均自动发现,放入对应目录即生效
  • 📱 响应式配置界面 — 移动端友好,支持深色模式
  • 上传进度提示 — Toast 通知,实时展示上传状态

📦 支持的存储驱动

驱动标识说明
本地存储local遵循 Typecho 原生逻辑,存储至 usr/uploads/
Lsky Pro 兰空图床lsky支持 v1 / v2 API
AWS S3 / 兼容s3支持 AWS S3、MinIO、Cloudflare R2、阿里云 OSS(S3 兼容)等
WebDAVwebdav标准 WebDAV 协议
GitHub 仓库github通过 GitHub Contents API 存储,支持 CDN 加速
S.EE (SM.MS)smmsS.EE 免费图床
阿里云 OSSaliyunoss阿里云对象存储(原生 V1 签名)
腾讯云 COStencentcos腾讯云对象存储(COS V5 签名)
七牛云 KODOqiniukodo七牛云对象存储
又拍云 USSupyun又拍云云存储
EasyImage 简单图床easyimageEasyImage 自建图床
CloudFlare ImgBedcfimgbed基于 Cloudflare Workers 的图床
NodeImagenodeimageNodeImage 图床,通过 X-API-Key 认证
Chevereto V4cheveretoV4Chevereto V4 自建图床,支持相册
ImgurimgurImgur 图床,支持匿名/账户上传
初春图床 (OneImg)oneimg初春图床,Bearer Token 认证
Telegram 图床tgimagebedtg-imagebed 项目,支持匿名和 Token 上传
Zpic 图床zpicZpic / ImgURL Pro,支持 V2/V3 API

🖼️ 图像处理扩展

扩展存放于 extensions/ 目录,每个方案(Profile)可独立配置开启/关闭。

扩展标识依赖说明
图片压缩compressPHP gd 扩展对 JPEG/PNG/WebP 进行有损/无损压缩,可设置质量百分比
自动转 WebPwebpPHP gd + WebP 支持上传前将 JPEG/PNG/GIF/BMP 转换为 WebP 格式
添加水印watermarkPHP gd 扩展支持文字水印(TTF 字体)和图片水印,可设置位置/透明度
提示:扩展会在文件上传至云存储前在服务端处理,原文件不会被修改。

水印添加示例:

水印展示


🔌 插件开发指南

所有驱动和扩展均自动发现——按规范命名放入对应目录即可,无需修改任何注册代码。

开发存储驱动

vendor/ 目录新建 XxxDriver.php,实现 DriverInterface 接口。文件名规则:大驼峰名称 + Driver.php,插件以文件名去掉 Driver 后缀转小写作为驱动标识符(如 MyS3Driver.phpmys3)。

<?php
namespace TypechoPlugin\PicUp\vendor;

class XxxDriver implements DriverInterface
{
    /** 驱动显示名称(后台设置中展示) */
    public static function getName(): string
    {
        return '我的存储服务';
    }

    /**
     * 驱动配置字段定义
     * type 支持:text | password | select
     * select 类型需提供 options: [['value'=>'v1','label'=>'V1'], ...]
     */
    public static function getConfigFields(): array
    {
        return [
            ['name' => 'endpoint',  'label' => '服务地址',    'type' => 'text',     'default' => ''],
            ['name' => 'bucket',    'label' => 'Bucket 名称', 'type' => 'text',     'default' => ''],
            ['name' => 'accessKey', 'label' => 'Access Key',  'type' => 'password', 'default' => ''],
            ['name' => 'secretKey', 'label' => 'Secret Key',  'type' => 'password', 'default' => ''],
            ['name' => 'region',    'label' => '地域',        'type' => 'select',   'default' => 'cn-east',
             'options' => [['value' => 'cn-east', 'label' => '华东'], ['value' => 'cn-north', 'label' => '华北']]],
        ];
    }

    public function __construct(array $config)
    {
        // 从 $config 读取配置字段,初始化 SDK / HTTP 客户端
        $this->endpoint  = rtrim($config['endpoint'] ?? '', '/');
        $this->bucket    = $config['bucket'] ?? '';
        $this->accessKey = $config['accessKey'] ?? '';
        $this->secretKey = $config['secretKey'] ?? '';
    }

    /**
     * 上传文件
     * @param string $localFile  本地临时文件绝对路径
     * @param string $remotePath 目标存储路径(含文件名),如 2024/01/photo.jpg
     * @param string $mimeType   文件 MIME 类型
     * @return string|false      成功返回可访问的文件 URL,失败返回 false
     */
    public function upload(string $localFile, string $remotePath, string $mimeType)
    {
        // 调用存储服务 SDK / HTTP API 上传文件
        // 失败时 return false,插件会自动弹出错误提示
        return 'https://cdn.example.com/' . $remotePath;
    }

    /**
     * 删除文件
     * @param string $remotePath 存储路径(由 getStoredPath() 返回的值)
     */
    public function delete(string $remotePath): bool
    {
        return true;
    }

    /**
     * 根据存储路径构造可访问的 URL
     */
    public function getUrl(string $remotePath): string
    {
        return 'https://cdn.example.com/' . $remotePath;
    }

    /**
     * 从上传结果反推存储路径,用于后续删除文件
     * 若 URL 即为存储路径,直接返回 $remotePath 即可
     */
    public function getStoredPath(string $remotePath, string $uploadedUrl): string
    {
        return $remotePath;
    }

    /**
     * 是否每次上传都生成新路径(某些图床会自动重命名)
     * 返回 true 时,插件不会复用已有路径
     */
    public function alwaysNewPath(): bool
    {
        return false;
    }
}

开发图像处理扩展

extensions/ 目录新建 XxxExtension.php,实现 ExtensionInterface 接口。扩展标识符为文件名去掉 Extension 后缀转小写。

<?php
namespace TypechoPlugin\PicUp\extensions;

class XxxExtension implements ExtensionInterface
{
    public static function getName(): string        { return '我的扩展'; }
    public static function getDescription(): string { return '对上传图片做 XX 处理'; }

    /**
     * 执行顺序,数字越小越先执行
     * 内置顺序参考:compress=10, webp=20, watermark=30
     */
    public static function getOrder(): int { return 50; }

    /** 依赖的 PHP 扩展,缺失时在后台显示"不可用"警告 */
    public static function getRequiredPhpExtensions(): array { return ['gd']; }
    public static function isAvailable(): bool { return extension_loaded('gd'); }

    /** 配置字段(格式同驱动配置字段) */
    public static function getConfigFields(): array
    {
        return [
            ['name' => 'quality', 'label' => '处理质量', 'type' => 'text', 'default' => '80'],
        ];
    }

    /**
     * 处理文件(核心方法)
     *
     * @param string $localFile 输入文件路径(只读,不要直接修改原文件)
     * @param string $mimeType  输入文件 MIME 类型
     * @param array  $config    当前扩展的配置项(来自方案的 _extensions.xxx)
     * @return array            [输出文件路径, 输出MIME类型]
     *                          若输出了新的临时文件,插件核心会统一清理
     */
    public function process(string $localFile, string $mimeType, array $config): array
    {
        // 创建临时文件承载处理结果
        $tmpFile = tempnam(sys_get_temp_dir(), 'picup_');

        // 示例:用 GD 处理图像
        $img = imagecreatefromjpeg($localFile);
        // ... 处理逻辑 ...
        imagejpeg($img, $tmpFile, (int)($config['quality'] ?? 80));
        imagedestroy($img);

        return [$tmpFile, $mimeType];
    }
}

开发注意事项

  • process() 接收的 $localFile只读的,输出结果请写入新临时文件后返回新路径。
  • 多个扩展按 getOrder() 串行执行,上一个扩展的输出是下一个扩展的输入。
  • 临时文件由插件核心在上传完成后统一清理,无需在扩展内手动 unlink
  • 错误建议通过 error_log('[PicUp][XxxExtension] ...') 记录,不要直接 echo(会污染上传响应)。

🖼️ 图床 SDK 接入指南

本节面向希望将自有图床服务接入 PicUp 或为第三方图床编写驱动的开发者。

接口规范

驱动须实现 vendor/DriverInterface.php 中定义的所有方法:

interface DriverInterface
{
    // 元信息(静态)
    public static function getName(): string;
    public static function getConfigFields(): array;

    // 构造(接收解析后的配置数组)
    public function __construct(array $config);

    // 核心操作
    public function upload(string $localFile, string $remotePath, string $mimeType): string|false;
    public function delete(string $remotePath): bool;
    public function getUrl(string $remotePath): string;
    public function getStoredPath(string $remotePath, string $uploadedUrl): string;
    public function alwaysNewPath(): bool;
}

上传调用流程

用户上传文件
     │
     ▼
Plugin::uploadHandle()
     │
     ├─► 文件安全检查(扩展名白名单)
     │
     ├─► applyExtensions()       // 依次调用各 Extension::process()
     │       compress(10) → webp(20) → watermark(30) → 自定义(50) → ...
     │
     ├─► 实例化活跃方案的 Driver(传入方案配置数组)
     │
     ├─► Driver::upload($localFile, $remotePath, $mimeType)
     │       └─ 返回文件 URL(失败返回 false)
     │
     └─► 清理临时文件,返回 Typecho 附件数组

getConfigFields() 字段属性说明

属性类型必填说明
namestring字段 key,$config['name'] 读取
labelstring前端显示标签
typestringtext \ password \ select
defaultstring默认值
optionsarrayselect 时必填[['value'=>'v1','label'=>'V1'], ...]
placeholderstring输入框占位符

调试技巧

  • 驱动内部错误统一使用 error_log('[PicUp][XxxDriver] 说明 ' . $detail) 记录。
  • upload() 返回 false 时,插件自动弹出错误 Toast,无需驱动内部处理 UI。
  • __construct() 中可做配置校验,抛出 \Exception 时插件会捕获并写入日志。
  • 开发时可临时在 upload() 里加 file_put_contents('/tmp/picup_debug.log', ...) 辅助调试。
返回文章列表 打赏
本页链接的二维码
打赏二维码
添加新评论

已有 2 条评论
  1. Huo Huo        
    这插件不错,太好了,感谢博主这么好的作品
    1. LHL LHL        
      @Huo谢谢~