Spring Boot 集成飞书推送超详细教程:文本消息、签名校验、封装工具类一篇搞定

在企业项目里,消息通知几乎是刚需。

比如订单支付成功要通知运营,接口报错要通知开发,定时任务执行失败要通知值班人员,服务器告警也要第一时间推到群里。很多团队第一反应是发短信、发邮件,但真到了落地阶段,飞书群消息反而更直接,成本更低,接入也更快。

如果你的项目是 Spring Boot,那么最适合快速落地的方案,通常就是接入飞书自定义机器人 。它本质上是一个群聊级别的 Webhook,你的服务端只需要向这个地址发送 HTTP POST 请求,就可以把文本、富文本、图片甚至卡片消息推送到飞书群中。飞书官方文档也明确说明了,自定义机器人适合群聊消息推送,通过 webhook 地址即可发送多种类型消息;如果你需要更复杂的系统集成能力,则通常要考虑"应用机器人"方案。(飞书开放平台)

这篇文章我不讲虚的,直接带你从 0 到 1 完成 Spring Boot 集成飞书推送,包括:

  1. 飞书机器人怎么创建

  2. Spring Boot 项目怎么配置

  3. 文本消息怎么发

  4. 开启"签名校验"之后怎么处理

  5. 怎么封装成企业项目里可复用的工具类

  6. 常见报错怎么排查

  7. 最后给你一个可以直接上线的实战代码结构


一、为什么企业项目里推荐先接飞书自定义机器人

对于大多数"群通知"场景,比如异常告警、业务提醒、订单通知、审批提醒,自定义机器人已经足够用了。它的优点很明显:

  • 接入简单,不需要你先做一整套飞书应用体系

  • 只要拿到 webhook 地址,就能直接发消息

  • 支持文本、富文本、图片、群名片、交互式消息卡片等消息类型

  • 很适合做项目里的"告警通知组件"或者"消息推送基础设施"

飞书官方说明中提到,自定义机器人可以直接通过 webhook 地址推送消息,支持文本、富文本、图片等多种类型;而"应用机器人"更偏向开放接口调用、复杂系统集成。(飞书开放平台)

所以如果你这篇文章是写给 CSDN 读者看,我建议你把主线聚焦在:

Spring Boot + 飞书自定义机器人 + Webhook 推送

这样最实用,也最容易让读者复现。


二、接入前先搞清楚两种方案

很多人一上来就会混淆"自定义机器人"和"应用机器人",这里先给你讲透。

1. 自定义机器人

它是直接在飞书群里添加的,拿到一个 webhook 地址后,你的服务端就能往群里发消息。适合快速通知、监控报警、业务提醒。(飞书开放平台)

2. 应用机器人

它是在飞书开放平台创建应用,然后给应用开通机器人能力,可以调用更多 OpenAPI,适合更完整的业务集成。飞书官方的机器人概述也把这两者做了区分:应用机器人适合系统集成,自定义机器人更适合群聊消息推送。(飞书开放平台)

本文选择的是最容易落地的方案:自定义机器人。


三、先在飞书里创建自定义机器人

这一步不写清楚,很多人连 webhook 都拿不到。

操作步骤

  1. 打开飞书客户端

  2. 进入你要接收通知的群聊

  3. 打开群设置

  4. 找到"群机器人"

  5. 选择"添加机器人"

  6. 选择"自定义机器人"

  7. 填写机器人名称和描述

  8. 创建成功后复制 webhook 地址

飞书官方 FAQ 说明,自定义机器人需要在飞书客户端的指定群聊内添加;不是去开发者后台建。(飞书开放平台)

创建完成后,你通常还能看到几项安全设置,比如:

  • 自定义关键词

  • IP 白名单

  • 签名校验

飞书官方文档明确提到,自定义机器人支持 IP 白名单和签名校验等安全设置,用来保证调用安全。(飞书开放平台)


四、Spring Boot 项目准备

下面我们开始写代码。

1. Maven 依赖

这里我建议用 spring-boot-starter-web,再配合 Jackson 做 JSON 序列化即可。为了方便写 HTTP 请求,也可以直接用 RestTemplate。如果你项目里已经在用 hutoolokhttp,也可以替换。

复制代码
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Lombok,可选 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- 单元测试,可选 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

五、配置文件怎么写

一般我们会把 webhook 和签名密钥放到配置文件里。

application.yml

