Supabase Edge Functions 实战:用 slack-bot-mention 构建 Slack 机器人 @提及响应
导读
本文以 Supabase 官方示例仓库中的 slack-bot-mention 文档 为主线,讲解如何基于 Supabase Edge Functions 编写一个处理 Slack @提及(app_mention)事件并自动回复的机器人。文中会覆盖 Slack App 事件订阅的完整配置流程、SLACK_TOKEN 密钥注入方式、url_verification 与 app_mention 两类事件的请求处理逻辑,并结合仓库内 index.ts 与 config.toml 的真实配置做源码级佐证。读完本文你将可以直接把这段函数代码部署上线,并根据自己的业务改写回复逻辑。
一、整体思路:为什么用 Edge Function 接收 Slack 事件
Slack 机器人的经典工作方式是:用户在频道或群里 @机器人 之后,Slack 通过「事件订阅(Event Subscriptions)」把 app_mention 事件以 HTTP POST 的形式推送到一个公网可达的 URL 上。这个 URL 既可以是你自建的后端服务,也可以是 Supabase Edge Function——Edge Function 天然提供了一个公网 HTTPS 端点,配合 示例仓库的 README 中描述的 CLI 部署方式,即可在数分钟内把一个 Deno 编写的机器人跑起来。
该示例函数需要处理 Slack 推送的两类请求:
url_verification(URL 校验):在 Slack App 后台配置事件订阅 URL 时,Slack 会发送一个带challenge字段的验证请求,函数必须原样把challenge返回,才能完成订阅配置。app_mention(@提及事件):用户在对话中@机器人后,Slack 推送包含user、text、channel、ts等字段的事件,函数调用 Slack Web API 回复消息。
二、配置 Slack App:事件订阅与 URL 校验
在部署任何代码之前,需要先在 Slack 侧完成 App 配置。按照 原文档 的步骤操作:
- 打开 Slack Apps 管理页面,创建或选择一个应用(建议使用 Bot User 类型,这样会得到以
xoxb-开头的 Bot Token)。 - 在 App 的 Event Subscriptions(事件订阅) 中开启订阅,并把
slack-bot-mention函数的 URL 填入 Request URL 输入框。 - 点击验证(Verify)按钮。此时 Slack 会立即向该 URL 发送一次
url_verification握手请求;Edge Function 会正确回显challenge字段,页面随即提示验证成功——这正是「函数已正确部署并可访问」的最直接确认。 - 在 Subscribe to bot events 中添加
app_mention事件,保存后机器人才能收到 @提及推送。
要点:URL 校验发生在你点击 Verify 的瞬间,所以必须先把函数部署上线(或通过
supabase functions serve本地暴露并经内网穿透工具转发)再执行第 3 步。
三、创建并部署 Edge Function
3.1 注入 SLACK_TOKEN 密钥
函数运行时代码会从环境变量中读取 Bot Token,因此部署前需要用 Supabase CLI 将其写入项目的密钥仓库。原文档给出的命令形式为:
supabase --project-ref <你的项目引用ID> secrets \
set SLACK_TOKEN=xoxb-0000000000-0000000000-01010101010nacho101010
--project-ref:目标 Supabase 项目的引用 ID(在项目 Dashboard 的 URL 中可以看到)。SLACK_TOKEN:值为 Slack Bot User OAuth Token(xoxb-开头),用于后续调用chat.postMessage发消息。- 更推荐的做法是准备一个
.env文件存放生产密钥,然后执行supabase secrets set --env-file ./supabase/.env.local批量写入,之后用supabase secrets list验证是否设置成功——这与 示例仓库 README 的 Deploy 一节 描述一致。
3.2 函数代码(原文档示例)
原文档提供了如下可直接部署的代码,你可以在收到 @提及后改写 text 的处理逻辑与回复内容:
import { WebClient } from 'https://deno.land/x/slack_web_api@6.7.2/mod.js'
const slackBotToken = Deno.env.get('SLACK_TOKEN') ?? ''
const botClient = new WebClient(slackBotToken)
console.log(`Slack URL verification function up and running!`)
Deno.serve(async (req) => {
try {
const reqBody = await req.json()
console.log(JSON.stringify(reqBody, null, 2))
const { token, challenge, type, event } = reqBody
if (type == 'url_verification') {
return new Response(JSON.stringify({ challenge }), {
headers: { 'Content-Type': 'application/json' },
status: 200,
})
} else if (event.type == 'app_mention') {
const { user, text, channel, ts } = event
// Here you should process the text received and return a response:
const response = await botClient.chat.postMessage({
channel: channel,
text: `Hello <@${user}>!`,
thread_ts: ts,
})
return new Response('ok', { status: 200 })
}
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { 'Content-Type': 'application/json' },
status: 500,
})
}
})
3.3 代码逐段拆解
模块导入与客户端初始化
import { WebClient } from 'https://deno.land/x/slack_web_api@6.7.2/mod.js'
const slackBotToken = Deno.env.get('SLACK_TOKEN') ?? ''
const botClient = new WebClient(slackBotToken)
Deno 支持直接通过 URL 导入第三方模块,此处引入的是社区版 Slack Web API 客户端。Deno.env.get('SLACK_TOKEN') 读取上一节通过 CLI 注入的密钥;使用 ?? '' 兜底是为了避免 Token 缺失时构造 WebClient 直接抛异常。
请求入口与日志
Deno.serve 是 Supabase Edge Functions(Deno Deploy 运行时)的标准 HTTP 服务入口,接收 Slack 推送的 POST 请求。console.log(JSON.stringify(reqBody, null, 2)) 会打印完整载荷——强烈建议第一次联调时保留这行日志,到函数日志面板里观察 Slack 实际推送的事件结构,能省去大量猜测。
url_verification 握手分支
if (type == 'url_verification') {
return new Response(JSON.stringify({ challenge }), {
headers: { 'Content-Type': 'application/json' },
status: 200,
})
}
Slack 验证 URL 所有权时发送的请求体形如 { "type": "url_verification", "challenge": "xxx" },函数必须把 challenge 原样包裹在 JSON 中返回。校验通过后 Slack 才会接受该 Request URL。
app_mention 事件处理分支
const { user, text, channel, ts } = event
const response = await botClient.chat.postMessage({
channel: channel,
text: `Hello <@${user}>!`,
thread_ts: ts,
})
user:触发 @提及的用户 ID。在 Slack 消息格式中,<@${user}>会被渲染成对用户的 @ 提及。channel:事件发生的频道 ID,回复消息必须发回同一频道。ts:触发事件的原始消息时间戳,把它作为thread_ts传入后,机器人会在触发消息的线程内回复,避免刷屏主频道。text:用户 @ 机器人的完整文本,是后续做指令解析(例如/help、/weather)的输入源。
异常兜底
catch 分支把所有异常统一封装为 {"error": message} 并返回 500。需要注意:如果函数抛错,Slack 会判定投递失败并按退避策略重试,所以对于「已进入处理流程但无需回复」的情况应返回 200。
四、源码纵览:当前仓库中的 Slack 机器人实现
原文档对应目录中的实际代码 slack-bot-mention/index.ts 在保持相同处理逻辑的前提下做了两个值得注意的演进,可作为你升级实现时的参考:
import { WebClient } from 'npm:@slack/web-api@^7'
import { withSupabase } from 'npm:@supabase/server@^1'
...
export default {
fetch: withSupabase({ auth: 'none' }, async (req, ctx) => {
try {
// Implement your Slack request signature verification here before trusting the payload
// (validate `x-slack-signature` / `x-slack-request-timestamp` with your signing secret).
const { challenge, type, event } = await req.json()
if (type == 'url_verification') {
return Response.json({ challenge })
} else if (event.type == 'app_mention') {
const { user, text, channel, ts } = event
await botClient.chat.postMessage({
channel: channel,
text: `Hello <@${user}>!`,
thread_ts: ts,
})
return new Response('ok', { status: 200 })
}
return Response.json({ ok: true })
} catch (error) {
return Response.json({ error: error.message }, { status: 500 })
}
}),
}
两点演进与对应说明:
- 包源从 deno.land 迁移到 npm 规范符:
npm:@slack/web-api@^7使用 Slack 官方维护的 Web API 客户端;入口改为export default { fetch: ... }并包裹withSupabase({ auth: 'none' }, ...)。这里显式声明auth: 'none'是因为该端点需要被 Slack 以匿名方式访问,与 config.toml 中的verify_jwt = false配置相呼应。 - 安全注释:仓库代码中的注释明确指出,在信任请求载荷之前,应实现 Slack 请求签名校验——即验证
x-slack-signature与x-slack-request-timestamp头。这属于生产部署必须补齐的一环(见下文第五节)。
五、部署与本地运行
5.1 关闭 JWT 校验
Slack 的请求不会携带 Supabase 签发的 JWT,因此必须在 config.toml 中为函数显式关闭 JWT 校验:
[functions.slack-bot-mention]
verify_jwt = false
verify_jwt 是 Supabase Edge Functions 的发布开关之一:设为 true 时函数只接受携带有效 JWT 的请求(用于保护内部 API),而 Slack/Stripe 等第三方 Webhook 回调场景一律设为 false(仓库内 stripe-webhooks、telegram-bot 等同理)。若本地调试使用 supabase functions serve,同样需要加 --no-verify-jwt 参数。
5.2 本地调试
- 启动本地环境:
supabase start(需 Docker daemon 运行中)。 - 为本地函数提供密钥并启动服务:
supabase functions serve slack-bot-mention --env-file ./supabase/.env.local --no-verify-jwt - 用 curl 模拟 Slack 的 URL 校验握手进行自测:
期望返回体为curl -X POST http://localhost:54321/functions/v1/slack-bot-mention \ -H 'Content-Type: application/json' \ -d '{"type":"url_verification","challenge":"test-challenge"}'{"challenge":"test-challenge"}。
5.3 部署上线
完整流程为:登录 CLI(supabase login)→ 关联项目(supabase link --project-ref <project-ref>)→ 注入密钥(supabase secrets set SLACK_TOKEN=...)→ 部署函数:
supabase functions deploy slack-bot-mention
部署完成后,回到 Slack App 后台把函数 URL(形如 https://<project-ref>.functions.supabase.co/slack-bot-mention)填入 Event Subscriptions 并验证即可。若希望接入 CI,示例仓库还提供了基于 supabase/setup-cli Action 的 GitHub Actions 自动部署参考,详见 examples/edge-functions/README.md。
六、生产化注意点
- 务必校验 Slack 请求签名:仓库实现中的注释也提醒了这一点。任何公网可达的端点都会被扫描器探测,仅校验
challenge还不够;应按 Slack 文档用 Signing Secret 计算 HMAC-SHA256 与x-slack-signature比对,同时校验x-slack-request-timestamp的时效性以防重放攻击。 - 消息响应在 3 秒内完成:Slack 事件订阅要求端点在 3 秒内确认(返回 200),否则会触发重试。因此较重的业务(如调用数据库、外部 AI 接口)建议先返回 200,再用
chat.postMessage异步补发,或改用 Slack 的chat_postMessage配合异步任务处理。 - 失败时的重试语义:Slack 对非 200 响应会按递增间隔重试,注意用响应状态码区分「已消费(200)」与「处理失败(500)」,避免重复回复。
- Token 权限范围:Bot Token 需要
chat:write权限才能在频道发消息,app_mention需要app_mentions:read事件订阅权限,在 Slack App 的 OAuth & Permissions 页确认后重新安装应用才会生效。
七、小结与进一步探索
通过本文,你已掌握了一条完整的 Slack 机器人落地链路:在 Slack App 后台配置 app_mention 事件与 Request URL → 用 CLI 把 SLACK_TOKEN 注入 Supabase 项目 → 部署 slack-bot-mention 函数(记住 verify_jwt = false)→ 在函数里解析 text 并调用 chat.postMessage 回复。剩余的可定制空间正是 app_mention 分支里的那段注释——把「回复固定文案」替换为查询数据库、调用 AI 模型或对接第三方 API,一个具备实际业务能力的 Slack 助手就成型了。
仓库中还有大量同构的第三方平台集成示例可供对照学习,例如 discord-bot 与 telegram-bot,它们与 Slack 示例一样遵循「读取平台事件 → 解析消息 → 调用平台 API 回复」的模式,可以在 examples/edge-functions/supabase/functions 目录下继续阅读。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00