springboot对接钉钉,发送钉钉消息

以下是使用 Spring Boot 对接钉钉发送消息的完整实现方案,包含多种消息类型(文本、链接、Markdown 等)的发送方式:


1. 准备工作

  1. 创建钉钉应用

    • 登录钉钉开放平台
    • 创建企业内部应用(或第三方企业应用)
    • 获取 AppKeyAppSecret
    • 开启「机器人」权限(如需使用自定义机器人则需单独配置)
  2. 安全配置

    • 设置IP白名单(重要!)
    • 启用加签(可选,推荐生产环境使用)

2. 项目配置

2.1 application.yml 配置

bash 复制代码
dingtalk:
  app:
    app-key: your_app_key
    app-secret: your_app_secret
    access-token-url: https://api.dingtalk.com/v1.0/oauth2/accessToken
    robot-webhook: https://oapi.dingtalk.com/robot/send

2.2 配置类

less 复制代码
@Data
@Configuration
@ConfigurationProperties(prefix = "dingtalk.app")
public class DingTalkConfig {
    private String appKey;
    private String appSecret;
    private String accessTokenUrl;
    private String robotWebhook;
}

3. AccessToken 管理

3.1 带缓存的Token管理

ini 复制代码
@Component
public class DingTalkTokenManager {
    private static final Logger logger = LoggerFactory.getLogger(DingTalkTokenManager.class);
    
    @Autowired
    private DingTalkConfig config;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String ACCESS_TOKEN_KEY = "dingtalk:access_token";

    public String getAccessToken() {
        String token = redisTemplate.opsForValue().get(ACCESS_TOKEN_KEY);
        if (StringUtils.isNotBlank(token)) {
            return token;
        }
        return refreshAccessToken();
    }

    private synchronized String refreshAccessToken() {
        String url = config.getAccessTokenUrl();
        Map<String, String> params = new HashMap<>();
        params.put("appKey", config.getAppKey());
        params.put("appSecret", config.getAppSecret());

        try {
            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            HttpEntity<Map<String, String>> request = new HttpEntity<>(params, headers);
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            
            JsonObject json = JsonParser.parseString(response.getBody()).getAsJsonObject();
            String newToken = json.get("accessToken").getAsString();
            int expiresIn = json.get("expireIn").getAsInt();
            
            redisTemplate.opsForValue().set(
                ACCESS_TOKEN_KEY, 
                newToken, 
                expiresIn - 300,  // 提前5分钟过期
                TimeUnit.SECONDS
            );
            
            return newToken;
        } catch (Exception e) {
            logger.error("刷新钉钉Token失败", e);
            throw new RuntimeException("钉钉服务不可用");
        }
    }
}

4. 消息发送服务

4.1 通用消息发送基类

typescript 复制代码
@Service
public class DingTalkService {
    
    @Autowired
    private DingTalkConfig config;
    @Autowired
    private DingTalkTokenManager tokenManager;

    // 发送工作通知(需用户ID)
    public void sendToUser(String userId, String msg) {
        String accessToken = tokenManager.getAccessToken();
        String url = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend";
        
        Map<String, Object> params = new HashMap<>();
        params.put("robotCode", config.getAppKey());
        params.put("userIds", Collections.singletonList(userId));
        params.put("msgKey", "sampleText");
        params.put("msgParam", "{"content":"" + msg + ""}");
        
        sendRequest(url, accessToken, params);
    }

    // 发送群机器人消息(无需用户ID)
    public void sendRobotMessage(Object messageBody, String secret) {
        long timestamp = System.currentTimeMillis();
        String sign = generateSign(timestamp, secret);
        String url = config.getRobotWebhook() + "?access_token=" + tokenManager.getAccessToken()
                   + "&timestamp=" + timestamp + "&sign=" + sign;

        sendRequest(url, null, messageBody);
    }

    private void sendRequest(String url, String token, Object params) {
        try {
            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            if (token != null) {
                headers.set("x-acs-dingtalk-access-token", token);
            }
            
            HttpEntity<Object> request = new HttpEntity<>(params, headers);
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            handleResponse(response.getBody());
        } catch (Exception e) {
            throw new RuntimeException("钉钉消息发送失败", e);
        }
    }

    // 生成加签(需要时使用)
    private String generateSign(Long timestamp, String secret) {
        String stringToSign = timestamp + "\n" + secret;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return URLEncoder.encode(new String(Base64.getEncoder().encode(signData)), "UTF-8");
    }

    private void handleResponse(String responseBody) {
        JsonObject json = JsonParser.parseString(responseBody).getAsJsonObject();
        if (json.has("errcode") && json.get("errcode").getAsInt() != 0) {
            throw new RuntimeException("钉钉接口错误: " + responseBody);
        }
    }
}

