首页
/ Supabase Edge Functions 实战:用 slack-bot-mention 构建 Slack 机器人 @提及响应

Supabase Edge Functions 实战:用 slack-bot-mention 构建 Slack 机器人 @提及响应

2026-09-06 18:28:03作者:董灵辛Dennis

导读

本文以 Supabase 官方示例仓库中的 slack-bot-mention 文档 为主线,讲解如何基于 Supabase Edge Functions 编写一个处理 Slack @提及(app_mention)事件并自动回复的机器人。文中会覆盖 Slack App 事件订阅的完整配置流程、SLACK_TOKEN 密钥注入方式、url_verificationapp_mention 两类事件的请求处理逻辑,并结合仓库内 index.tsconfig.toml 的真实配置做源码级佐证。读完本文你将可以直接把这段函数代码部署上线,并根据自己的业务改写回复逻辑。

一、整体思路:为什么用 Edge Function 接收 Slack 事件

Slack 机器人的经典工作方式是:用户在频道或群里 @机器人 之后,Slack 通过「事件订阅(Event Subscriptions)」把 app_mention 事件以 HTTP POST 的形式推送到一个公网可达的 URL 上。这个 URL 既可以是你自建的后端服务,也可以是 Supabase Edge Function——Edge Function 天然提供了一个公网 HTTPS 端点,配合 示例仓库的 README 中描述的 CLI 部署方式,即可在数分钟内把一个 Deno 编写的机器人跑起来。

该示例函数需要处理 Slack 推送的两类请求:

  1. url_verification(URL 校验):在 Slack App 后台配置事件订阅 URL 时,Slack 会发送一个带 challenge 字段的验证请求,函数必须原样把 challenge 返回,才能完成订阅配置。
  2. app_mention(@提及事件):用户在对话中 @机器人 后,Slack 推送包含 usertextchannelts 等字段的事件,函数调用 Slack Web API 回复消息。

二、配置 Slack App:事件订阅与 URL 校验

在部署任何代码之前,需要先在 Slack 侧完成 App 配置。按照 原文档 的步骤操作:

  1. 打开 Slack Apps 管理页面,创建或选择一个应用(建议使用 Bot User 类型,这样会得到以 xoxb- 开头的 Bot Token)。
  2. 在 App 的 Event Subscriptions(事件订阅) 中开启订阅,并把 slack-bot-mention 函数的 URL 填入 Request URL 输入框。
  3. 点击验证(Verify)按钮。此时 Slack 会立即向该 URL 发送一次 url_verification 握手请求;Edge Function 会正确回显 challenge 字段,页面随即提示验证成功——这正是「函数已正确部署并可访问」的最直接确认。
  4. 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 })
    }
  }),
}

两点演进与对应说明:

  1. 包源从 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 配置相呼应。
  2. 安全注释:仓库代码中的注释明确指出,在信任请求载荷之前,应实现 Slack 请求签名校验——即验证 x-slack-signaturex-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-webhookstelegram-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

六、生产化注意点

  1. 务必校验 Slack 请求签名:仓库实现中的注释也提醒了这一点。任何公网可达的端点都会被扫描器探测,仅校验 challenge 还不够;应按 Slack 文档用 Signing Secret 计算 HMAC-SHA256 与 x-slack-signature 比对,同时校验 x-slack-request-timestamp 的时效性以防重放攻击。
  2. 消息响应在 3 秒内完成:Slack 事件订阅要求端点在 3 秒内确认(返回 200),否则会触发重试。因此较重的业务(如调用数据库、外部 AI 接口)建议先返回 200,再用 chat.postMessage 异步补发,或改用 Slack 的 chat_postMessage 配合异步任务处理。
  3. 失败时的重试语义:Slack 对非 200 响应会按递增间隔重试,注意用响应状态码区分「已消费(200)」与「处理失败(500)」,避免重复回复。
  4. 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-bottelegram-bot,它们与 Slack 示例一样遵循「读取平台事件 → 解析消息 → 调用平台 API 回复」的模式,可以在 examples/edge-functions/supabase/functions 目录下继续阅读。

登录后查看全文
热门项目推荐
相关项目推荐