Ghost 博客替换云存储翻车记录
- W_Z_C
- 共 1758 字,阅读约 4 分钟
这几天访问博客总感觉打开图片比较慢,应该是服务器带宽不够,加上我上传图片也不怎么注意大小导致的,搜索了一下 Ghost 官方文档,可以手动更改博客图片的存储方式,感觉不是很复杂,心想替换上去不是美滋滋,所以直接换了,没想到直接翻车。记得上一次是弄熊掌号的时候同样遇见麻烦,后来博客直接倒闭了……这一次记录一下细节,希望可以帮助其他人避免这些坑爹情况。
Ghost 官方文档上说,用户可以自定义一个存储适配器,该适配器本质上是一个类,Ghost 在运行过程中会根据用户上传图片的动作调用这个类的若干函数。默认情况下 Ghost 会调用本地存储的适配器,该适配器是 Ghost 自带的,假如你已经实现了自己的存储适配器,只需要更改一下配置即可,具体的步骤如下:
- 在 Ghost 程序的根目录,有一个 content 文件夹,第一步需要创建存储适配器的目录,然后将存储适配器的代码放进入。该目录的位置:
content/adapters/storage
。 - 实现自己的存储适配器代码,官方有一个第三方适配器的列表,我选了其中一个名字叫做 ghost-upyun-store 的适配器,这个适配器可以将图片自动的保存到又拍云上。
安装好后,需要重启 Ghost 才能生效。但是我万万没想到,修改了配置之后,原有的图片路径竟然全部失效,博客上所有的图片都无法打开,全部 404 ,我也是醉了……
我立刻登陆服务器,进入数据库,将数据库中 posts 表中的 feature_image 以及 html 两个字段中的链接全部更新,参考语句如下:
update posts set feature_image = CONCAT('https://img.wangzhechao.com/upload', substr(feature_image, 9))
update posts set html = replace(html, "/content/", "https://img.wangzhechao.com/upload/")
本来以为一下搞定,但是又发现了一个奇葩 bug:
就知道事情没这么简单,继续研究,查看 Ghost 日志:
TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be one of type string, TypedArray, or DataView. Received type boolean
at Hash.update (internal/crypto/hash.js:58:11)
at buildContentResponse (/var/www/ghost/versions/3.2.0/core/server/web/shared/middlewares/serve-favicon.js:17:48)
at storage.getStorage.read.then (/var/www/ghost/versions/3.2.0/core/server/web/shared/middlewares/serve-favicon.js:56:35)
at tryCatcher (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/promise.js:512:31)
at Promise._settlePromise (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/promise.js:569:18)
at Promise._settlePromise0 (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/promise.js:614:10)
at Promise._settlePromises (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/promise.js:693:18)
at Async._drainQueue (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/async.js:133:16)
at Async._drainQueues (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/async.js:143:10)
at Immediate.Async.drainQueues [as _onImmediate] (/var/www/ghost/content/adapters/storage/ghost-upyun-store/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)
at process.topLevelDomainCallback (domain.js:126:23)
日志提示信息感觉像是读图标失败的表现,尼玛,我的图标呢?
没办法只能读代码研究一下:
const buildContentResponse = (ext, buf) => {
content = {
headers: {
'Content-Type': `image/${ext}`,
'Content-Length': buf.length,
ETag: `"${crypto.createHash('md5').update(buf, 'utf8').digest('hex')}"`,
'Cache-Control': `public, max-age=${config.get('caching:favicon:maxAge')}`
},
body: buf
};
return content;
};
初步看应该是 buf 返回不正常,导致 md5 调用 update 方法失败报异常,再次看之前的调用,也是这个文件:
function serveFavicon() {
let iconType;
let filePath;
return function serveFavicon(req, res, next) {
if (req.path.match(/^/favicon.(ico|png)/i)) {
//......
filePath = imageLib.blogIcon.getIconPath();
let originalExtension = path.extname(filePath).toLowerCase();
const requestedExtension = path.extname(req.path).toLowerCase();
// CASE: custom favicon exists, load it from local file storage
if (settingsCache.get('icon')) {
// depends on the uploaded icon extension
if (originalExtension !== requestedExtension) {
return res.redirect(302, urlUtils.urlFor({relativeUrl: `/favicon${originalExtension}`}));
}
storage.getStorage()
.read({path: filePath})
.then((buf) => {
iconType = imageLib.blogIcon.getIconType();
content = buildContentResponse(iconType, buf);
res.writeHead(200, content.headers);
res.end(content.body);
})
.catch(next);
} else {
//......
}
} else {
return next();
}
};
}
这里应该就是加载图标的函数,只不过貌似做了一下缓存,其中调用了 storeage.getStorage.read 函数,这个函数正是自定义存储适配器中的函数,难道这个类实现有问题?
我打印了一下这个 filePath 路径,确认路径是:
/2020/01/bitbug_favicon.ico
我有点懵逼了,为毛图标的路径在这里?我又看了看存储适配器中的代码:
read(options) {
options = options || {};
const client = this.client;
const key = urlParse(options.path).pathname.slice(1);
return new Promise(function (resolve, reject) {
client.getFile(key).then(function (result) {
resolve(result);
}).catch(function (error) {
reject('Could not read image');
});
});
}
这里 client 是又拍云提供的官方 SDK,我去查了一下 getFile 函数:
getFile(remotePath, saveStream = null)
下载保存在又拍云服务的文件
参数
remotePath
: 需要下载的文件路径saveStream
: 可选值,一个可以写入的流。若传递该参数,下载的文件将直接写入该流。该参数不支持浏览器端使用
第一个参数是完整的图片路径,SDK 既然是官方提供的,参数说明肯定不会错,反推回来,也就是说这个图标的路径有问题,并且我有一个更奇怪的问题,为什么图标的名称叫做 bitbug_favicon.ico
,bitbug_
这个前缀是什么鬼?
我想了很久,并且去看是否有 ico 的后台配置,找着找着,突然我想起个问题,为什么这个图标的位置会在 2020/01
这个目录?这个目录貌似是因为我 1 月建立博客才会存在的,我突然想起应该看看博客后台,看过之后,我恨不得给我自己一巴掌……
我竟然忘记了查看后台配置信息:
破案了,原来是因为本地存储器保存图片的路径是 2020/01/bitbug_favicon.ico
,所以一直加载这个路径,最后导致显示 404,删除重新上传即可。
看了几篇文章,貌似一切都很完美,不过等一等,尼玛这是什么?
所有引用文章的图片貌似显示都是有问题的,我看了一下路径:
https://wangzhechao.comhttps://img.wangzhechao.com/upload/images/2020/02/sdl-windows.jpg
😰,很明显是因为前面我 SQL 语句替换导致的,文章中有绝对路径,也有相对路径,你统一用一种能死么?
继续写 SQL 替换:
update posts set html = replace(html, "https://wangzhechao.comhttps://img.wangzhechao.com", "https://img.wangzhechao.com")
除了这个之外,我还发现 posts 表中还有个 mobiledoc 字段,同样替换之:
update posts set mobiledoc = replace(mobiledoc, "/content/images/", "https://img.wangzhechao.com/upload/images/")
update posts set mobiledoc = replace(mobiledoc, "https://wangzhechao.comhttps://img.wangzhechao.com", "https://img.wangzhechao.com")
我接着查看其它的表结构,发现一个 mobiledoc 的表,其中的链接貌似都是相对链接,感觉最好还是更改一下:
update mobiledoc_revisions set mobiledoc = replace(mobiledoc, "/content/images/", "https://img.wangzhechao.com/upload/images/")
貌似改了好多行,不知道会不会有啥问题……
到此总算结束了,我点开大部分的文章,感觉应该是可以了,如果大家发现其它问题请及时告诉我,最后奉劝大家修改需谨慎,不要瞎折腾!