何时使用
适用:
- 用官方 Bolt 框架新建或迁移 Slack App:斜杠命令、按钮/菜单交互、模态框(modal)、Home tab、事件订阅。
- 需要 Block Kit 搭富文本/交互式消息,且要避开 50 块、3000 字符等隐性限额。
- 多工作区分发,要做 OAuth 2.0 安装流(state 防 CSRF、token 持久化加密)。
- 本地/内网开发用 Socket Mode,生产切 HTTP webhook。
- 给 Slack Workflow Builder 提供自定义步骤(edit/save/execute)。
不该用:
- 仅需单向通知:用 Incoming Webhook 一行 POST 即可,无需 Bolt。
- 纯无代码 Workflow Builder 配置(不写自定义步骤)。
- 非 Slack 平台(Discord、Teams、企业微信)——命令与 payload 结构不通用,见互见
discord-bot-architect。
核心铁律:所有交互请求(斜杠命令、按钮、菜单、模态提交、快捷指令)必须 3 秒内 ack(),慢操作一律丢后台;token/signing secret 永不硬编码或入日志。
步骤
- 初始化 App:从环境变量读
SLACK_BOT_TOKEN+SLACK_SIGNING_SECRET建App。提供 signing_secret 后 Bolt 自动验签(X-Slack-Signature),无需手写。 - 注册监听器:
@app.message/@app.command/@app.action/@app.view/@app.event分别接消息、斜杠命令、交互组件、模态提交、事件。 - 守住 3 秒:每个交互处理器先
ack()再干活;慢逻辑用线程或 Bolt lazy listener 放后台,用respond()回结果。 - 搭 UI:用 Block Kit 组装 blocks,发送前用
validate_blocks校限额(见示例);交互后chat_update移除已点按钮防重复点击。 - 选运行模式:开发用 Socket Mode(需
xapp-app-level token +connections:writescope,无需公网 URL);生产用 HTTP(Flask/FastAPI 适配器)。 - 要分发就上 OAuth:配
OAuthSettings,只申请最小 scope;callback 必校state(一次性 consume);token 落库前加密。 - 接 Workflow Builder:定义
edit/save/execute三函数,WorkflowStep注册,execute内必须调用complete()或fail()。
指令
ack-then-process(最高频且最易错)——慢操作丢后台:
@app.command("/slow-task")
def handle_slow_task(ack, command, respond):
ack("Processing...") # 必须先 ack,3 秒内
def do_work():
result = call_slow_api(command["text"]) # 可耗时 10s+
respond(f"Done! {result}")
threading.Thread(target=do_work).start()
# 或用 Bolt lazy listener(自动并发)
@app.command("/slow-task")
def ack_only(ack): ack()
@ack_only.lazy
def process(command, respond):
respond(slow_operation(command["text"])) # ack 后跑,不限时
模态:开窗 client.views_open(trigger_id=body["trigger_id"], view={...});提交处理器 @app.view("callback_id") 里从 view["state"]["values"][block_id][action_id]["value"] 取值,可 ack(response_action="clear"/"update")。
OAuth 防 CSRF(CRITICAL)——签发并校验 state:
state = state_store.issue() # 加密随机 state
# callback:
if request.args.get("state") != stored or not state_store.consume(state):
return "Invalid state - possible CSRF", 403
最小 scope 速查(申请越少装机率越高,约 70% 用户因权限过多放弃安装):
| 用途 | 所需 scope |
|---|---|
| 发消息 | chat:write |
| 斜杠命令 | commands |
| 响应 @提及 | app_mentions:read, chat:write |
| 读公开频道历史 | channels:history(私有 groups:history) |
| 加 reaction | reactions:write |
| 上传文件 | files:write |
Socket Mode(开发)vs HTTP(生产):
# 开发
from slack_bolt.adapter.socket_mode import SocketModeHandler
SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() # xapp- token
# 生产(Flask)
from slack_bolt.adapter.flask import SlackRequestHandler
handler = SlackRequestHandler(app)
@flask_app.route("/slack/events", methods=["POST"])
def events(): return handler.handle(request)
示例
Block Kit 富消息 + 限额校验 + 幂等更新:
def build_incident_blocks(inc: dict) -> list:
return [
{"type": "header", "text": {"type": "plain_text", "text": "🚨 Incident Alert"}},
{"type": "section", "fields": [
{"type": "mrkdwn", "text": f"*Incident:*\n{inc['title']}"},
{"type": "mrkdwn", "text": f"*Severity:*\n{inc['severity'].upper()}"},
]},
{"type": "actions", "block_id": f"incident_actions_{inc['id']}", "elements": [
{"type": "button", "text": {"type": "plain_text", "text": "Acknowledge"},
"style": "primary", "action_id": "acknowledge_incident", "value": inc["id"]},
{"type": "button", "text": {"type": "plain_text", "text": "Resolve"},
"style": "danger", "action_id": "resolve_incident", "value": inc["id"],
"confirm": {"title": {"type": "plain_text", "text": "Resolve?"},
"text": {"type": "mrkdwn", "text": "确定已解决?"},
"confirm": {"type": "plain_text", "text": "Yes"},
"deny": {"type": "plain_text", "text": "Cancel"}}},
]},
]
BLOCK_KIT_LIMITS = {"blocks_per_message": 50, "text_block_chars": 3000, "elements_per_actions": 10}
def validate_blocks(blocks: list) -> tuple[bool, str]:
if len(blocks) > BLOCK_KIT_LIMITS["blocks_per_message"]:
return False, f"Too many blocks: {len(blocks)} > 50"
for b in blocks:
if b.get("type") == "section" and len(b.get("text", {}).get("text", "")) > 3000:
return False, "Text > 3000 chars"
if b.get("type") == "actions" and len(b.get("elements", [])) > 10:
return False, "actions > 10 elements"
return True, "OK"
@app.action("acknowledge_incident")
def on_ack(ack, body, client):
ack() # 先 ack
blocks = body["message"]["blocks"]
action_block = next(b for b in blocks if b.get("block_id","").startswith("incident_actions"))
# 移除已点按钮,防重复点击(幂等)
action_block["elements"] = [e for e in action_block["elements"] if e["action_id"] != "acknowledge_incident"]
client.chat_update(channel=body["channel"]["id"], ts=body["message"]["ts"], blocks=blocks)
Workflow Builder 自定义步骤(execute 必须收尾):
def execute(step, complete, fail):
try:
ticket = create_ticket(title=step["inputs"]["title"]["value"])
complete(outputs={"ticket_id": ticket["id"], "ticket_url": ticket["url"]})
except Exception as e:
fail(error={"message": str(e)}) # 不调 complete/fail 会让工作流卡死
app.step(WorkflowStep(callback_id="create_ticket_step", edit=edit, save=save, execute=execute))
token 加密落库(Fernet):
from cryptography.fernet import Fernet
cipher = Fernet(os.environ["TOKEN_ENC_KEY"])
enc = cipher.encrypt(bot_token.encode()) # 入库前加密
tok = cipher.decrypt(row).decode() # 用时解密
注意事项
- 3 秒 ack(CRITICAL):斜杠命令/按钮/模态/快捷指令未在 3 秒内 ack,用户见「This command timed out」,即使后台跑成功也算失败;ack 里也不能放慢操作。
- 验签(CRITICAL):Slack 用 signing secret 签每个请求;提供
signing_secret时 Bolt 自动验,自建 webhook 必须手动校X-Slack-Signature且拒绝 5 分钟外的时间戳(防重放)。 - OAuth state(CRITICAL):callback 不校验 state 即有 CSRF 风险;用 state_store 签发 + 一次性 consume。
- token 泄露(CRITICAL):
xoxb-/xoxp-/xapp-一律环境变量注入,绝不硬编码、不入日志、不下发前端、入库须加密;泄露后立即在 OAuth & Permissions 里 Rotate 并查审计日志。 - Block Kit 限额:单消息/模态 50 块、Home tab 100 块、单文本块 3000 字符、actions 块 10 元素、select 100 选项、模态总体 24KB;超限报
invalid_blocks或静默失败,发送前先校验、长内容分页。 - 最小 scope:避免
admin等大权限,按用途只申请所需;过度申请压低安装率,可能被 App Directory 拒。 - Socket Mode 不上生产:单 WebSocket 连接、不能水平扩展、会断线,仅适合开发/内网;生产用 HTTP webhook(必须时给 Socket Mode 加重连退避)。
- 幂等:交互处理器要可重复执行——更新消息移除已点按钮,避免重复副作用。
- fallback 文本:发 blocks 时务必同时给
text=作为通知与无障碍回退。
互见
- related:
discord-bot-architect—— 需要跨 Discord/Slack 多平台机器人架构时对照。 - related:
auth-implementation-patterns—— OAuth 2.0、state/CSRF、企业 SSO 等通用认证模式的深入实现。 - combines_with:
production-llm-app-builder—— 把 LLM 接入做成对话式 / AI 驱动的 Slack 机器人。
—— 本条采编自 sickn33/antigravity-awesome-skills(MIT 许可;原技能 SKILL.md frontmatter 标注上游为 vibeship-spawner-skills,Apache-2.0)。