在现代企业应用开发中,及时准确的消息通知机制至关重要。本文将介绍一种基于策略模式的飞书机器人消息推送实现方案,帮助开发者快速集成多种消息推送场景。
设计理念
我们的解决方案采用了策略模式,通过抽象基类 AbstractFeishuMessageStrategy 统一处理消息发送的核心逻辑,而具体的策略实现类则专注于配置差异化的 webhook 地址。
核心组件解析
- 抽象基类 AbstractFeishuMessageStrategy
AbstractFeishuMessageStrategy 是整个消息推送框架的核心,它提供了统一的消息发送机制和三种标准消息类型的构建方法。
java
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 飞书消息推送抽象策略基类
* 提供统一的消息发送逻辑和标准消息构建方法
*/
@Slf4j
public abstract class AbstractFeishuMessageStrategy {
/**
* 获取飞书机器人webhook地址
* 子类必须实现此方法提供具体的webhook地址
*
* @return webhook地址
*/
protected abstract String getWebhookUrl();
/**
* 获取策略名称,默认返回类名
*
* @return 策略名称
*/
public String getStrategyName() {
return this.getClass().getSimpleName();
}
/**
* 发送消息核心方法
* 使用HTTP工具发送JSON格式消息到飞书机器人
*
* @param message 消息JSON对象
* @return 是否发送成功
*/
protected boolean sendMessage(JSONObject message) {
Map<String, String> headers = new HashMap<>();
try {
String responseBody = HttpUtil.post(getWebhookUrl(), message.toJSONString());
log.info("【飞书消息通知】发送飞书消息结果: {}", responseBody);
return Objects.equals(JSON.parseObject(responseBody).get("StatusCode"), 0);
} catch (Exception e) {
log.error("【飞书消息通知】发送消息时发生异常: {}", e.getMessage(), e);
return false;
}
}
/**
* 发送文本消息
*
* @param text 消息内容
* @return 是否发送成功
*/
public boolean sendTextMessage(String text) {
try {
JSONObject message = new JSONObject();
message.put("msg_type", "text");
JSONObject content = new JSONObject();
content.put("text", text);
message.put("content", content);
return sendMessage(message);
} catch (Exception e) {
log.error("【飞书消息通知】发送飞书消息失败,sendTextMessage", e);
}
return false;
}
/**
* 发送富文本消息
*
* @param title 消息标题
* @param contentList 富文本内容列表
* @return 是否发送成功
*/
public boolean sendRichTextMessage(String title, List<RichTextItem> contentList) {
try {
JSONObject message = new JSONObject();
message.put("msg_type", "post");
JSONObject content = new JSONObject();
JSONObject post = new JSONObject();
JSONObject zhCn = new JSONObject();
zhCn.put("title", title);
JSONArray contentArray = new JSONArray();
for (RichTextItem item : contentList) {
JSONArray contentArrayItem = new JSONArray();
JSONObject element = new JSONObject();
element.put("tag", item.getTag());
if ("a".equals(item.getTag())) {
element.put("text", item.getText());
element.put("href", item.getHref());
} else {
element.put("text", item.getText());
}
if (item.getUnEscape() != null) {
element.put("un_escape", item.getUnEscape());
}
contentArrayItem.add(element);
contentArray.add(contentArrayItem);
}
zhCn.put("content", contentArray);
post.put("zh_cn", zhCn);
content.put("post", post);
message.put("content", content);
log.info("【飞书消息通知】发送飞书消息结果: {}", message);
return sendMessage(message);
} catch (Exception e) {
log.error("【飞书消息通知】发送飞书消息失败,sendRichTextMessage ", e);
}
return false;
}
/**
* 发送卡片消息
*
* @param params 卡片消息参数
* @return 是否发送成功
*/
public boolean sendCardMessage(CardMessageParams params) {
JSONObject message = new JSONObject();
message.put("msg_type", "interactive");
JSONObject card = new JSONObject();
card.put("header", createCardHeader(params.getTitle()));
card.put("elements", createCardElements(params.getContent(), params.getButtons()));
message.put("card", card);
return sendMessage(message);
}
/**
* 创建卡片头部
*
* @param title 标题
* @return 卡片头部对象
*/
protected JSONObject createCardHeader(String title) {
JSONObject header = new JSONObject();
JSONObject titleNode = new JSONObject();
titleNode.put("tag", "plain_text");
titleNode.put("content", title);
header.put("title", titleNode);
header.put("template", "blue");
return header;
}
/**
* 创建卡片元素
*
* @param content 内容
* @param buttons 按钮列表
* @return 卡片元素数组
*/
protected JSONArray createCardElements(String content, List<CardButtonItem> buttons) {
JSONArray elementsArray = new JSONArray();
// 内容部分
JSONObject divElement = new JSONObject();
divElement.put("tag", "div");
JSONObject textContent = new JSONObject();
textContent.put("tag", "lark_md");
textContent.put("content", content);
divElement.put("text", textContent);
elementsArray.add(divElement);
// 按钮部分
if (buttons != null && !buttons.isEmpty()) {
JSONObject actions = new JSONObject();
actions.put("tag", "action");
JSONArray actionsArray = new JSONArray();
for (CardButtonItem button : buttons) {
JSONObject buttonNode = new JSONObject();
buttonNode.put("tag", "button");
JSONObject buttonText = new JSONObject();
buttonText.put("tag", "plain_text");
buttonText.put("content", button.getText());
buttonNode.put("text", buttonText);
buttonNode.put("url", button.getUrl());
buttonNode.put("type", "primary");
actionsArray.add(buttonNode);
}
actions.put("actions", actionsArray);
elementsArray.add(actions);
}
return elementsArray;
}
/**
* 富文本消息内容项
*/
public static class RichTextItem {
private String tag;
private String text;
private String href;
private Boolean unEscape;
public RichTextItem(String tag, String text) {
this.tag = tag;
this.text = text;
}
public RichTextItem(String tag, String text, String href) {
this(tag, text);
this.href = href;
}
public RichTextItem(String tag, String text, Boolean unEscape) {
this(tag, text);
this.unEscape = unEscape;
}
// getters and setters
public String getTag() { return tag; }
public void setTag(String tag) { this.tag = tag; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getHref() { return href; }
public void setHref(String href) { this.href = href; }
public Boolean getUnEscape() { return unEscape; }
public void setUnEscape(Boolean unEscape) { this.unEscape = unEscape; }
}
/**
* 卡片按钮项
*/
public static class CardButtonItem {
private String text;
private String url;
public CardButtonItem(String text, String url) {
this.text = text;
this.url = url;
}
// getters and setters
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
}
/**
* 卡片消息参数类
*/
public static class CardMessageParams {
private String title;
private String content;
private List<CardButtonItem> buttons;
public CardMessageParams() {
}
public CardMessageParams(String title, String content, List<CardButtonItem> buttons) {
this.title = title;
this.content = content;
this.buttons = buttons;
}
// getters and setters
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<CardButtonItem> getButtons() {
return buttons;
}
public void setButtons(List<CardButtonItem> buttons) {
this.buttons = buttons;
}
}
}
- 具体策略实现 BlogDemoMessageStrategy
java
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 博客演示飞书消息策略实现
* 展示如何继承抽象策略类实现具体的消息推送策略
*/
@Component
public class BlogDemoMessageStrategy extends AbstractFeishuMessageStrategy {
// 飞书机器人webhook地址
private static final String WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/7c016607-678e-4a69-9c41-e8ef0eaca031";
/**
* 实现获取webhook地址的方法
*
* @return 飞书机器人webhook地址
*/
@Override
protected String getWebhookUrl() {
return WEBHOOK_URL;
}
/**
* 测试方法,演示各种消息类型的发送
*
* @param args 命令行参数
*/
public static void main(String[] args) {
BlogDemoMessageStrategy strategy = new BlogDemoMessageStrategy();
// 测试发送文本消息
boolean textResult = strategy.sendTextMessage("【博客演示】这是一条来自博客演示项目的测试文本消息");
System.out.println("文本消息发送结果: " + textResult);
// 测试发送富文本消息
List<RichTextItem> richContent = new ArrayList<>();
richContent.add(new RichTextItem("text", "【技术分享】"));
richContent.add(new RichTextItem("a", "查看完整技术文档", "https://tech-docs.example.com"));
richContent.add(new RichTextItem("text", "\n发布时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
boolean richResult = strategy.sendRichTextMessage("技术博客系统通知", richContent);
System.out.println("富文本消息发送结果: " + richResult);
// 测试发送卡片消息
List<CardButtonItem> buttons = new ArrayList<>();
buttons.add(new CardButtonItem("阅读更多", "https://blog.example.com/article/123"));
buttons.add(new CardButtonItem("联系作者", "https://work.example.com/contact/author"));
CardMessageParams cardParams = new CardMessageParams();
cardParams.setTitle("技术博客重要提醒");
cardParams.setContent("**文章发布状态**\n- 状态: 已发布\n- 时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
cardParams.setButtons(buttons);
boolean cardResult = strategy.sendCardMessage(cardParams);
System.out.println("卡片消息发送结果: " + cardResult);
}
}
使用方式
通过简单的实例化和调用即可完成消息推送:
java
// 创建策略实例
BlogDemoMessageStrategy strategy = new BlogDemoMessageStrategy();
// 发送文本消息
strategy.sendTextMessage("【博客演示】测试文本消息");
// 发送富文本消息
List<AbstractFeishuMessageStrategy.RichTextItem> richContent = new ArrayList<>();
richContent.add(new AbstractFeishuMessageStrategy.RichTextItem("text", "【系统通知】"));
boolean richResult = strategy.sendRichTextMessage("系统通知标题", richContent);
// 发送卡片消息
List<AbstractFeishuMessageStrategy.CardButtonItem> buttons = new ArrayList<>();
buttons.add(new AbstractFeishuMessageStrategy.CardButtonItem("查看详情", "https://example.com"));
CardMessageParams cardParams = new CardMessageParams();
cardParams.setTitle("重要提醒");
cardParams.setContent("这是卡片内容");
cardParams.setButtons(buttons);
strategy.sendCardMessage(cardParams);
扩展新的策略
要为新的飞书群创建消息推送策略,只需简单继承 AbstractFeishuMessageStrategy 并提供对应的 webhook 地址:
java
@Component
public class NewTeamMessageStrategy extends AbstractFeishuMessageStrategy {
private static final String WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/new-team-webhook-url";
@Override
protected String getWebhookUrl() {
return WEBHOOK_URL;
}
}
总结:
这种设计的优势在于:
1、高内聚低耦合:核心逻辑与配置分离
2、易于扩展:新增飞书群只需继承抽象类并提供 webhook URL
3、类型安全:使用专用参数类避免运行时类型错误
4、维护简便:统一的错误处理和日志记录机制
通过这套方案,开发者可以快速构建适用于不同业务场景的飞书消息推送服务。