5. 消息类型封装

5.1 文本消息

typescript 复制代码
public class TextMessage {
    private String msgtype = "text";
    private TextContent text;
    private AtInfo at;

    public TextMessage(String content, List<String> atMobiles) {
        this.text = new TextContent(content);
        this.at = new AtInfo(atMobiles);
    }

    @Data
    @AllArgsConstructor
    private static class TextContent {
        private String content;
    }

    @Data
    @AllArgsConstructor
    private static class AtInfo {
        private List<String> atMobiles;
        private boolean isAtAll = false;
    }
}

5.2 Markdown消息

arduino 复制代码
public class MarkdownMessage {
    private String msgtype = "markdown";
    private MarkdownContent markdown;
    private AtInfo at;

    public MarkdownMessage(String title, String text, List<String> atMobiles) {
        this.markdown = new MarkdownContent(title, text);
        this.at = new AtInfo(atMobiles);
    }

    @Data
    @AllArgsConstructor
    private static class MarkdownContent {
        private String title;
        private String text;
    }

    @Data
    @AllArgsConstructor
    private static class AtInfo {
        private List<String> atMobiles;
        private boolean isAtAll = false;
    }
}

6. 控制器调用示例

less 复制代码
@RestController
@RequestMapping("/dingtalk")
public class DingTalkController {

    @Autowired
    private DingTalkService dingTalkService;

    // 发送文本消息
    @PostMapping("/send-text")
    public ResponseEntity<String> sendText(@RequestParam String userId) {
        dingTalkService.sendToUser(userId, "您的订单已处理完成");
        return ResponseEntity.ok("消息已发送");
    }

    // 发送机器人Markdown消息
    @PostMapping("/send-markdown")
    public ResponseEntity<String> sendMarkdown(@RequestParam String secret) {
        MarkdownMessage message = new MarkdownMessage(
            "项目通知",
            "### 服务器状态告警\n> **CPU使用率**: 95%\n> **内存使用率**: 80%\n> 请及时处理!",
            Collections.singletonList("18812345678")
        );
        dingTalkService.sendRobotMessage(message, secret);
        return ResponseEntity.ok("机器人消息已发送");
    }
}

7. 注意事项

  1. 消息类型限制

    • 单次消息最大长度:文本消息20000字符,Markdown消息5000字符
    • 频率限制:每个机器人每分钟最多发送20条消息
  2. 安全策略

    • 推荐使用加签方式(与钉钉机器人设置一致)
    • 敏感信息不要明文传输
  3. 用户识别

    • 通过手机号或用户ID发送需确保用户已在组织内
    • 使用userid需先通过钉钉API获取用户信息
  4. 错误代码处理

    • 300001:无效的access_token
    • 310000:消息内容超过长度限制
    • 330001:机器人被禁用

8. 高级功能扩展

  1. 消息卡片

    arduino 复制代码
    public class ActionCardMessage {
        private String msgtype = "actionCard";
        private String title;
        private String text;
        private String singleTitle;
        private String singleURL;
    }
  2. 异步消息

    typescript 复制代码
    @Async
    public void asyncSendMessage(String userId, String message) {
        dingTalkService.sendToUser(userId, message);
    }
  3. 消息模板

    typescript 复制代码
    public class TemplateMessage {
        private String msgtype = "template";
        private String templateId;
        private Map<String, String> data;
    }

9. 完整调用流程

  1. 获取用户ID(通过钉钉API或扫码登录)
  2. 选择消息类型并构造消息体
  3. 获取有效的access_token
  4. 调用钉钉消息接口发送
  5. 处理发送结果(成功/失败)

通过以上步骤即可实现 Spring Boot 与钉钉的消息对接,建议根据实际业务需求选择适合的消息类型和安全策略。

相关推荐
八了个戒1 小时前
「数据可视化 D3系列」入门第三章:深入理解 Update-Enter-Exit 模式
开发语言·前端·javascript·数据可视化
noravinsc1 小时前
html页面打开后中文乱码
前端·html
小满zs2 小时前
React-router v7 第四章(路由传参)
前端·react.js
angushine2 小时前
Gateway获取下游最终响应码
java·开发语言·gateway
爱的叹息2 小时前
关于 JDK 中的 jce.jar 的详解,以及与之功能类似的主流加解密工具的详细对比分析
java·python·jar
小陈同学呦2 小时前
聊聊双列瀑布流
前端·javascript·面试
一一Null2 小时前
Token安全存储的几种方式
android·java·安全·android studio
来自星星的坤2 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc2 小时前
RaabitMQ 快速入门
java·后端·rabbitmq