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