惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
爱范儿
爱范儿
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
博客园 - 聂微东
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
T
Tailwind CSS Blog
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
宝玉的分享
宝玉的分享
罗磊的独立博客
Jina AI
Jina AI

ImQi1

本站 CMS 已开源,欢迎体验 抖音刷到了一个模拟高中的游戏 或许我们每个人都有问题 介绍下本站特色功能及一些杂谈 蜘蛛侠4,咋说呢,还是挺烂的 找个工作好难啊 跟风做了一系列地图界面 川渝游结束,浅浅记录一下 来成都看大熊猫 跨越了两千公里,只为见到你 为了一个女孩,我哭了一整晚 彻底换到新的 CMS 了,Glass 主题介绍 我的 Nuxt 版网站即将上线了 个人毕业设计展 尝试从情绪中走出 更新到Ty1.3,顺便维护一些插件 没想到现在的编程AI已经这么好用了 ChatGPT年度总结,卖号纠纷,代码块新样式 近期拍摄的新照片 zai-org / Open-AutoGLM 已开源,体验一下 12月近况,实况照片功能 理想的未来和现实的现在 4000公里首摔,真是当头一棒 属实是信息差了,没想到现在的手机生态这么完善 比较是快乐的小偷 搬家,新车,父亲手术
Typecho 图床从USS迁移至COS
· 2025-12-30 · via ImQi1

图床迁移耗费了两天,终于是将数据和配置都传好了。这篇文章将讲述我是如何从又拍云 USS 迁移至腾讯云 COS 的,留作自己备份,同时也给大家做个参考。

心路历程

为什么要将数据存储从又拍云迁移至腾讯云呢。一方面是今年已经出现过很多次云厂商跑路的行为了,让我不由得害怕起我用了三年的免费又拍云是否某天也会卷铺走人。还有一个就是我想多一个来备份我的云数据。我的宝塔面板有一个定时任务,就是每隔一段时间备份数据库或者源码到又拍云。之前确实是发生过一次勒索事件,我的数据库被加密了,需要支付赎金才能解锁。好在我当时正在迁站,旧数据不要也没事,就算没有迁站,我也有备份,就在又拍云上,所以当时我就在邮箱里面给他骂回去了。

于是,我就打算将又拍云的数据下载下来传到腾讯云上。又拍云自带的文件管理不能归档,我用的是可道云将数据打包成压缩包下载的。下载速度还是不错的,我的文件一共 1.5GB,算上我的各个静态资源和备份。腾讯云自带一个 COS 管理工具,上传文件挺方便的,我也不需要依赖又拍云了。

然后就是 Typecho 和 COS 的对接。我找到一款插件,但是这是腾讯云国际版维护的插件,且 Github 上确实有其他开发者维护的 COS 插件。不过我看了源码,其他插件和 tencentcloud-typecho-plugin-cos 插件是一样的,源码都一模一样,所以我直接用了官方的。

配置的过程都没有什么困难,但是真正难到我的是在配置上传时自动加水印这个功能。原本又拍云,是支持图片处理规则的,然后又拍云插件也是支持上传时处理的,填一样的就行。但是 tencentcloud-typecho-plugin-cos 插件还真没有这个功能,于是我查官网查 php sdk 源码问客服,碰了许多次壁之后,终于找到了一个可行的方案。

跟售后对线

那就是使用腾讯云的工作流

至于为什么要在上传图片时就加水印呢。如果每次访问再加水印的话,那每次都需要调用一次图片处理,而图片处理是收费的。

基础配置

插件地址:https://github.com/Tencent-Cloud-Plugins/tencentcloud-typecho-plugin-cos

从这里下载插件后,按照插件的说明将插件复制到指定目录下并应用,然后进入 Typecho 后台配置插件设置。

首先是 SecretId 和 SecretKey,这个我建议使用子用户的形式配置,详细方式查看官方文档吧。大体步骤就是创建子用户,新建一个策略,允许在对象存储中的 DeleteObject 和 PutObject,然后给这个子用户分配此策略。然后用子用户生成 SecretId 和 SecretKey,填写进来就行了。

分配完此策略以后,可以本地上传一个图片试试,用子用户的 SecretId 和 SecretKey。

<?php

require dirname(__FILE__, 2) . '/vendor/autoload.php';

$secretId = "SECRETID"; //替换为用户的 secretId,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi
$secretKey = "SECRETKEY"; //替换为用户的 secretKey,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi
$region = "ap-beijing"; //替换为用户的 region,已创建桶归属的region可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket
$cosClient = new Qcloud\Cos\Client(
    array(
        'region' => $region,
        'scheme' => 'https', //协议头部,默认为http
        'credentials'=> array(
            'secretId'  => $secretId,
            'secretKey' => $secretKey)));
$local_path = "/data/exampleobject";
//添加tagging
/*$tagSet = http_build_query( array(
    urlencode("key1") => urlencode("value1"),
    urlencode("key2") => urlencode("value2")),
    '',
    '&'
); */
try {
    $result = $cosClient->putObject(array(
        'Bucket' => 'examplebucket-125000000', //存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.cloud.tencent.com/cos5/bucket
        'Key' => 'exampleobject',
        'Body' => fopen($local_path, 'rb'),
        /*
        'CacheControl' => 'string',
        'ContentDisposition' => 'string',
        'ContentEncoding' => 'string',
        'ContentLanguage' => 'string',
        'ContentLength' => integer,
        'ContentType' => 'string',
        'Expires' => 'string',
        'Metadata' => array(
            'string' => 'string',
        ),
        'StorageClass' => 'string',
        'Tagging' => $tagSet //最多10个标签
        */
    ));
    // 请求成功
    print_r($result);
} catch (\Exception $e) {
    // 请求失败
    echo($e);
}

