很多个人站长都有这样的困扰:网站是纯静态的,想加评论功能但不想自己搭服务器。市面上的评论系统要么需要后端,要么要绑银行卡,对纯静态站点很不友好。
今天教大家一个完全免费的方案:用 Cloudflare D1 + Workers 搭建评论系统,不需要服务器,不需要绑卡,配置一次终身使用。
为什么选择这个方案?
在开始之前,先说说为什么选择 Cloudflare 全家桶:
- 完全免费:D1 数据库免费 5GB 存储,Workers 免费计划每天约 10 万次请求
- 无需服务器:纯静态网站也能拥有后端能力
- 无需绑卡:不像 Vercel、Netlify 需要绑信用卡
- 全球加速:Cloudflare CDN 遍布全球,访问速度快
- 数据自主:评论数据存在自己的数据库里,完全可控
整体架构
这套评论系统由三部分组成:
- Cloudflare D1:SQLite 数据库,存储评论数据
- Cloudflare Workers:处理 API 请求(增删改查)
- 前端页面:原生 JavaScript,调用 API 实现评论功能
工作流程很简单:用户提交评论 → 前端调用 Worker API → Worker 写入 D1 数据库 → 其他用户访问时从 D1 读取评论列表。
第一步:创建 D1 数据库
登录 Cloudflare Dashboard,按照以下步骤操作:
- 左侧菜单找到 Workers & Pages
- 点击 D1 SQL Database
- 点击 Create database
- 输入数据库名称
comment-db,点击创建
数据库创建完成后,需要初始化表结构。点击进入数据库,选择 Console,执行以下 SQL:
CREATE TABLE IF NOT EXISTS Comment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nick TEXT NOT NULL,
mail TEXT,
link TEXT,
comment TEXT NOT NULL,
url TEXT NOT NULL,
likes INTEGER DEFAULT 0,
parentId INTEGER DEFAULT NULL,
createdAt TEXT DEFAULT (datetime('now')),
updatedAt TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_comment_url ON Comment(url);
CREATE INDEX IF NOT EXISTS idx_comment_parent ON Comment(parentId);
CREATE INDEX IF NOT EXISTS idx_comment_created ON Comment(createdAt);
这个表结构包含以下字段:
id:评论 ID,自增主键nick:评论者昵称mail:邮箱(可选)link:网站链接(可选)comment:评论内容url:页面路径,用于区分不同文章的评论likes:点赞数parentId:父评论 ID,用于实现回复功能createdAt:创建时间updatedAt:更新时间
第二步:创建 Worker
回到 Workers & Pages 页面,点击 Create application,选择 Create Worker,输入名称
worker-api,点击部署。
创建完成后,点击 Edit code,将以下代码复制进去:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const method = request.method;
// CORS 预检请求
if (method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
});
}
// 获取评论列表
if (method === 'GET' && url.pathname === '/api/comment') {
const pageUrl = url.searchParams.get('url');
if (!pageUrl) {
return new Response(JSON.stringify({ error: '缺少 url 参数' }), {
status: 400,
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
const { results } = await env.DB.prepare(
'SELECT * FROM Comment WHERE url = ? AND (parentId IS NULL OR parentId = 0) ORDER BY likes DESC, createdAt DESC'
).bind(pageUrl).all();
const replies = await env.DB.prepare(
'SELECT * FROM Comment WHERE url = ? AND parentId > 0 ORDER BY createdAt ASC'
).bind(pageUrl).all();
return new Response(JSON.stringify({ data: results, replies: replies.results || [] }), {
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
// 提交评论
if (method === 'POST' && url.pathname === '/api/comment') {
const data = await request.json();
if (!data.nick || !data.comment || !data.url) {
return new Response(JSON.stringify({ error: '缺少必要参数' }), {
status: 400,
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
await env.DB.prepare(
'INSERT INTO Comment (nick, mail, link, comment, url, parentId) VALUES (?, ?, ?, ?, ?, ?)'
).bind(data.nick, data.mail || '', data.link || '', data.comment, data.url, data.parentId || null).run();
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
// 点赞
if (method === 'POST' && url.pathname === '/api/comment/like') {
const data = await request.json();
const result = await env.DB.prepare(
'UPDATE Comment SET likes = likes + 1 WHERE id = ? RETURNING likes'
).bind(data.commentId).first();
return new Response(JSON.stringify({ success: true, likes: result?.likes || 1 }), {
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
// 删除评论(需要管理员令牌)
if (method === 'DELETE' && url.pathname.startsWith('/api/comment/')) {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.includes(env.ADMIN_TOKEN)) {
return new Response(JSON.stringify({ error: '未授权' }), {
status: 401,
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
const commentId = url.pathname.split('/').pop();
await env.DB.prepare('DELETE FROM Comment WHERE id = ?').bind(commentId).run();
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
return new Response('Comment API Server - Powered by Cloudflare D1');
}
}
点击保存并部署。
第三步:绑定数据库
Worker 创建好了,但还需要绑定数据库才能访问。操作步骤:
- 在 Worker 页面点击 Settings
- 找到 D1 Database
- 点击 Add binding
- 变量名填写
DB - 选择我们创建的
comment-db数据库
还需要添加一个管理员令牌,用于删除评论:
- 在 Worker 页面点击 Settings
- 找到 Environment Variables 或 Variables
- 点击 Add variable
- 变量名填写
ADMIN_TOKEN - 值填写你的管理密码(可以是任意字符串)
- 注意:这个变量要选 Encrypt(加密)
现在 API 应该可以访问了,地址是:https://worker-api.dygnjdtdq9.workers.dev(请把域名换成你自己的)
第四步:前端集成
后端完成了,现在需要在网页上添加评论功能。我已经封装好了完整的 JavaScript 和 CSS,你只需要在页面中引用即可。
评论系统的文件已经放在 articles/assets/ 目录下:
comment-system.css- 评论样式文件comment-system.js- 评论功能 JavaScript
在网页的
中添加 CSS 引用,在 </body> 之前添加 JS 引用并初始化即可。
具体集成方式可以参考项目中已有的评论系统实现,这是最简单的复用方式。
功能说明
这套评论系统实现了以下功能:
- 发表评论:只需昵称和内容即可评论
- 评论列表:优先按点赞数排序,其次按时间倒序
- 点赞功能:本地记录防止重复点赞
- 回复功能:支持对评论进行回复
- 时间格式化:显示"刚刚"、"X分钟前"等友好格式
- 字符计数:评论限制 500 字,回复限制 300 字
- 响应式设计:完美适配手机和电脑
多站点共用
这套系统的最大优势是可以多个站点共用同一个 API。不同站点的评论会通过 URL 路径自动区分。
例如:
/articles/post/xxx.html→ 小宝科技站的评论/game/xxx.html→ 小宝游戏站的评论/astrology/xxx.html→ 小宝命理站的评论
只需要在前端调用时使用 location.pathname 作为 URL 参数即可。
成本分析
这套方案完全免费!来看看 Cloudflare 的免费额度:
- D1 数据库:免费 5GB 存储,个人站点完全够用
- Workers:免费计划每天约 10 万次请求
- CDN 流量:官方标称带宽不单独计量,个人博客场景基本够用
对于个人博客来说,这些额度绰绰有余。
总结
用 Cloudflare D1 + Workers 搭建评论系统,是一个零成本、零服务器、零绑卡的方案。只需要:
- 创建一个 D1 数据库
- 创建一个 Worker 并绑定数据库
- 在前端添加几行代码
就能拥有完整的评论功能,而且数据完全自主可控。如果你也是纯静态网站,强烈推荐试试这个方案!
有问题欢迎在评论区留言~
加载评论中...