基于 Cloudflare Workers + Telegram Bot + D1 的双向匿名聊天系统完整实现
2565 字
13 分钟
基于 Cloudflare Workers + Telegram Bot + D1 的双向匿名聊天系统完整实现
在本篇文章中,我将分享一个基于 Cloudflare Workers + Telegram Bot + D1 数据库 的完全无服务器(Serverless)聊天系统。该机器人支持用户匿名与管理员双向通信,具备首访验证、自动诈骗检测、屏蔽管理、消息映射等功能,是一个非常适合作为客服、匿名反馈、小型 IM 系统的解决方案。
⭐ 系统亮点
- 🚀 100% Serverless 无需服务器
- 🔐 首访用户自动加减法验证(防骚扰)
- 🔁 双向匿名对话(用户看不到管理员 ID)
- 🛡 管理员可屏蔽 / 解封任何用户
- 🧠 自动诈骗账号检测(基于 D1 fraud 表)
- 💾 D1 数据库存储用户状态与消息映射
- 📌 管理员收到用户消息时自动生成“用户资料卡”
- 🔔 可控频率的通知提醒机制
🧩 系统架构
[Telegram User] ↓ 发送消息[Cloudflare Workers /endpoint] ↓[验证系统(加减法 1~10)] ↓ 通过后[反诈检测(fraud)] ↓[自动转发消息 → 管理员]
管理员 ←→ 用户 (双向聊天)📦 使用技术
| 技术 | 用途 |
|---|---|
| Cloudflare Workers | 核心逻辑执行环境 |
| Telegram Bot API | 双向聊天接口 |
| Cloudflare D1 | 用户资料、屏蔽状态、验证码、消息映射存储 |
| Webhooks | 实时接收 Telegram 更新事件 |
🗄 数据库结构(D1)
为保证机器人功能完整,需要创建以下表:
CREATE TABLE blocks ( user_id TEXT PRIMARY KEY, is_blocked INTEGER);
CREATE TABLE msg_map ( admin_msg_id TEXT PRIMARY KEY, user_id TEXT);
CREATE TABLE lastmsg ( user_id TEXT PRIMARY KEY, ts INTEGER);
CREATE TABLE fraud ( user_id TEXT PRIMARY KEY);
CREATE TABLE verify ( user_id TEXT PRIMARY KEY, question TEXT, answer INTEGER, passed INTEGER);
CREATE TABLE user_first ( user_id TEXT PRIMARY KEY);🔢 首次验证系统(加减法 1~10)
为了防止广告机器人、骚扰用户,系统会对第一次聊天的用户进行简单的数学验证:
✨ 验证示例
为了防止骚扰,请先完成验证:
7 + 3 = ?
请直接回复数字答案。答对后自动进入聊天流程;答错则重新生成题目。
🎯 核心功能说明
1️⃣ 匿名聊天(Forward/Copy)
用户发送消息 → 机器人自动转发给管理员
管理员「回复这条转发消息」→ 机器人将回复复制给用户
整个过程:
- 用户不会看到管理员的 UID
- 管理员同样看不到用户昵称(除资料卡)
实现完整“匿名双向通信”。
2️⃣ 用户资料卡(首次出现)
管理员第一次收到用户消息时,自动生成用户信息卡:

管理员点按钮后 → 自动屏蔽
3️⃣ 屏蔽 / 解除屏蔽
管理员可直接通过命令控制:
| 指令 | 说明 |
|---|---|
/block | 屏蔽此用户 |
/unblock | 解除屏蔽 |
/checkblock | 查看用户是否被屏蔽 |
4️⃣ 诈骗检测(D1 fraud 表)
只需点击按钮

或者

回复的是输入/block 也可以拉黑用户
5️⃣ 可控间隔的通知提醒系统
为了避免管理员被刷屏,可设置提醒间隔(默认 1 小时):
作用:
- 用户第一次联系 → 提醒
- 用户长时间没聊天,再次联系 → 再提醒一次
- 保证管理员不会被Spam
🛠 Worker 代码结构说明
代码整体分为:
- Webhook 接收器
- Telegram API 封装
- 核心消息处理
- 加减法验证模块
- 管理员控制命令
- D1 数据访问模块
- 首次资料卡发送模块(可扩展)
该架构清晰、可维护性高、支持未来扩展。
📚 使用教程
1. 创建 Telegram Bot
在 @BotFather 创建一个机器人,并获得:
- Bot Token

