深夜提醒

现在是深夜,建议您注意休息,不要熬夜哦~

🏮 🏮 🏮

新年快乐

祝君万事如意心想事成!

2024 桐庐半程马拉松
00:00:00
时间
0.00
距离(公里)
--:--
配速
--
步频
--
心率 (bpm)
--
配速
步频
|
share-image
ESC

博客评论飞书审批机器人:实现留言实时通知与一键审批

前言

作为一个博客作者,我经常遇到这样的困扰:

  • 读者提交了评论或留言,我不能第一时间知道
  • 需要登录后台才能审核评论,操作繁琐
  • 垃圾评论和正常评论混在一起,筛选困难

为了解决这些问题,我决定为博客开发一个飞书审批机器人,实现:

  • 📱 新评论/留言实时推送到飞书
  • ✅ 一键审批通过或拒绝
  • 🔄 卡片状态实时更新

效果展示

飞书机器人效果

技术方案

架构设计

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ 博客前端 │────▶│ Blog API │────▶│ 飞书服务器 │
└─────────────┘ └─────────────┘ └─────────────┘

┌──────┴──────┐
│ WebSocket │
│ 长连接 │
└──────┬──────┘

┌─────────────┐
│ 飞书客户端 │
└─────────────┘

核心技术选型

  • 飞书 SDK: oapi-sdk-go/v3 - 官方 Go SDK
  • 连接方式: WebSocket 长连接(无需公网域名)
  • 卡片交互: 飞书消息卡片 v2

实现过程

1. 创建飞书应用

首先需要在飞书开放平台创建应用:

  1. 创建企业自建应用
  2. 获取 App IDApp Secret
  3. 订阅事件:im.message.receive_v1card.action.trigger
  4. 开启机器人能力

2. 核心代码实现

FeishuBotService 结构

type FeishuBotService struct {
client *lark.Client
db *gorm.DB
appID string
appSecret string
adminOpenID string // 接收通知的管理员
}

启动 WebSocket 连接

func (s *FeishuBotService) Start(ctx context.Context) error {
// 创建事件处理器
eventHandler := dispatcher.NewEventDispatcher("", "").
OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
return s.handleCardAction(event)
})

// WebSocket 客户端
cli := larkws.NewClient(s.appID, s.appSecret,
larkws.WithEventHandler(eventHandler),
)

return cli.Start(ctx)
}

发送通知卡片

func (s *FeishuBotService) SendCommentNotification(comment *models.Comment) error {
card := map[string]interface{}{
"config": map[string]interface{}{"wide_screen_mode": true},
"header": map[string]interface{}{
"title": map[string]interface{}{
"tag": "plain_text",
"content": "📝 收到新评论",
},
"template": "blue",
},
"elements": []interface{}{
// 评论信息展示
map[string]interface{}{
"tag": "div",
"fields": []interface{}{
map[string]interface{}{
"is_short": true,
"text": map[string]interface{}{
"tag": "lark_md",
"content": fmt.Sprintf("**评论者:**\n%s", comment.Nickname),
},
},
// ... 更多字段
},
},
// 操作按钮
map[string]interface{}{
"tag": "action",
"actions": []interface{}{
map[string]interface{}{
"tag": "button",
"text": map[string]interface{}{
"tag": "plain_text",
"content": "✅ 通过",
},
"type": "primary",
"value": map[string]interface{}{
"action": "approve_comment",
"comment_id": comment.ID,
},
},
map[string]interface{}{
"tag": "button",
"text": map[string]interface{}{
"tag": "plain_text",
"content": "❌ 拒绝",
},
"type": "danger",
"value": map[string]interface{}{
"action": "reject_comment",
"comment_id": comment.ID,
},
},
},
},
},
}

return s.sendCard(s.adminOpenID, card)
}

处理按钮点击

func (s *FeishuBotService) handleCardAction(event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
action := event.Event.Action.Value["action"]

switch action {
case "approve_comment":
return s.handleApproveComment(event)
case "reject_comment":
return s.handleRejectComment(event)
// ... 其他操作
}
}

func (s *FeishuBotService) handleApproveComment(event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
commentID := event.Event.Action.Value["comment_id"]

// 更新数据库状态
s.db.Model(&models.Comment{}).Where("id = ?", commentID).Update("status", 1)

// 返回更新后的卡片
return &callback.CardActionTriggerResponse{
Toast: &callback.Toast{
Type: "success",
Content: "✅ 评论已通过",
},
Card: &callback.Card{
Type: "raw",
Data: s.buildApprovedCommentCard(comment),
},
}, nil
}

3. 与博客系统集成

在博客主程序中启动机器人:

func main() {
// ... 初始化数据库、路由等

// 启动飞书机器人
if cfg.Feishu.AppID != "" {
botService := services.NewFeishuBotService(
db,
cfg.Feishu.AppID,
cfg.Feishu.AppSecret,
cfg.Feishu.AdminOpenID,
)

go func() {
botService.Start(context.Background())
}()
}

// 启动 HTTP 服务器
r.Run(addr)
}

4. 部署配置

使用 systemd 管理服务,环境变量配置:

[Service]
Environment="FEISHU_APP_ID=cli_xxxxxxxx"
Environment="FEISHU_APP_SECRET=xxxxxxxx"
Environment="FEISHU_ADMIN_OPEN_ID=ou_xxxxxxxx"

遇到的问题

1. 卡片 JSON 格式错误

现象: 发送卡片时报错 parse card json err

解决: 飞书卡片要求数组类型为 []interface{} 而非 []map[string]interface{}

// 错误
"elements": []map[string]interface{}{...}

// 正确
"elements": []interface{}{...}

2. 回调响应格式

现象: 点击按钮后卡片没有更新

解决: 回调响应必须使用 callback.Card 结构:

return &callback.CardActionTriggerResponse{
Toast: &callback.Toast{Type: "success", Content: "已处理"},
Card: &callback.Card{
Type: "raw",
Data: cardData, // map[string]interface{}
},
}, nil

3. 管理员 OpenID 获取

给机器人发送任意消息后,从日志中获取发送者的 OpenID:

journalctl -u blog-api -f | grep "OpenId"

最终效果

实时通知: 评论/留言提交后 1 秒内收到飞书通知
一键审批: 点击按钮即可完成审核,无需登录后台
状态同步: 操作后卡片立即更新,显示审批结果和时间
长连接: WebSocket 模式,无需配置公网回调地址

总结

通过飞书机器人,我把博客评论审核的工作流从「登录后台→查找评论→审核」简化为「收到通知→点击按钮」,大大提高了效率。同时 WebSocket 长连接模式让部署变得简单,不需要公网域名和回调配置。

如果你也想为博客添加类似功能,欢迎参考本文的实现方案。

文章作者:阿文
文章链接: https://www.awen.me/post/9c2176be.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘 🛑📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭 ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️ 🈯💹❇️ ✳️🌐 💠Ⓜ️🌀 💤🏧🚾 🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
您的评论由 AI 智能审核,一般1分钟内会展示,若不展示请确认你的评论是否符合社区和法律规范
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘 🛑📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭 ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️ 🈯💹❇️ ✳️🌐 💠Ⓜ️🌀 💤🏧🚾 🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️