复制代码
feishu:
  webhook: https://open.feishu.cn/open-apis/bot/v2/hook/你的webhook
  enabled: true
  secret: 你的签名密钥

说明一下:

  • webhook:飞书机器人地址

  • enabled:是否启用,方便区分测试环境和生产环境

  • secret:如果你开启了签名校验,这里要配置对应密钥;如果没开启,可以为空


六、先定义配置类

复制代码
package com.example.demo.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "feishu")
public class FeishuProperties {

    /**
     * 机器人 webhook 地址
     */
    private String webhook;

    /**
     * 是否启用
     */
    private Boolean enabled = true;

    /**
     * 签名密钥,可为空
     */
    private String secret;
}

七、先跑通最基础的文本消息推送

飞书自定义机器人发送消息,本质上就是向 webhook 发一个 POST 请求。常见的消息体里会带 msg_type,文本消息一般对应 text 类型。飞书官方文档中也给出了自定义机器人支持的消息类型,包含文本、富文本、图片等;在相关资料里也能看到 msg_type 常用取值包括 textpostimageinteractive 等。(飞书开放平台)

1. 定义请求对象

复制代码
package com.example.demo.feishu.dto;

import lombok.Data;

@Data
public class FeishuTextRequest {

    private String msg_type;
    private TextContent content;

    @Data
    public static class TextContent {
        private String text;
    }
}

2. 编写发送服务

复制代码
package com.example.demo.feishu.service;

import com.example.demo.config.FeishuProperties;
import com.example.demo.feishu.dto.FeishuTextRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

@Service
@RequiredArgsConstructor
public class FeishuBotService {

    private final FeishuProperties feishuProperties;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RestTemplate restTemplate = new RestTemplate();

    public String sendText(String text) {
        if (Boolean.FALSE.equals(feishuProperties.getEnabled())) {
            return "飞书推送未启用";
        }

        FeishuTextRequest request = new FeishuTextRequest();
        request.setMsg_type("text");

        FeishuTextRequest.TextContent content = new FeishuTextRequest.TextContent();
        content.setText(text);
        request.setContent(content);

        return doPost(request);
    }

    private String doPost(Object requestBody) {
        try {
            String json = objectMapper.writeValueAsString(requestBody);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<String> entity = new HttpEntity<>(json, headers);

            ResponseEntity<String> response = restTemplate.postForEntity(
                    feishuProperties.getWebhook(),
                    entity,
                    String.class
            );

            return response.getBody();
        } catch (JsonProcessingException e) {
            throw new RuntimeException("飞书消息序列化失败", e);
        } catch (Exception e) {
            throw new RuntimeException("飞书消息发送失败", e);
        }
    }
}

3. 写一个测试接口

复制代码
package com.example.demo.controller;

import com.example.demo.feishu.service.FeishuBotService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/feishu")
@RequiredArgsConstructor
public class FeishuTestController {

    private final FeishuBotService feishuBotService;

    @GetMapping("/send")
    public String send(@RequestParam String msg) {
        return feishuBotService.sendText(msg);
    }
}

启动项目后访问:

复制代码
http://localhost:8080/feishu/send?msg=SpringBoot接入飞书成功

如果群里成功收到消息,说明最基础的链路已经打通了。


八、企业项目里一定要加签名校验

上面只是最简版。

真正线上使用时,我强烈建议你把飞书机器人的签名校验 打开。因为 webhook 一旦泄露,别人就可能往你的群里乱发消息。飞书官方文档明确提到,自定义机器人支持签名校验;开启后,请求中需要带上 timestampsign 字段。其签名规则是:将 timestamp + "\n" + 密钥 作为签名字符串,使用 HmacSHA256 算法计算,再做 Base64 编码。(飞书开放平台)

这一步非常关键,也是很多教程写得不够清楚的地方。


九、签名算法怎么写

1. 定义带签名的通用请求对象

复制代码
package com.example.demo.feishu.dto;

import lombok.Data;

@Data
public class FeishuSignedRequest<T> {

    /**
     * 时间戳
     */
    private String timestamp;

    /**
     * 签名
     */
    private String sign;

    /**
     * 消息类型
     */
    private String msg_type;

    /**
     * 消息内容
     */
    private T content;
}

2. 编写签名工具类

