在 小白服务器踩坑(1)中,我们成功部署网站上线,现在为网站实现留言板功能
实现原理
采用 Github Issues + CloudFlare Worker 实现,在 Worder 添加函数实现留言内容接收,并提交至 Github Issues,Github Issues 来存放留言数据,也方便网站显示留言记录
这样做的好处不需要数据库,也不需要部署后台服务,Github Issues 审核,分类,标签也方便管理留言
创建 Worker
Worker 只是一个函数,前端调用接口实际是执行这个函数,进入 cloudflare,选择计算机和AI/Workders和Pages创建应用程序,选择从 Hello World!开始

可以先部署程序,部署成功后修改程序。在程序详情页面,右上角点击编辑代码,添加提交留言的代码
php
const corsHeaders = {
"Access-Control-Allow-Origin": "*", // 本地 + 生产都能用
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
export default {
async fetch(request, env) {
// =========================
// 1️⃣ 处理 CORS 预检请求
// =========================
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: corsHeaders,
});
}
// =========================
// 2️⃣ GET:健康检查
// =========================
if (request.method === "GET") {
return new Response(
JSON.stringify({
status: "ok",
message: "Comment API is running",
}),
{
headers: {
"Content-Type": "application/json",
...corsHeaders,
},
}
);
}
// =========================
// 3️⃣ 只允许 POST
// =========================
if (request.method !== "POST") {
return new Response("Method Not Allowed", {
status: 405,
headers: corsHeaders,
});
}
// =========================
// 4️⃣ 正常处理留言
// =========================
let data;
try {
data = await request.json();
} catch {
return new Response("Invalid JSON", {
status: 400,
headers: corsHeaders,
});
}
const { content } = data;
if (!content) {
return new Response("Content is required", {
status: 400,
headers: corsHeaders,
});
}
// -------------------------
// 创建 GitHub Issue
// -------------------------
const issueBody = content;
const res = await fetch(
`https://api.github.com/repos/${env.GITHUB_REPO}/issues`,
{
method: "POST",
headers: {
Authorization: `Bearer ${env.GITHUB_TOKEN}`,
"Content-Type": "application/json",
"User-Agent": "CF-Worker",
},
body: JSON.stringify({
title: "用户留言",
body: issueBody,
labels: ["pending"],
}),
}
);
if (!res.ok) {
const err = await res.text();
return new Response(err, {
status: 500,
headers: corsHeaders,
});
}
// -------------------------
// 发送通知(Telegram 或邮件)
// -------------------------
try {
// Telegram 机器人通知
if (env.TELEGRAM_BOT_TOKEN && env.TELEGRAM_CHAT_ID) {
const telegramMessage = `📝 新留言\n\n${content}`;
await fetch(
`https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chat_id: env.TELEGRAM_CHAT_ID,
text: telegramMessage,
}),
}
);
}
// 邮件通知(使用 SendGrid)
if (env.SENDGRID_API_KEY && env.NOTIFICATION_EMAIL) {
await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
Authorization: `Bearer ${env.SENDGRID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
personalizations: [
{
to: [{ email: env.NOTIFICATION_EMAIL }],
subject: "新留言通知",
},
],
from: { email: env.FROM_EMAIL || env.NOTIFICATION_EMAIL },
content: [
{
type: "text/plain",
value: content,
},
],
}),
});
}
} catch (notifyError) {
// 通知失败不影响主流程,只记录错误
console.error("通知发送失败:", notifyError);
}
return new Response(
JSON.stringify({ success: true }),
{
headers: {
"Content-Type": "application/json",
...corsHeaders,
},
}
);
},
};
机器人和邮箱通知可选,因为要用我们自己的 Github 账号提交 Issues,还需要在 Worker 里配置变量

GITHUB_REPO是保存 Issues 的项目名,GITHUB_TOKEN需要在 github 生成 token
Github Token
登录 github,打开Settings/Developer settings,选择Personal access tokens/Token(classic),

点击Generate new token/Generate new token(classic),填写 token 备注,选择有效时长,不需要勾选其他权限,创建 token,复制 token 到上一步设置的GITHUB_TOKEN变量
前端提交
前端核心就是调用 cloudflare 创建的 worker api 地址
php
const response = await fetch("https://comment.staryou.workers.dev", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
content: content,
}),
});
前端调用 api 成功,worker 获取前端提交 content 内容,将 issues 状态设为 pending(github 自行设定),我们可以修改 issues 状态,方便前端筛选留言
前端调用留言 api,这样就实现了留言过滤和分页显示
perl
https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/issues?labels=approved&state=open&per_page=${ISSUES_PER_PAGE}&page=${page}
其他
worker 中仓库名配置错误,前端控制台报错

生成 github token 最好选择 classic,避免权限问题
