在 Ghost(v5版本)中通过修改 core 实现模板随机文章推荐

作者: dreamfly 分类: 个人博客 发布时间: 2025-09-17 10:43

要点:自定义 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)。


二、准备工作(备份 + 路径定位)

  1. 先备份 core(非常重要):
cd /var/www/ghost
cp -a versions/5.113.0/core versions/5.113.0/core.bak
  1. 找到当前运行的 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> pathchmod 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。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!