Sanity 文档函数实战:用 slack-notify 构建内容发布即通知的 Slack 自动化流程
【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity
Sanity Functions 是运行在 Sanity 云环境中的服务端函数,能够对内容事件做出实时响应。本指南以 examples/functions/slack-notify 为蓝本,讲解如何基于@sanity/blueprints与@slack/web-api构建一个「文章创建即推送 Slack 通知」的文档函数:从创建 Slack 应用、配置 blueprint、本地测试到生产部署全流程。读完本文,你将掌握 Sanity 文档函数(Document Function)的事件触发、投影过滤、环境变量管理以及 CLI 测试/部署/日志排障的完整技能,并能在此基础上自由扩展消息格式与触发条件。
问题与解决方案
问题:内容团队需要在新内容产生时第一时间知晓,但手动通知成员既耗时又容易遗漏。团队期望获得包含关键信息(标题、发布日期)以及直达链接(发布页面 + Studio 编辑器)的自动化通知。
解决方案:利用 Sanity 的文档函数(document function)监听内容创建事件,配合 Slack Web API,在文章发布时自动向指定频道发送一条格式化消息,包含文章标题、创建时间,以及指向发布页面和 Studio 编辑器的快捷链接。
收益:
- 团队即时获知新内容动态
- 提供发布页与 Studio 的一键直达入口,便于快速审阅和推广
- 减少手动通知的工作量
- 缩短内容审阅与推广的响应时间
适用模板与 Schema 要求
该函数与任何包含post文档类型(含title与slug字段)的官方 clean 模板兼容,建议先在本地安装的模板中试用。
函数对 post 文档的 schema 要求如下:
| 字段 | 类型 | 说明 |
|---|---|---|
title | string | 文章标题 |
slug | slug 类型,含current属性 | 用于拼接网页 URL |
大多数官方模板已内置这些字段。若缺失,需在 schema 中补充后才可触发通知。
实现步骤
1. 创建 Slack 应用并获取 OAuth Token
创建应用:在 Slack 的 App 管理后台(api.slack.com/apps)点击 "Create New App",选择 "From scratch",为应用取一个描述性名称(如 "Sanity Content Notifications"),并从下拉框选择你的工作区。
配置权限:进入 "OAuth & Permissions" 页面,滚动到 "Scopes" 部分,在 "Bot Token Scopes" 下点击 "Add an OAuth Scope",添加chat:write权限(允许机器人发送消息)。
安装应用:点击 "Install to Workspace",审阅权限后点击 "Allow",复制以xoxb-开头的 "Bot User OAuth Token",下一步将用到。
将应用邀请进频道:进入接收通知的 Slack 频道(如#test-channel),输入/invite @your-app-name,或点击频道名 → Settings → Integrations → Add apps,选择刚创建的应用将其加入频道:
2. 初始化示例
若尚未初始化 blueprints,先执行:
npx sanity blueprints init按提示选择你的组织和 Sanity studio。然后添加 slack-notify 函数示例:
npx sanity blueprints add function --example slack-notify3. 在 blueprint 中添加资源配置
在项目根目录的sanity.blueprint.ts中注册该文档函数:
// sanity.blueprint.ts import 'dotenv/config' import process from 'node:process' import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints' export default defineBlueprint({ resources: [ defineDocumentFunction({ name: 'slack-notify', src: './functions/slack-notify', memory: 1, timeout: 10, event: { on: ['create'], filter: "_type == 'post'", projection: '{_id, title, slug, _createdAt}', }, env: { SLACK_OAUTH_TOKEN: process.env.SLACK_OAUTH_TOKEN, SLACK_CHANNEL: process.env.SLACK_CHANNEL, BASE_URL: process.env.BASE_URL, STUDIO_URL: process.env.STUDIO_URL, }, }), ], })各配置项的作用:
| 配置项 | 取值 | 说明 |
|---|---|---|
name | slack-notify | 函数名称,本地测试、部署、env/logs 命令均按此引用 |
src | ./functions/slack-notify | 函数代码所在目录 |
memory | 1 | 分配给函数的内存(GB),轻量通知任务 1 即可 |
timeout | 10 | 函数超时时间(秒) |
event.on | ['create'] | 触发事件:文档创建时执行 |
event.filter | "_type == 'post'" | 过滤条件:仅post类型文档触发,避免无关文档造成不必要的执行 |
event.projection | '{_id, title, slug, _createdAt}' | 投影字段:注入事件负载的最小字段集 |
env | 见下节 | 注入到函数运行时的环境变量 |
该示例的资源元数据也固化在 examples/functions/slack-notify/package.json 的blueprintResourceItem字段中;而 examples/sanity.blueprint.ts 展示了如何扫描functions/目录自动发现并注册所有示例函数,可作为批量配置多个函数的参考模式。
4. 安装依赖
在项目根目录执行:
npm installslack-notify 函数本身依赖@sanity/functions(提供documentEventHandler)与@slack/web-api(提供 Slack 消息发送能力),两者版本声明见 examples/functions/slack-notify/package.json。
5. 配置环境变量
在项目根目录创建.env文件:
# Required SLACK_OAUTH_TOKEN=xoxb-your-slack-bot-token-here # Optional (defaults shown) SLACK_CHANNEL=general BASE_URL=http://localhost:3000 STUDIO_URL=http://localhost:3333| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
SLACK_OAUTH_TOKEN | 是 | 无 | Slack 机器人 OAuth Token(xoxb-开头) |
SLACK_CHANNEL | 否 | general | 目标 Slack 频道名 |
BASE_URL | 否 | http://localhost:3000 | 网站(发布页)URL 前缀 |
STUDIO_URL | 否 | http://localhost:3333 | Sanity Studio URL 前缀 |
源码解析:通知消息是如何组装的
打开 examples/functions/slack-notify/index.ts 可以看到核心实现。函数从node:process的env读取配置,并为可选变量提供与 blueprint 一致的默认值:
import {env} from 'node:process' import {documentEventHandler} from '@sanity/functions' import {WebClient} from '@slack/web-api' const { SLACK_OAUTH_TOKEN = '', SLACK_CHANNEL = '', BASE_URL = 'http://localhost:3000', STUDIO_URL = 'http://localhost:3333', } = envhandler由documentEventHandler包装,事件负载中的投影字段通过event.data访问。消息在一条模板字符串中完成组装:标题缺省时降级为'Untitled',slug 缺失时降级为'no-slug',日期通过new Date(event.data._createdAt).toLocaleString()格式化:
export const handler = documentEventHandler(async ({event}) => { // Initialize Slack client const slack = new WebClient(SLACK_OAUTH_TOKEN) try { // Prepare message content const message = `*New Document Created!*\nTitle: ${event.data.title || 'Untitled'}\nWebpage: <${BASE_URL}/posts/${event.data.slug?.current || 'no-slug'}|Click Here>\nStudio: <${STUDIO_URL}/structure/post;${event.data._id}|Click Here>\nDateTime Created: ${new Date(event.data._createdAt).toLocaleString()}` // Send message to Slack await slack.chat.postMessage({ channel: SLACK_CHANNEL, text: message, }) console.log( 'Slack notification sent successfully to channel:', SLACK_CHANNEL, 'Message sent:', message, ) } catch (error) { console.error('Error sending Slack notification:', error) throw error } })几个值得注意的实现要点:
event.data直接来自 blueprint 的 projection:这里仅能访问{_id, title, slug, _createdAt}四个字段,若要使用更多字段,须同步修改 projection。- WebClient 的初始化时机:
new WebClient(SLACK_OAUTH_TOKEN)在每次调用时创建,token 为空时 Slack API 会返回invalid_auth错误,这正是下文故障排查中常见错误之一的根因。 - 错误处理:catch 中
console.error后重新throw,便于在函数日志中定位问题。 - 本地测试数据:examples/functions/slack-notify/document.json 提供了一份样例文档(
_type: post、_id: drafts.test-document-id、_createdAt等),供npx sanity functions test直接使用,无需连接真实数据集。
本地测试函数
部署前可用 Sanity CLI 在本地测试,注意:该函数需要有效的 Slack OAuth Token,测试期间会真实地向目标频道发送消息,请先确认环境变量已配置完成。
1. 基础函数测试
使用随示例附带的测试文档(在项目根目录执行):
# If using .env file (recommended) npx sanity functions test slack-notify \ --file functions/slack-notify/document.json \ --dataset production \ --with-user-token # Or set environment variables inline SLACK_OAUTH_TOKEN=xoxb-your-token SLACK_CHANNEL=test-channel \ npx sanity functions test slack-notify \ --file functions/slack-notify/document.json \ --dataset production \ --with-user-token备选方案:用数据集中的真实文档测试——先从 studio 目录导出一篇现有 post:
cd studio npx sanity documents query "*[_type == 'post'][0]" > ../real-post.json # Back to project root for function testing cd .. npx sanity functions test slack-notify \ --file real-post.json \ --dataset production \ --with-user-token(同样支持用内联环境变量替换--file前的配置方式。)
2. 不实际发送消息的测试
若只想验证函数逻辑而不真正发送 Slack 消息,可临时注释掉slack.chat.postMessage调用,改为仅console.log输出将要发送的消息内容。
3. 交互式开发模式
启动开发服务器进行交互测试:
SLACK_OAUTH_TOKEN=slack-OAuth-token npx sanity functions dev该命令会打开一个交互式 playground,支持用自定义数据测试函数。
测试建议
- 使用专用测试频道:为开发创建独立频道,避免打扰生产频道成员。
- 使用真实的 Slack Token:函数依赖有效 OAuth Token 才能工作。
- 测试期间留意频道:观察指定频道是否收到测试消息。
- 测试边界情况:尝试无
title或无slug的文档,确认函数能优雅降级(源码中|| 'Untitled'、|| 'no-slug'正是为此设计)。
部署到生产环境
本地测试通过后即可部署。部署前请确认你拥有 Sanity 项目的 Deploy Studio 权限。
部署前置条件
- Sanity CLI v3.92.0 或更高版本
- 项目的 Deploy Studio 权限
- Node.js v22.x(与生产运行时一致)
- 具备
chat:write权限的有效 Slack OAuth Token
部署步骤
1. 校验 blueprint 配置:确认sanity.blueprint.ts中函数配置正确(事件、过滤、投影与环境变量),部署时无需再重复传入env映射,但需保证.env已存在:
// sanity.blueprint.ts import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints' export default defineBlueprint({ resources: [ defineDocumentFunction({ name: 'slack-notify', src: './functions/slack-notify', memory: 1, timeout: 10, event: { on: ['create'], filter: "_type == 'post'", projection: '{_id, title, slug, _createdAt}', }, }), ], })2. 部署 blueprint:在项目根目录执行:
npx sanity blueprints deploy该命令会:打包函数代码 → 上传到 Sanity 基础设施 → 配置 post 创建的事件触发器 → 使 slack-notify 函数在生产环境生效。
3. 添加环境变量:部署后需将 Slack OAuth Token 添加为函数环境变量:
npx sanity functions env add slack-notify SLACK_OAUTH_TOKEN "your-slack-oauth-token-here"将"your-slack-oauth-token-here"替换为实际的xoxb-Token。可通过以下命令验证是否添加成功:
npx sanity functions env list slack-notify4. 验证部署:
- 新建一篇 post,确认 Slack 收到通知;
- 通过 CLI 监控函数日志:
npx sanity functions logs slack-notify部署最佳实践
- 先充分测试:始终在本地验证通过后再部署。
- 使用精确的过滤条件:当前 filter 仅匹配 post,避免不必要执行、节省资源。
- 监控性能:这是一个轻量函数,资源需求极小,但仍建议通过日志保持观察。
部署故障排查
| 错误 | 可能原因 | 解决办法 |
|---|---|---|
| "Deploy Studio permission required" | 账号缺少部署权限 | 请项目管理员授予 Deploy Studio 权限 |
| "Blueprint validation failed" | sanity.blueprint.ts配置有误 | 核对配置是否符合 blueprint 预期 schema |
| "Function not sending Slack notifications after deployment" | OAuth Token 无效或频道访问受限 | 检查 Slack 应用配置,确认机器人已被邀请进目标频道 |
| "Missing environment variable SLACK_OAUTH_TOKEN" | 生产环境未设置环境变量 | 在 Sanity 项目设置中配置SLACK_OAUTH_TOKEN |
使用效果示例
当创建新的 post 文档时,函数自动完成:提取标题、slug 与创建时间 → 组装 Slack 消息 → 发送到指定频道 → 附带网页与 Studio 编辑器的直达链接。Slack 中收到的消息形如:
*New Document Created!* Title: Getting Started with Sanity Webpage: <http://localhost:3000/posts/getting-started-with-sanity|Click Here> Studio: <http://localhost:3333/structure/post;post-id|Click Here> DateTime Created: 1/15/2024, 10:30:00 AM自定义通知行为
修改 URL 与 Slack 频道
README 建议通过修改函数顶部的配置常量来定制:
// Configuration constants const baseUrl = 'https://your-domain.com' // Update to your production URL const studioUrl = 'https://your-studio.sanity.studio' // Update to your studio URL const slackChannel = 'your-channel-name' // Update to your target channel从源码结构看,index.ts 中这些值实际来自环境变量(BASE_URL、STUDIO_URL、SLACK_CHANNEL)并带 localhost 默认值,因此更推荐的定制方式是直接修改 blueprint 的env映射或.env文件,部署后则使用npx sanity functions env add更新,无需改动代码。
修改消息格式
text: `Your custom message format with ${event.data.title}`,增加更多文档字段
通过event.data.fieldName访问更多字段,同时记得在 blueprint 的projection中把新字段加进投影,否则事件负载中不会包含该字段。
修改通知触发条件
调整 blueprint 配置中的filter即可针对不同的文档类型或条件触发,例如改为"_type == 'news'"或叠加defined(...)判断(可参考 telegram-notify 示例 中的_type == "comment" && defined(comment)写法)。
常见故障排查
| 错误 | 可能原因 | 解决办法 |
|---|---|---|
| "An API error occurred: invalid_auth" | Slack OAuth Token 无效或缺失 | 确认 Token 正确且具备chat:write权限 |
| "An API error occurred: channel_not_found" | 频道不存在或机器人无权访问 | 确认频道存在,并把 Slack 应用邀请进频道 |
| "Missing environment variable SLACK_OAUTH_TOKEN" | 环境变量未设置 | 用SLACK_OAUTH_TOKEN配置机器人 Token |
| "Messages not appearing in Slack" | 机器人缺权限或不在频道内 | 将机器人加入目标频道并确认chat:write权限 |
相关示例
- Telegram Notify —— 文档创建时发送 Telegram 通知,实现思路与本示例高度一致(事件触发 + 环境变量注入 + 消息链接),可作为横向对比参考:它额外演示了
defined()过滤写法、内联键盘回复以及根据 localhost 判断消息形态的条件逻辑。
需求清单速览
完成本示例所需的前提:一个 Sanity 项目;一个具备管理员权限(可创建应用)的 Slack 工作区;带chat:write权限的 Slack OAuth Token;含title与slug字段的 post schema;本地测试需 Node.js v22.x。
【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考