封面

Typecho 图床从USS迁移至COS

图床迁移耗费了两天,终于是将数据和配置都传好了。这篇文章将讲述我是如何从又拍云 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,从而让工作流能够覆盖源文件。

评论

评论即代表你已阅读并同意评论协议
内容加载中...