2. 部署到 Cloudflare Workers
// ================== 常量区 ==================
const START_MESSAGE = `🤖 你好,欢迎使用 @guaguawa_bot 双向聊天机器人!请通过此机器人发送您的消息与我联系,我会立即为您传达。`;
const START_MESSAGE_ADMIN = `🤖 <b>关于本机器人</b>
本机器人用于 <b>双向匿名聊天</b>,旨在提高用户的沟通体验,并防止骚扰。
📌 <b>功能介绍:</b>- 匿名聊天:用户可以自由交流,管理员可管理消息流转。- 防骚扰功能:管理员可使用 /block 指令屏蔽不良用户。- 诈骗检测:自动检测是否属于诈骗用户,并提醒管理员。
📜 <b>可用命令:</b>- /start 获取欢迎信息- /block 屏蔽用户(管理员)- /unblock 解除屏蔽(管理员)- /checkblock 查询用户状态(管理员)
🔹 机器人由 <b>匿名聊天管理系统</b> 提供支持。`;
// ================== Worker 入口(ESM 格式) ==================
export default { async fetch(request, env, ctx) { const url = new URL(request.url);
if (url.pathname === "/endpoint") { return handleWebhook(request, env, ctx); } if (url.pathname === "/registerWebhook") { return registerWebhook(request, env); } if (url.pathname === "/unRegisterWebhook") { return unRegisterWebhook(request, env); }
return new Response("OK"); }};
// ===================== Telegram API 封装 ========================
function apiUrl(methodName, params = null, env) { let query = params ? "?" + new URLSearchParams(params).toString() : ""; return `https://api.telegram.org/bot${env.ENV_BOT_TOKEN}/${methodName}${query}`;}
function requestTelegram(method, body, env, params = null) { return fetch(apiUrl(method, params, env), body).then(r => r.json());}
const makeReq = (body) => ({ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body)});
const sendMessage = (msg, env) => requestTelegram("sendMessage", makeReq(msg), env);
const forwardMessage = (msg, env) => requestTelegram("forwardMessage", makeReq(msg), env);
const copyMessage = (msg, env) => requestTelegram("copyMessage", makeReq(msg), env);
// ===================== Webhook ========================
async function handleWebhook(request, env, ctx) { if (request.headers.get("X-Telegram-Bot-Api-Secret-Token") !== env.ENV_BOT_SECRET) { return new Response("Unauthorized", { status: 403 }); }
const update = await request.json(); ctx.waitUntil(onUpdate(update, env)); return new Response("OK");}
// ===================== Update Routing ========================
async function onUpdate(update, env) { if (update.message) { await onMessage(update.message, env); } if (update.callback_query) { await onCallback(update.callback_query, env); }}
// ===================== D1 数据库封装 ========================
// 拉黑async function getBlock(env, userId) { const row = await env.TG_BOT_DB.prepare("SELECT is_blocked FROM blocks WHERE user_id=?").bind(userId).first(); return row ? row.is_blocked === 1 : false;}
async function setBlock(env, userId, val) { await env.TG_BOT_DB.prepare("INSERT OR REPLACE INTO blocks (user_id, is_blocked) VALUES (?, ?)").bind(userId, val ? 1 : 0).run();}
// 消息映射async function getMsgMap(env, adminMsgId) { const row = await env.TG_BOT_DB.prepare("SELECT user_id FROM msg_map WHERE admin_msg_id=?").bind(adminMsgId).first(); return row ? row.user_id : null;}
async function setMsgMap(env, adminMsgId, userId) { await env.TG_BOT_DB.prepare("INSERT OR REPLACE INTO msg_map (admin_msg_id, user_id) VALUES (?, ?)").bind(adminMsgId, userId).run();}
// 通知节流async function getLastMsg(env, userId) { const row = await env.TG_BOT_DB.prepare("SELECT ts FROM lastmsg WHERE user_id=?").bind(userId).first(); return row ? row.ts : null;}
async function setLastMsg(env, userId, ts) { await env.TG_BOT_DB.prepare("INSERT OR REPLACE INTO lastmsg (user_id, ts) VALUES (?, ?)").bind(userId, ts).run();}
// 诈骗async function isFraud(env, userId) { const row = await env.TG_BOT_DB.prepare("SELECT user_id FROM fraud WHERE user_id=?").bind(userId).first(); return !!row;}
// ===================== 首次加减验证 ========================
function genVerifyQuestion() { let a = Math.floor(Math.random() * 10) + 1; let b = Math.floor(Math.random() * 10) + 1;
if (Math.random() < 0.5) { return { question: `${a} + ${b} = ?`, answer: a + b }; } else { if (a < b) [a, b] = [b, a]; return { question: `${a} - ${b} = ?`, answer: a - b }; }}
async function getVerify(env, userId) { return await env.TG_BOT_DB.prepare("SELECT * FROM verify WHERE user_id=?").bind(userId).first();}
async function setVerify(env, userId, question, answer) { await env.TG_BOT_DB.prepare("INSERT OR REPLACE INTO verify (user_id, question, answer, passed) VALUES (?, ?, ?, 0)").bind(userId, question, answer).run();}
async function passVerify(env, userId) { await env.TG_BOT_DB.prepare("UPDATE verify SET passed=1 WHERE user_id=?").bind(userId).run();}
// ===================== 消息主处理 ========================
async function onMessage(message, env) { const chatId = message.chat.id.toString(); const ADMIN_UID = env.ENV_ADMIN_UID;
// /start if (message.text === "/start") { return sendMessage({ chat_id: chatId, text: chatId === ADMIN_UID ? START_MESSAGE_ADMIN : START_MESSAGE, parse_mode: "HTML" }, env); }
// -------- 管理员 -------- if (chatId === ADMIN_UID) { return handleAdminMessage(message, env); }
// -------- 用户 -------- return handleGuestMessage(message, env);}
// ===================== 管理员逻辑 ========================
async function handleAdminMessage(message, env) { const ADMIN_UID = env.ENV_ADMIN_UID;
if (!message.reply_to_message?.message_id) { return sendMessage({ chat_id: ADMIN_UID, text: START_MESSAGE_ADMIN, parse_mode: "HTML" }, env); }
const guestId = await getMsgMap(env, message.reply_to_message.message_id.toString());
if (!guestId) { return sendMessage({ chat_id: ADMIN_UID, text: "无法找到用户映射" }, env); }
// 管理员命令 if (message.text === "/block") return handleBlock(message, env); if (message.text === "/unblock") return handleUnblock(message, env); if (message.text === "/checkblock") return handleCheckBlock(message, env);
// 正常回复 return copyMessage({ chat_id: guestId, from_chat_id: ADMIN_UID, message_id: message.message_id }, env);}
// ===================== 用户逻辑(含验证 + 资料卡) ========================
async function handleGuestMessage(message, env) { const chatId = message.chat.id.toString(); const ADMIN_UID = env.ENV_ADMIN_UID;
// --- 1. 验证 --- const verify = await getVerify(env, chatId);
if (!verify) { const q = genVerifyQuestion(); await setVerify(env, chatId, q.question, q.answer);
return sendMessage({ chat_id: chatId, text: `为了防止骚扰,请先完成验证:\n\n<b>${q.question}</b>`, parse_mode: "HTML" }, env); }
if (verify.passed !== 1) { const ans = parseInt(message.text);
if (ans === verify.answer) { await passVerify(env, chatId);
return sendMessage({ chat_id: chatId, text: "验证通过!你现在可以给管理员发送消息了 🎉" }, env); }
const q = genVerifyQuestion(); await setVerify(env, chatId, q.question, q.answer);
return sendMessage({ chat_id: chatId, text: `❌ 错误,请重新验证:\n\n<b>${q.question}</b>`, parse_mode: "HTML" }, env); }
// --- 2. 是否被屏蔽 --- if (await getBlock(env, chatId)) { return sendMessage({ chat_id: chatId, text: "🚫 你被拉黑了" }, env); }
// --- 3. 首次与管理员建立联系 → 发送资料卡 --- const firstRow = await env.TG_BOT_DB.prepare("SELECT user_id FROM user_first WHERE user_id=?").bind(chatId).first();
if (!firstRow) { await env.TG_BOT_DB.prepare("INSERT INTO user_first (user_id) VALUES (?)").bind(chatId).run();
const fullname = (message.from.first_name || "") + (message.from.last_name ? " " + message.from.last_name : ""); const username = message.from.username ? "@" + message.from.username : "无";
const now = new Date(); const timeDisplay = now.toLocaleString("zh-CN", { hour12: false });
const userCard = `<b>用户资料卡</b>---• 昵称名称: <b>${fullname}</b>• 用户名: <b>${username}</b>• ID: <code>${chatId}</code>• 首次连接时间: <code>${timeDisplay}</code>`;
const blockKeyboard = { inline_keyboard: [ [{ text: "🚫 屏蔽此人 (Block)", callback_data: `block:${chatId}` }] ] };
await sendMessage({ chat_id: ADMIN_UID, text: userCard, parse_mode: "HTML", reply_markup: blockKeyboard }, env); }
// --- 4. 转发消息 --- const fw = await forwardMessage({ chat_id: ADMIN_UID, from_chat_id: chatId, message_id: message.message_id }, env);
if (fw.ok) { await setMsgMap(env, fw.result.message_id.toString(), chatId); }}
// ===================== 回调按钮(屏蔽) ========================
async function onCallback(query, env) { const ADMIN_UID = env.ENV_ADMIN_UID;
if (query.from.id.toString() !== ADMIN_UID) { return; }
const data = query.data;
if (data.startsWith("block:")) { const userId = data.split(":")[1];
await setBlock(env, userId, true);
await requestTelegram( "answerCallbackQuery", makeReq({ callback_query_id: query.id, text: "已拉黑该用户" }), env );
await requestTelegram( "editMessageReplyMarkup", makeReq({ chat_id: query.message.chat.id, message_id: query.message.message_id, reply_markup: { inline_keyboard: [[{ text: "✔ 已拉黑", callback_data: "noop" }]] } }), env ); }}
// ===================== 管理员命令 ========================
async function handleBlock(message, env) { const guest = await getMsgMap(env, message.reply_to_message.message_id.toString());
await setBlock(env, guest, true); return sendMessage({ chat_id: env.ENV_ADMIN_UID, text: `UID:${guest} 已屏蔽` }, env);}
async function handleUnblock(message, env) { const guest = await getMsgMap(env, message.reply_to_message.message_id.toString());
await setBlock(env, guest, false); return sendMessage({ chat_id: env.ENV_ADMIN_UID, text: `UID:${guest} 已解除屏蔽` }, env);}
async function handleCheckBlock(message, env) { const guest = await getMsgMap(env, message.reply_to_message.message_id.toString()); const blocked = await getBlock(env, guest);
return sendMessage({ chat_id: env.ENV_ADMIN_UID, text: `UID:${guest} → ${blocked ? "已屏蔽" : "未屏蔽"}` }, env);}
// ===================== Webhook 控制 ========================
async function registerWebhook(request, env) { const url = new URL(request.url); const webhookUrl = `${url.protocol}//${url.hostname}/endpoint`;
const res = await fetch(apiUrl("setWebhook", { url: webhookUrl, secret_token: env.ENV_BOT_SECRET }, env)).then(r => r.json()); return new Response(res.ok ? "OK" : JSON.stringify(res));}
async function unRegisterWebhook(request, env) { const res = await fetch(apiUrl("setWebhook", { url: "" }, env)).then(r => r.json()); return new Response(res.ok ? "OK" : JSON.stringify(res));}


然后把上面的代码全部复制到里面去.
3. 绑定 D1 数据库


D1的key为
4. 设置环境变量
| 变量 | 说明 |
|---|---|
ENV_BOT_TOKEN | Telegram Bot Token |
ENV_ADMIN_UID | 管理员 UID |
ENV_BOT_SECRET | Webhook secret(可随意生成) |

- 从@BotFather获取token,并且可以发送
/setjoingroups来禁止此Bot被添加到群组 - 从uuidgenerator获取一个随机uuid作为secret
- 从@username_to_id_bot获取你的用户id
- 配置worker的变量
- 增加一个
ENV_BOT_TOKEN变量,数值为从步骤1中获得的token - 增加一个
ENV_BOT_SECRET变量,数值为从步骤2中获得的secret - 增加一个
ENV_ADMIN_UID变量,数值为从步骤3中获得的用户id
- 增加一个
5. 注册 Webhook
访问:
https://你的worker域名/registerWebhook6. 测试
测试Bot: @guaguawa_bot
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
基于 Cloudflare Workers + Telegram Bot + D1 的双向匿名聊天系统完整实现
https://muxui.com/posts/基于-cloudflare-workers-telegram-bot-d1-的双向匿名聊天系统完整实现/相关文章智能推荐
1
从 WordPress 到 Astro迁移实战全记录
文章记录将 Muxui 从 WordPress(B2 Pro)完整迁移到 Astro 静态博客主题 Firefly 的全过程:导出 WXR、HTML 转 Markdown、图床改写、友链与导航定制,以及迁移中踩过的坑。
2
YOLO训练麻将(Mahjong)识别,导出ONNX
文章本文基于 Roboflow Universe 数据集(例如 $1 )与仓库内脚本,在 Windows 上完成训练,并导出 ONNX 供后端推理。下文配图均为本机实测截图,便于对照。快速体验 小程序 1\. 环境要求 \ Windows 10
3
自建 Headscale + DERP 全流程实战记录
文章记录一次完整、可上线、可长期运行的 Headscale + 自建 DERP 搭建过程。 本文不是“能跑就行”的教程,而是 生产可用、已多客户端验证 的配置方案。 简介 Tailscale(Headscale)就是组建一个大的局域网,可以将你
4
Mac中磁盘没有正常推出的解决方案
文章由于mac电脑关机或者没有实现正常推出硬盘导致的磁盘没有正常推出。 解决办法 : 1)打开终端,输入如下命令查看一下硬盘的信息,确定一下自己的磁盘是否可以显示: diskutil list 在终端的最后一行显示了我硬盘的名字,因此 /dev
5
白嫖 .com .net .jp .shop .xyz 等顶级域名
文章1.$1 日本IP 点开活动地址之后搜索你想要的域名 注意全程日本IP 身份生成:$1 2.付款选择 $1 输入你的卡信息 卡需要支持3ds 3.看到 您的申请已被接受。 来这里 $1 登录你的账户,账号是右上角的数字 。在邮箱里也可以看到
随机文章随机推荐