复制代码
package com.example.demo.feishu.util;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class FeishuSignUtil {

    private static final String HMAC_SHA256 = "HmacSHA256";

    public static String genSign(String timestamp, String secret) {
        try {
            String stringToSign = timestamp + "\n" + secret;
            Mac mac = Mac.getInstance(HMAC_SHA256);
            SecretKeySpec spec = new SecretKeySpec(
                    stringToSign.getBytes(StandardCharsets.UTF_8),
                    HMAC_SHA256
            );
            mac.init(spec);
            byte[] signData = mac.doFinal(new byte[]{});
            return Base64.getEncoder().encodeToString(signData);
        } catch (Exception e) {
            throw new RuntimeException("生成飞书签名失败", e);
        }
    }
}

3. 改造发送服务

复制代码
package com.example.demo.feishu.service;

import com.example.demo.config.FeishuProperties;
import com.example.demo.feishu.dto.FeishuSignedRequest;
import com.example.demo.feishu.util.FeishuSignUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class FeishuBotService {

    private final FeishuProperties feishuProperties;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RestTemplate restTemplate = new RestTemplate();

    public String sendText(String text) {
        if (Boolean.FALSE.equals(feishuProperties.getEnabled())) {
            return "飞书推送未启用";
        }

        Map<String, String> content = new HashMap<>();
        content.put("text", text);

        return send("text", content);
    }

    public String send(String msgType, Object content) {
        try {
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);

            FeishuSignedRequest<Object> request = new FeishuSignedRequest<>();
            request.setMsg_type(msgType);
            request.setContent(content);

            if (StringUtils.hasText(feishuProperties.getSecret())) {
                request.setTimestamp(timestamp);
                request.setSign(FeishuSignUtil.genSign(timestamp, feishuProperties.getSecret()));
            }

            String json = objectMapper.writeValueAsString(request);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);

            HttpEntity<String> entity = new HttpEntity<>(json, headers);

            ResponseEntity<String> response = restTemplate.postForEntity(
                    feishuProperties.getWebhook(),
                    entity,
                    String.class
            );

            return response.getBody();
        } catch (JsonProcessingException e) {
            throw new RuntimeException("飞书消息序列化失败", e);
        } catch (Exception e) {
            throw new RuntimeException("飞书消息发送失败", e);
        }
    }
}

十、发送富文本消息

很多读者写完文本消息就结束了,但真实项目里,纯文本往往不够用。比如你想发:

  • 项目名

  • 环境名

  • 错误时间

  • 错误堆栈

  • 点击链接查看详情

这时候富文本更适合。

飞书官方文档中说明,自定义机器人支持富文本消息;相关说明中 msg_type 可使用 post。(飞书开放平台)

示例代码

复制代码
public String sendPost() {
    Map<String, Object> zhCn = new HashMap<>();
    zhCn.put("title", "系统告警通知");

    Object[][] content = new Object[][]{
            {
                    Map.of("tag", "text", "text", "服务名:订单服务")
            },
            {
                    Map.of("tag", "text", "text", "级别:ERROR")
            },
            {
                    Map.of("tag", "text", "text", "时间:2026-03-26 10:30:00")
            },
            {
                    Map.of("tag", "a", "text", "点击查看详情", "href", "https://your-domain.com/log/detail/1001")
            }
    };

    zhCn.put("content", content);

    Map<String, Object> post = new HashMap<>();
    post.put("zh_cn", zhCn);

    return send("post", post);
}

这个结构本质上就是告诉飞书:

我发送的是 post 富文本消息,语言版本是 zh_cn,标题是"系统告警通知",正文里每一行放哪些组件。


十一、发送卡片消息

如果你只是做普通通知,文本和富文本就够了。但如果你想把消息做得更像"运维告警面板"或者"审批通知卡片",那就可以用消息卡片

飞书官方文档说明,自定义机器人也支持发送卡片消息;同时飞书卡片是一种结构化内容载体,适合做信息展示和轻量交互。(飞书开放平台)

简单卡片示例

复制代码
public String sendCard() {
    Map<String, Object> header = Map.of(
            "title", Map.of("tag", "plain_text", "content", "订单支付通知"),
            "template", "blue"
    );

    Map<String, Object> element1 = Map.of(
            "tag", "div",
            "text", Map.of(
                    "tag", "lark_md",
                    "content", "**订单号:** 202603260001\n**支付状态:** 成功\n**支付金额:** ¥99.00"
            )
    );

    Map<String, Object> element2 = Map.of(
            "tag", "action",
            "actions", new Object[]{
                    Map.of(
                            "tag", "button",
                            "text", Map.of("tag", "plain_text", "content", "查看订单"),
                            "type", "primary",
                            "url", "https://your-domain.com/order/detail/202603260001"
                    )
            }
    );

    Map<String, Object> card = new HashMap<>();
    card.put("header", header);
    card.put("elements", new Object[]{element1, element2});

    return send("interactive", card);
}

