在 Hugo 复刻 Bear Blog 的 Upvote Button
我的博客最开始使用的是 Hugo Bear Blog 这个主题,它移植于 Bear Blog,我注意到 Hugo Bear Blog 的 GitHub 仓库里 第一个 Issue 就是 Add »Toast this post«-button,这是想复刻 Bear Blog 的文章点赞功能,感兴趣可以访问 Bear Blog 创始人 Herman 的博客 看看效果。
这个 Issue 时隔 4 年多都没有被解决,所以我决定一试。
通过一番检索,我发现了 Emaction 和 hugo-cf-worker 这两个项目,它们都是给博客文章增加类似点赞的功能,并且都使用了 Cloudflare Workers + D1,我也打算采用类似的思路。
接口设计
我设计了两个接口:
- 接口 A:当用户访问文章时,返回该文章的点赞数量、该用户对该文章的点赞状态
- 接口 B:当用户对文档点赞时,上报一条点赞记录,返回该文章的最新点赞数量、该用户对该文章的最新点赞状态
由于接口非常简单,数据结构也非常简单,使用 Cloudflare D1 多少有点大材小用了,使用 Cloudflare KV 则刚刚好,所以我设计了两个 KV 空间:
KV 空间 | 作用 | key | value |
---|---|---|---|
UPVOTE_COUNT |
存储每篇文章的点赞数量 | count:${postId} |
点赞数量(Int ) |
UPVOTE_RECORD |
存储每一条点赞记录 | upvote:${postId}:${ip} |
点赞操作(Int ,只记录 0 或 1) |
因为需要记录用户对文章的点赞状态,就需要能够识别用户,考虑到用户隐私以及实现复杂性,我选择了最简单但是不稳定的 IP 作为用户标识,其实也够用了~
设计好接口后就可以编写 Workers 代码了,代码相对简单,并且 KV 空间的操作语法也是简单到极致(至少比数据库操作省事很多),就一个 .get()
和 .put()
方法就够了。
具体的代码我已经上传到 GitHub,并且附带了详细的部署指南。
前端接入
开发好接口后还需要在主题前端中接入接口,首先在主题用来渲染文章页面的 HTML 代码中插入一个点赞按钮:
<div class="upvote-container">
<small class="upvote">
<button class="upvote-btn" id="upvote-btn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1">
<polyline points="17 11 12 6 7 11"></polyline>
<polyline points="17 18 12 13 7 18"></polyline>
</svg>
<span class="upvote-count" id="upvote-count">0</span>
</button>
</small>
</div>
然后直接在这个 HTML 中引入一个 JavaScript 脚本来调用接口并根据接口返回结果更新 DOM 元素即可,比如:
// 获取 Upvote 数量的方法,支持设置重试次数,默认不重试
async function getCount(slug, retryCount = 0) {
try {
const response = await fetch('{{ .Site.Params.upvoteURL }}count?post=' + slug, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (data.code === 0) {
const count = data.data.count;
upvoteCount.innerText = count;
hasUpvoted = data.data.hasUpvoted;
if (hasUpvoted) {
upvoteBtn.classList.add('upvoted');
} else {
upvoteBtn.classList.remove('upvoted');
}
} else {
console.error('Failed to get upvote count: ', data.msg);
}
} catch (error) {
console.error('Error: ', error);
if (retryCount > 0) {
setTimeout(() => {
getCount(slug, retryCount - 1);
}, 1000);
}
}
}
完整代码依然已上传至 GitHub。
结
感觉这个项目的后端(接口)部分的方案超适合做一些有趣的 API 服务,Cloudflare 真好用!
我已经将这个功能整合到 Hugo Bear Neo 这个主题中了,这个主题的目标依然是与 Bear Blog(以及 Hugo Bear Blog)尽可能保持一致,但提供更丰富的功能,且所有功能均可通过配置开启或关闭,我还会尽可能遵守 Bear Blog 的理念 来维护这个主题。
如果你也在使用 Hugo 博客框架,欢迎试试这个主题。