源码:https://github.com/tencentyun/cos-php-sdk-v5/blob/master/sample/putObject.php

运行此代码需要使用腾讯云提供的 cos-php-sdk-v5,我们用源码方式下载就行,去 release 界面将.tar.gz 的包下载到本地,然后在 sample 内就可以找到这个文件。

配置工作流

接下来就是如何实现在上传时给图片自动加水印了。

官方文档:https://cloud.tencent.com/document/product/436/53967

进入Cos 控制台,然后按照文档中的找到工作流,然后创建。

工作流名称可以填“博客图片上传自动加水印”,格式匹配选图片文件。

点击输入后的“+”号,进入图片处理页面。存储桶选你的图床,目标文件名填 ${InputName}.webp,目标路径默认,然后去新建一个图片处理模板。

这里的目标文件名这么填的原因是,工作流有点相当于一个任务钩子,在上传图片完成后触发。而 Typecho 要利用我们上传图片完成后,cos 返回给 typecho 的链接来引用图片。所以我们的这个工作流实际上是将文件处理后将原文件覆盖。

图片处理配置

图片处理模板的配置中,其他的你自己选。图片水印需要你先将水印素材传到云存储中,然后配置即可。

图片处理模板

回到图片处理配置,刷新,选择新建的水印处理模板就行了。

回到工作流列表,勾选开启的开关启动工作流。

最后需要改一下插件的源码。将插件的 Plugin.php 中的 uploadHandle 函数,整体替换为下面的代码。

private static function isImageExt(string $ext): bool
{
    return in_array(strtolower($ext), [
            'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'
    ], true);
}

private static function uploadImageWithWatermark(
        $cosClient,
        string $bucket,
        string $uploadfile,
        string $path
): void {

    $cosClient->putObject([
            'Bucket' => $bucket,
            'Key'    => $path,
            'Body'   => fopen($uploadfile, 'rb'),
            'PicOperations' => "",
    ]);
}


private static function uploadNormalFile(
        $cosClient,
        string $bucket,
        string $uploadfile,
        string $path
): void {

    $cosClient->upload(
            $bucket,
            $path,
            fopen($uploadfile, 'rb'),
            [
                    'ACL' => 'public-read',
                    'CacheControl' => 'private',
            ]
    );
}

/**
 * @description: 上传文件处理函数
 * @param {array} $file
 * @return {*}
 * @throws Exception
 */
public static function uploadHandle(array $file)
{
    if (empty($file['name'])) {
        return false;
    }

    // 原始扩展名
    $originExt = self::getSafeName($file['name']);

    if (!\Widget\Upload::checkFileType($originExt)) {
        return false;
    }

    $opt = Options::alloc()->plugin(pluginName);

    $date = new \Typecho\Date($opt->gmtTime);
    $fileDir = self::getUploadDir() . $date->year . '/' . $date->month;

    // 是否图片
    $isImage = self::isImageExt($originExt);

    // 图片统一存成 webp
    $ext = $isImage ? 'webp' : $originExt;

    $fileName = sprintf('%u', crc32(uniqid())) . '.' . $ext;
    $path = $fileDir . '/' . $fileName;

    $uploadfile = self::getUploadFile($file);
    if (!isset($uploadfile)) {
        return false;
    }

    $cosClient = self::CosInit();

    try {
        // 防止重名
        $times = 10;
        while ($times > 0 && self::doesObjectExist($path)) {
            $fileName = sprintf('%u', crc32(uniqid($times--))) . '.' . $ext;
            $path = $fileDir . '/' . $fileName;
        }

        if ($isImage) {
            self::uploadImageWithWatermark(
                    $cosClient,
                    $opt->bucket,
                    $uploadfile,
                    $path
            );
        } else {
            self::uploadNormalFile(
                    $cosClient,
                    $opt->bucket,
                    $uploadfile,
                    $path
            );
        }

    } catch (\Exception $e) {
        echo $e;
        return false;
    }

    // 补 size
    if (!isset($file['size'])) {
        $info = $cosClient->headObject([
                'Bucket' => $opt->bucket,
                'Key'    => $path
        ])->toArray();

        $file['size'] = $info['ContentLength'];
    }

    // 本地备份(注意:图片这里是原图,不是 webp)
    if ($opt->local === 'open' && self::makeUploadDir($fileDir)) {
        @move_uploaded_file($uploadfile, $path);
    }

    return [
            'name' => $file['name'],
            'path' => $path,
            'size' => $file['size'],
            'type' => $ext,
            'mime' => \Typecho\Common::mimeContentType($path),
    ];
}

自己记得格式化一下。源码的作用是为了给上传图片和其他附件做个区分,最主要的是确保上传后的后缀名为 webp。因为我们上传的文件不一定是什么格式的,用这种方式能确保上传后的图片是 webp,从而让工作流能够覆盖源文件。