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 与钉钉的消息对接,建议根据实际业务需求选择适合的消息类型和安全策略。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