这个效果会比普通文本好很多,特别适合:

  • 订单通知

  • 发布提醒

  • 服务器告警

  • 审批消息

  • 定时任务执行结果


十二、把它封装成项目里的公共能力

如果你是做企业项目,千万别把飞书推送代码散落在各个业务类里。正确做法是做一层统一封装。

我建议你这样分层:

复制代码
com.example.demo
├── config
│   └── FeishuProperties.java
├── feishu
│   ├── dto
│   │   ├── FeishuTextRequest.java
│   │   └── FeishuSignedRequest.java
│   ├── service
│   │   └── FeishuBotService.java
│   └── util
│       └── FeishuSignUtil.java
└── controller
    └── FeishuTestController.java

如果你的项目已经比较大了,可以进一步抽成:

  • message-api:定义消息接口

  • message-feishu:飞书实现

  • message-wechat:企业微信或微信公众号实现

  • message-email:邮件实现

这样后续替换通知渠道时,不需要改业务代码,只要改消息实现。


十三、推荐你在业务里怎么用

飞书推送最常见的使用方式,不是给前台用户发消息,而是给内部团队通知

1. 异常告警

复制代码
try {
    // 业务代码
} catch (Exception e) {
    feishuBotService.sendText("【系统异常】订单服务发生异常:" + e.getMessage());
    throw e;
}

2. 支付成功通知

复制代码
feishuBotService.sendText(
    "【支付通知】用户 10001 支付成功,订单号:202603260001,金额:99.00 元"
);

3. 定时任务结果通知

复制代码
feishuBotService.sendText(
    "【定时任务】会员过期扫描任务执行完成,处理 125 条记录,耗时 3.2 秒"
);

4. 运维告警通知

复制代码
feishuBotService.sendText(
    "【告警】生产环境 Redis 连接数过高,请立即排查"
);

十四、频控问题一定要知道

这个点非常重要,很多人线上一出问题,日志炸了,然后代码疯狂往飞书推送,最后直接触发限流。

飞书官方文档里给出了自定义机器人的频控规则:单租户单机器人 100 次/分钟,5 次/秒 。超限后消息会发送失败。(飞书开放平台)

所以线上建议你至少做两层保护:

1. 本地限流

例如同一种错误 1 分钟内只推一次。

2. 去重推送

同一异常堆栈不要连续推送 100 条。

3. 异步发送

不要在主业务线程里同步卡住,尤其是高并发接口。

4. 告警聚合

把 1 分钟内的同类错误汇总后一次发送。

这一步做了,你的飞书告警体系才算真正能用。


十五、常见报错排查

下面我把最常见的问题直接给你总结出来。

1. webhook 地址错误

表现:请求发出去了,但飞书群没收到。

排查:检查 webhook 是否复制完整,是否多了空格。

2. 没开机器人

表现:一直返回失败。

排查:确认机器人已经成功添加到目标群。

3. 签名错误

表现:开启签名校验后发送失败。

排查重点:

  • timestamp 是否传了

  • sign 是否传了

  • 签名算法是否正确

  • 密钥是否和飞书后台一致

  • 时间戳是否使用秒级,而不是毫秒级

飞书官方文档明确要求,开启签名验证后,请求中需要附带 timestampsign;签名算法基于 timestamp + "\n" + 密钥,再使用 HmacSHA256 和 Base64 处理。(飞书开放平台)

4. 触发频控

表现:前几条正常,后面突然发送失败。

排查:检查是否短时间内推送过多消息。官方对自定义机器人有明确频控要求。(飞书开放平台)

5. JSON 结构不正确

表现:HTTP 200 但飞书不展示,或者接口直接报参数错误。

排查:检查 msg_typecontent 结构是否匹配。文本、富文本、卡片对应的 JSON 结构不一样。飞书官方文档对不同消息类型的内容结构有专门说明。(飞书开放平台)


十六、生产环境最佳实践

如果你想把这篇文章写得更"像大厂实战",这一段一定要保留。

1. webhook 不要硬编码

统一放到配置中心,或者至少放到环境变量里。

2. 不同环境分不同机器人

开发环境、测试环境、生产环境分别使用不同群,不然测试消息会污染生产群。

