在 Ghost(v5版本)中通过修改 core 实现模板随机文章推荐
要点:自定义 helper 必须放到 Ghost 的 core helpers 目录(例如
/var/www/ghost/versions/5.113.0/core/frontend/helpers/
或current/core/frontend/helpers/
),然后重启 Ghost 才会被加载。托管在 Ghost(Pro) 的站点无法修改 core,这点请注意。
一、为什么要放到 core?
Ghost 在渲染时只会加载 core 中注册的 Handlebars helper。把 JS 放到主题目录下(content/themes/.../helpers/
)不会被 Ghost 自动识别为 core helper,因此不会生效。把 helper 放到 core 目录并重启 Ghost,Ghost 才会把它注册为 Handlebars helper 并在模板里可用(我们已在你的环境验证过:把测试 helper 放到 core 后页面能显示 HELLO_HELPER
)。
二、准备工作(备份 + 路径定位)
- 先备份 core(非常重要):
cd /var/www/ghost
cp -a versions/5.113.0/core versions/5.113.0/core.bak
- 找到当前运行的 core 目录(两个常见位置):
- 直接版本路径:
/var/www/ghost/versions/5.113.0/core/frontend/helpers/
- 或
current
快捷路径:/var/www/ghost/current/core/frontend/helpers/
用下面命令查看:
ls /var/www/ghost/versions
ls -l /var/www/ghost/current/core/frontend/helpers
三、把 helper 放到 core(完整代码)
创建文件:/var/www/ghost/versions/5.113.0/core/frontend/helpers/random_post.js
或 /var/www/ghost/current/core/frontend/helpers/random_post.js
文件内容(可直接拷贝):
const Handlebars = require('handlebars');
module.exports = function random_post(options) {
try {
// 1) 优先使用传入的 posts({{#random_post posts=all_posts}})
let posts = options.hash && options.hash.posts ? options.hash.posts : null;
// 2) 回退:从模板上下文里抓
if (!posts && options.data) {
const root = options.data.root || {};
const site = options.data.site || {};
posts = root.posts || site.posts || (root.site && root.site.posts) || [];
}
// 3) 兼容 Bookshelf collection(.models)
if (posts && posts.models && Array.isArray(posts.models)) {
posts = posts.models.map(m => (m && typeof m.toJSON === 'function') ? m.toJSON() : m);
}
// 4) 转数组(若不是数组)
if (posts && !Array.isArray(posts)) {
try { posts = Array.from(posts); } catch (e) { posts = Object.values(posts || {}); }
}
if (!posts || posts.length === 0) return '';
// 支持 limit 参数(默认 1)
const limit = Math.max(1, parseInt(options.hash.limit, 10) || 1);
// Fisher-Yates 洗牌
let shuffled = posts.slice();
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
// 取前 N 项
const selected = shuffled.slice(0, Math.min(limit, shuffled.length));
// 渲染每项,并手动注入 @index/@first/@last
let result = '';
selected.forEach((post, i) => {
const ctx = (post && typeof post.toJSON === 'function') ? post.toJSON() : post;
let data = Handlebars.createFrame(options.data);
data.index = i;
data.first = (i === 0);
data.last = (i === selected.length - 1);
result += options.fn(ctx, { data });
});
return result;
} catch (e) {
try { console.error('random_post helper error:', e); } catch (_) {}
return '';
}
};
四、重启 Ghost(使 helper 生效)
在 Ghost 安装根目录执行:
cd /var/www/ghost
# 以 ghost-cli 管理的情况下
ghost restart
如果需要以特定用户运行(视你服务器配置):
sudo -u <ghost-user> ghost restart
五、测试 helper 是否被加载
先用最小测试确认加载(先将文件临时改为):
module.exports = function random_post() {
return 'HELLO_HELPER';
};
- 重启 Ghost,刷新页面,查看是否出现
HELLO_HELPER
(或在页面源代码中查找)。如果出现,说明 helper 已加载成功。
把文件恢复成完整版本后继续下一步。
六、在模板中的使用(推荐:先用 get
再传 posts)
随机 5 篇(推荐写法)
{{#get "posts" limit="all" include="authors,tags" as |all_posts|}}
{{#random_post posts=all_posts limit=5}}
<div class="related-post {{#if @last}}last{{/if}}">
<a href="{{url}}">{{title}}</a>
</div>
{{/random_post}}
{{/get}}
说明:
{{#get}}
确保我们拿到全站文章数组并把它传入 helper(这保证在任意页面都可用)。- 在 helper 里我们已经手动传入
@index
/@first
/@last
,模板中可以使用{{#if @last}}
等。
在文章页排除当前文章(示例)
{{#get "posts" limit="all" filter="id:-{{id}}" as |all_posts|}}
{{#random_post posts=all_posts limit=3}}
<a href="{{url}}">{{title}}</a>
{{/random_post}}
{{/get}}
filter="id:-{{id}}"
用来排除当前文章(避免推荐自己)。
七、常见问题与排查
- 放在主题下无效:主题目录下的
helpers/
文件不会被 Ghost core 自动加载 —— 必须放到 core helpers。 - 权限问题:确保文件属主/权限正确,例如
sudo chown <ghost-user>:<ghost-user> path
、chmod 644
。 - 升级后失效:Ghost 升级会生成新版本目录,你需要把 helper 复制到新的
versions/X.Y.Z/core/frontend/helpers/
或使用current
的路径(将 helper 放在current
的同一路径下会更方便,但升级时仍需确认)。 - 托管版限制:若你使用 Ghost(Pro),你无法修改 core,这种情况请改用“服务端定时脚本写 partial”或其它代理层方案(我可提供脚本)。
- gscan / 主题校验:自定义 core helper 不影响 theme 的 gscan,但如果你在主题里引用了尚未注册的 helper,gscan 可能会报错 ——确保在部署主题前已在 core 注册 helper。
八、备选方案(当你没法改 core)
如果你不能修改 core(例如 Ghost(Pro)),使用 定时脚本 + Content API + 写 partial 是可行替代:
- 写一个脚本定期(如每 30 分钟)用 Content API 拉取所有文章并随机选若干篇,生成
content/themes/your-theme/partials/random.hbs
内容; - 模板中
{{> "random"}}
即可渲染。优点:不改 core、SEO 好;缺点:需要一个定时任务和 Content API key。