3. 做统一消息网关

不要在订单模块、支付模块、会员模块各写一套飞书代码。统一做成 MessageService

4. 失败重试要克制

网络抖动可以重试,但不要无限重试,更不要在高频异常场景中疯狂补发,否则会更容易命中频控。

5. 敏感信息脱敏

错误通知里不要直接发用户身份证、手机号、token、密钥等敏感信息。

6. 告警要分级

  • INFO:普通业务通知

  • WARN:业务异常提醒

  • ERROR:系统错误

  • FATAL:核心服务不可用

建议不同级别走不同群,或者不同消息模板。


十七、完整工具类版本

如果你想让读者复制就能用,那最好给一个整合版。下面这个版本可以直接作为公共组件放进项目。

复制代码
package com.example.demo.feishu.service;

import com.example.demo.config.FeishuProperties;
import com.example.demo.feishu.util.FeishuSignUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class FeishuBotService {

    private final FeishuProperties feishuProperties;
    private final RestTemplate restTemplate = new RestTemplate();
    private final ObjectMapper objectMapper = new ObjectMapper();

    public String sendText(String text) {
        Map<String, String> content = new HashMap<>();
        content.put("text", text);
        return send("text", content);
    }

    public String sendPost(String title, Object[][] contentArray) {
        Map<String, Object> zhCn = new HashMap<>();
        zhCn.put("title", title);
        zhCn.put("content", contentArray);

        Map<String, Object> post = new HashMap<>();
        post.put("zh_cn", zhCn);

        return send("post", post);
    }

    public String sendCard(Map<String, Object> card) {
        return send("interactive", card);
    }

    public String send(String msgType, Object content) {
        if (Boolean.FALSE.equals(feishuProperties.getEnabled())) {
            return "飞书推送未启用";
        }

        try {
            Map<String, Object> body = new HashMap<>();
            body.put("msg_type", msgType);
            body.put("content", content);

            if ("interactive".equals(msgType)) {
                body.remove("content");
                body.put("card", content);
            }

            if (StringUtils.hasText(feishuProperties.getSecret())) {
                String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
                String sign = FeishuSignUtil.genSign(timestamp, feishuProperties.getSecret());
                body.put("timestamp", timestamp);
                body.put("sign", sign);
            }

            String json = objectMapper.writeValueAsString(body);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<String> entity = new HttpEntity<>(json, headers);

            ResponseEntity<String> response = restTemplate.postForEntity(
                    feishuProperties.getWebhook(),
                    entity,
                    String.class
            );

            return response.getBody();
        } catch (Exception e) {
            throw new RuntimeException("飞书推送失败", e);
        }
    }
}

十八、这篇文章的总结

如果你是第一次做 Spring Boot 集成飞书推送,你只要记住下面这条主线就够了:

创建飞书自定义机器人 → 拿到 webhook → Spring Boot 发 HTTP POST → 文本消息先跑通 → 再加签名校验 → 最后封装成公共通知组件。

飞书官方资料表明,自定义机器人非常适合群聊消息推送,通过 webhook 即可发送多种消息类型;而开启签名校验后,需要额外带上 timestampsign,并遵守相应频控规则。(飞书开放平台)

对于大多数 Spring Boot 项目来说,这种接入方式已经足够应对:

  • 接口异常告警

  • 支付结果通知

  • 订单状态提醒

  • 审批流程通知

  • 定时任务执行结果推送

  • 运维报警

相关推荐
重庆小透明2 小时前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展
RuoyiOffice2 小时前
企业请假销假系统设计实战:一张表、一套流程、两段生命周期——BPM节点驱动的表单变形术
java·spring·uni-app·vue·产品运营·ruoyi·anti-design-vue
鹤旗2 小时前
While语句,do-while语句,for语句
java·jvm·算法
小碗羊肉2 小时前
【从零开始学Java | 第十八篇】BigInteger
java·开发语言·新手入门
sky wide2 小时前
[特殊字符] Docker Swarm 集群搭建指南
java·docker·容器
wuqingshun3141592 小时前
谈谈你对springAop动态代理的理解?
java·jvm
执笔画流年呀2 小时前
PriorityQueue(堆)续集
java·开发语言
武超杰2 小时前
Spring Boot入门教程
java·spring boot·后端
左左右右左右摇晃2 小时前
JDK 1.7 ConcurrentHashMap——分段锁
java·开发语言·笔记