尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot对接钉钉机器人:从加签到消息推送的完整实战指南

SpringBoot对接钉钉机器人:从加签到消息推送的完整实战指南 SpringBoot对接钉钉机器人这件事我在项目里已经反复折腾过好几轮了。最开始只是想让系统异常的时候能第一时间提醒我结果越做越深入从最简单的文本消息到markdown卡片、定时日报、异步告警基本上把钉钉自定义机器人的玩法都摸了一遍。如果你正打算在自己的SpringBoot项目里接入钉钉消息推送或者已经在接但被签名、限制、消息格式这些问题卡住了这篇文章应该能帮你少走不少弯路。我会把完整的实现思路、关键代码、以及实测踩过的坑都整理出来。1. 为什么团队最后选了钉钉机器人做消息推送很多业务系统跑着跑着都会面临同一个问题系统内部发生了一些需要人关注的事情比如订单支付回调成功、定时任务执行失败、服务器磁盘快满了这些信息怎么触达给对应的负责人先拉一张我在项目里实际做过的选型对比你就明白为什么钉钉机器人是当前性价比最高的选择之一。方案接入成本触达及时性是否收费适用场景短信高需对接运营商或短信平台有资费还有签名审核很高按条收费发给用户的验证码、重要业务通知邮件低Spring自带JavaMailSender一般容易进垃圾箱免费日报、周报、不敏感的系统报告企业微信机器人中需要企业内部应用审核高免费公司内部已经深度使用企业微信的场景钉钉机器人极低只需一个Webhook地址高群成员都会收到艾特提醒免费技术告警、运维通知、开发协作群我当时的场景是系统没有面对外部用户的强通知需求主要是内部研发、运维、业务运营需要接收系统事件消息团队日常沟通也都在钉钉群里。这种情况下短信和邮件明显不贴合而钉钉机器人只需要在一个钉钉群里添加一个自定义机器人拿一个Webhook地址后端发一条HTTP请求就能把消息推到群里。这个方案真正打动我的点有三个第一零成本接入。不需要申请开放平台应用不需要回调接口只要群里有管理权限的人添加机器人即可后端完全不接触钉钉复杂的OAuth2.0授权流程。第二消息形态丰富。钉钉机器人支持的msgtype有text、markdown、link、actionCard、feedCard。这意味着不只能发干巴巴的一句话还可以发带标题、带图片、带操作按钮的卡片消息。日常告警和日报展示都可以做得比较美观。第三触达可靠。消息发到钉钉群里只要群成员没有关闭免打扰基本都能在几秒内看到。相比邮件被埋进收件箱群消息的提醒效果强太多了。所以如果你面临类似的需求场景我的建议顺序是内部可沟通场景优先选钉钉机器人或企业微信机器人对外强通知再考虑短信通道。后面所有实战内容都基于SpringBoot但思路本身是通用的你用任何HTTP客户端都行。2. SpringBoot接入前的准备工作配好机器人和安全策略很多人急着写代码结果连钉钉那边的机器人都没配明白后面自然报一堆错。接入钉钉机器人其实分为两端钉钉端的配置和SpringBoot端的开发。2.1 在钉钉群里创建一个自定义机器人这一步的入口路径要记清楚我刚开始也找了好一会儿打开钉钉群 → 右上角设置或群设置图标 → 智能群助手 → 添加机器人 → 选择自定义类型 → 填写机器人名称比如订单系统告警和头像 → 完成添加。创建完成后钉钉会给你一个Webhook地址形如https://oapi.dingtalk.com/robot/send?access_tokenxxxxxxxxxx这个access_token就相当于你群机器人的身份凭证。任何人拿到这个完整地址都能往你的群发消息。所以它的安全级别很高不能直接写进前端页面或者公开仓库。最佳实践是放进SpringBoot的配置文件并且在配置中心或者本地配置中加入环境隔离有条件的直接放到环境变量或密钥管理服务里。注意这个access_token并不需要通过钉钉开放平台获取它就是机器人的唯一标志。只要群不解散、机器人不删除token就一直是可用的。2.2 安全设置选哪种直接影响代码怎么写添加自定义机器人时有三种安全设置可以多选但我的建议是搞清楚每种策略的差异后再决定配几项自定义关键字要求消息内容里必须包含你设定的关键词比如告警不然钉钉拒绝推送。这种方式最简单但只防君子不防小人且你的业务消息里都要强制塞这个词不灵活。加签钉钉会给你一个Secret发送请求时需要在URL上拼接timestamp和sign参数。安全级别最高外部伪造请求的难度很大。IP地址段只允许来自指定IP的请求适合后端服务IP固定的场景。我实测下来最推荐的方式是加签。最关键的原因是你的请求来自公司的测试环境、生产环境服务器生产服务器可能在不同的云厂商这样会降低IP白名单的适用性。而加签只需要在服务端做一次HmacSHA256运算代码量很小安全性却有明显提升。提醒如果你同时配置了多个安全策略钉钉端的要求是同时满足也就是消息不仅要通过关键词校验还要通过加签校验最后还得来自白名单IP任何一个不过都会被拦截。所以不要盲目全开我建议一般场景开加签就够了。2.3 SpringBoot工程侧的必要准备在代码这边其实不需要额外引入复杂的钉钉SDK。钉钉机器人本身就是对HTTP接口的简单调用所以SpringBoot项目里只需要有HTTP客户端能力。我通常的做法是选Spring自带的RestTemplate为什么选它而不是Feign、WebClient因为消息推送属于典型的主动外呼操作用一个轻量的同步HTTP客户端就能满足需求RestTemplate在场外调用场景下代码最简洁。在SpringBoot 2.x时代项目中它天然可用配置也简单。准备好这几样之后你就可以开始写真正的推送逻辑了。整个核心其实就是一个构造HTTP请求发送JSON消息体的过程只是加签这一步稍微需要点算法知识。3. 封装一个可复用的钉钉推送Service把常用消息类型全部跑通很多文章只丢一个text消息的Demo真到业务里要用markdown就抓瞎。我建议直接把Service层实现成一个具备多类型支持的工具一次封装后续所有业务随便调。3.1 加签请求URL是第一个核心点如果你选择了加签安全设置那么发送请求时不能直接使用钉钉给你的Webhook地址需要动态拼接出带签名的新URL。算法规则很简单取当前毫秒级时间戳timestamp用timestamp \n Secret作为签名字符串以HmacSHA256算法计算得到签名再对签名做Base64编码最后对Base64后的字符串做一次URLEncode拼接到原Webhook后面。我直接贴一份完整可跑的代码import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; /** * 钉钉机器人签名工具 */ public class DingTalkSignUtil { public static String generateSignedUrl(String webhookUrl, String secret) throws Exception { long timestamp System.currentTimeMillis(); 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)); String sign URLEncoder.encode( Base64.getEncoder().encodeToString(signData), UTF-8 ); String separator webhookUrl.contains(?) ? : ?; return webhookUrl separator timestamp timestamp sign sign; } }这里最容易踩的坑是最后那个URLEncoder.encode。很多人算完签名直接拼URL结果出现号、/号、号这些特殊字符钉钉端做参数解析时签名校验失败。必须对Base64后的结果再编码一次否则时好时坏这种问题又很难定位。另外注意timestamp一定要用毫秒值别用秒值否则钉钉会认为签名无效。3.2 封装一个DingTalkRobotClient支持多种消息类型我一般把发送逻辑拆成两部分一个通用的sendRawMessage(String signUrl, MapString, Object messageBody)方法以及各个具体消息类型的便捷方法。先看基础的消息结构。钉钉机器人最常用的消息类型有四种我按实际使用频率排序msgtype类型主要用途消息体结构简介text普通文本通知用于最简单的日志告警{msgtype:text,text:{content:消息内容},at:{atMobiles:[],isAtAll:false}}markdown日报、数据统计、多一点排版的推送{msgtype:markdown,markdown:{title:标题,text:markdown文本}}link带封面图和跳转链接的消息{msgtype:link,link:{title:标题,text:描述,picUrl:图片,messageUrl:跳转URL}}actionCard带按钮操作的消息可以跳转到处理页面{msgtype:actionCard,actionCard:{title:标题,text:内容,singleTitle:按钮文案,singleURL:跳转地址}}基于这个结构我直接展示一段完整的Client封装代码。这里用ObjectMapper来构造JSON比手动拼字符串更安全避免内容里出现引号导致整个JSON坏掉。import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.*; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 钉钉机器人消息客户端 */ public class DingTalkRobotClient { private final RestTemplate restTemplate new RestTemplate(); private final ObjectMapper objectMapper new ObjectMapper(); private static final String CONTENT_TYPE MediaType.APPLICATION_JSON_VALUE; /** * 发送纯文本消息 */ public String sendText(String webhookUrl, String secret, String content, boolean isAtAll) throws Exception { MapString, Object message new HashMap(); message.put(msgtype, text); MapString, Object text new HashMap(); text.put(content, content); MapString, Object at new HashMap(); at.put(atMobiles, Collections.emptyList()); at.put(isAtAll, isAtAll); message.put(text, text); message.put(at, at); return sendRawMessage(webhookUrl, secret, message); } /** * 发送markdown消息 */ public String sendMarkdown(String webhookUrl, String secret, String title, String markdownText, boolean isAtAll) throws Exception { MapString, Object message new HashMap(); message.put(msgtype, markdown); MapString, Object markdown new HashMap(); markdown.put(title, title); markdown.put(text, markdownText); MapString, Object at new HashMap(); at.put(atMobiles, Collections.emptyList()); at.put(isAtAll, isAtAll); message.put(markdown, markdown); message.put(at, at); return sendRawMessage(webhookUrl, secret, message); } /** * 发送link消息 */ public String sendLink(String webhookUrl, String secret, String title, String text, String picUrl, String messageUrl) throws Exception { MapString, Object message new HashMap(); message.put(msgtype, link); MapString, Object link new HashMap(); link.put(title, title); link.put(text, text); link.put(picUrl, picUrl); link.put(messageUrl, messageUrl); message.put(link, link); return sendRawMessage(webhookUrl, secret, message); } /** * 发送actionCard消息支持按钮跳转 */ public String sendActionCard(String webhookUrl, String secret, String title, String text, String buttonTitle, String buttonUrl) throws Exception { MapString, Object actionCard new HashMap(); actionCard.put(title, title); actionCard.put(text, text); actionCard.put(singleTitle, buttonTitle); actionCard.put(singleURL, buttonUrl); MapString, Object message new HashMap(); message.put(msgtype, actionCard); message.put(actionCard, actionCard); return sendRawMessage(webhookUrl, secret, message); } private String sendRawMessage(String webhookUrl, String secret, MapString, Object messageBody) throws Exception { String signedUrl DingTalkSignUtil.generateSignedUrl(webhookUrl, secret); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); String jsonBody objectMapper.writeValueAsString(messageBody); HttpEntityString request new HttpEntity(jsonBody, headers); ResponseEntityString response restTemplate.postForEntity(signedUrl, request, String.class); if (response.getStatusCode().is2xxSuccessful()) { return response.getBody(); } throw new RuntimeException(钉钉推送失败HTTP状态码 response.getStatusCode()); } }这部分值得注意的细节sendRawMessage是核心总闸所有消息发送最终都走这里。加签逻辑被单独抽到DingTalkSignUtil这样将来如果钉钉端安全设置改成关键字或IP白名单你只需要换掉签名方法即可。另外我把RestTemplate判断响应的逻辑写成了非2xx就抛异常实际推送时你还需要对返回体里的errcode字段做二次判断因为钉钉接口即使HTTP状态是200业务上也可能返回错误码比如errcode: 310000代表关键字不匹配。3.3 SpringBoot配置化接入一次注册随处注入有了上面的Client接下来把它交给Spring管理。我习惯写一个DingTalkProperties类绑定配置然后用Configuration装配出Bean。# application.yml dingtalk: robot: webhook: https://oapi.dingtalk.com/robot/send?access_tokenxxxxxxxx secret: SECxxxxxxxxxxxxxxxxxxxxxxxximport org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; Component ConfigurationProperties(prefix dingtalk.robot) public class DingTalkProperties { private String webhook; private String secret; // getter/setter 省略 }import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class DingTalkAutoConfiguration { Bean public DingTalkRobotClient dingTalkRobotClient() { return new DingTalkRobotClient(); } }然后在业务代码里直接注入DingTalkRobotClient调用sendText或sendMarkdown即可。这样整个推送能力对业务方来说就是一个黑盒一行代码触发推送服务内部细节完全隔离。4. 从能用变成好用定时日报、异步推送、告警艾特一整套实战框架搭好之后接下来就要看怎么在实际项目里用得顺手了。我这里分享三个最常见的落地场景每个都有对应的真实代码和思路。4.1 定时任务自动推送运营日报很多管理后台都需要每天早上往运营群里推送昨天的核心业务指标。这个需求用SpringBoot的Scheduled加钉钉机器人就能轻松实现。import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDate; import java.time.format.DateTimeFormatter; Component RequiredArgsConstructor public class DailyReportTask { private final DingTalkRobotClient dingTalkRobotClient; private final DingTalkProperties dingTalkProperties; // 每个工作日上午9点整推送 Scheduled(cron 0 0 9 * * MON-FRI) public void pushDailyReport() { LocalDate yesterday LocalDate.now().minusDays(1); String dateStr yesterday.format(DateTimeFormatter.ofPattern(yyyy-MM-dd)); long orderCount 1024; // 实际从业务服务查询 double orderAmount 8848.0; String md #### 昨日运营日报 dateStr \n ---\n - 订单总量** orderCount ** 单\n - 支付总金额** orderAmount ** 元\n ---\n 更多明细请前往数据看板查看。; try { String result dingTalkRobotClient.sendMarkdown( dingTalkProperties.getWebhook(), dingTalkProperties.getSecret(), 运营日报, md, false ); // 可对result的errcode做进一步判断 } catch (Exception e) { // 推日报失败不能影响主业务这里要落日志并可选做本地告警 log.error(推送运营日报失败, e); } } }这里有一个很重要的工程判断定时任务里发送推送失败的后果不能影响主流程。日报推送只是一个提醒动作就算钉钉接口临时不可用也不能让整个定时任务挂掉。所以我明确把异常捕获在外层用log记录错误。业务上如果连日志都不可靠那就要考虑把失败消息推送给本地文件或人工值班群了。还需要你注意Scheduled任务默认是单线程串行执行的。如果项目里有多个定时任务同时触发后起的任务会被阻塞。项目规模变大时建议为钉钉推送这类任务配置独立的TaskScheduler线程池避免因为一次HTTP超时把其他定时任务拖死。4.2 用Async实现异常告警不让推送阻塞业务接口最典型的场景是用户下单成功后系统要推送一条消息到内部群。如果你用同步方式在请求线程里直接调用钉钉接口可能出现最坏情况——一次网络超时让用户请求延迟好几秒。这在生产环境是绝对不能接受的。解决方式很简单把推送方法改成异步。import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; Service public class AlarmPushService { private final DingTalkRobotClient dingTalkRobotClient; private final DingTalkProperties dingTalkProperties; public AlarmPushService(DingTalkRobotClient dingTalkRobotClient, DingTalkProperties dingTalkProperties) { this.dingTalkRobotClient dingTalkRobotClient; this.dingTalkProperties dingTalkProperties; } Async(dingTalkExecutor) public void pushAlarmAsync(String content) { try { dingTalkRobotClient.sendMarkdown( dingTalkProperties.getWebhook(), dingTalkProperties.getSecret(), 系统告警, content, true ); } catch (Exception e) { log.error(异步推送告警失败, e); } } }注意这里使用了Async(dingTalkExecutor)这种指定线程池的写法。为什么不能直接用默认的Async因为Spring的Async默认使用的是SimpleAsyncTaskExecutor它对每个任务都会新建线程在高并发推送场景下会带来线程爆炸风险。正确做法是自定义一个线程池专门给消息推送用import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; Configuration EnableAsync public class AsyncConfig { Bean(dingTalkExecutor) public Executor dingTalkExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(4); executor.setQueueCapacity(100); executor.setThreadNamePrefix(dingtalk-push-); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }我把setRejectedExecutionHandler设置成了CallerRunsPolicy含义是如果线程池队列满了推送任务会退回调用线程执行。从业务角度来说推送失败总比静默丢弃消息要好退回调用线程虽然会阻塞一点点时间但能保证告警不丢。如果你对延迟极度敏感也可以配置成丢弃策略并做好监控日志这个取舍要看业务方的真实诉求。4.3 多机器人管理与消息频率控制一个项目里你可能不只一个群需要推送研发群要收系统异常告警运营群要收业务日报管理层可能还要收核心指标周报。最笨的做法是每个群各建一个机器人代码里维护一个机器人Map。我比较推荐的做法是建立一个简单的RobotRouterComponent public class DingTalkRobotRouter { private final MapString, DingTalkRobotClient clients new HashMap(); private final MapString, DingTalkProperties propertiesMap new HashMap(); public void register(String alias, DingTalkProperties properties) { propertiesMap.put(alias, properties); clients.put(alias, new DingTalkRobotClient()); } public DingTalkRobotClient route(String alias) { return clients.get(alias); } public DingTalkProperties props(String alias) { return propertiesMap.get(alias); } }为什么要把控制代码重复当作一个重要问题来讲因为钉钉自定义机器人是有频率限制的官方文档明确写着每个机器人每分钟最多只能发送20条消息具体额度以官方文档为准。如果你的多个业务模块都直接往同一个机器人推消息一旦某个模块出现异常狂发日志可能几分钟内你就把频控打满导致其他正常业务推送也全部失败。我实测过的表现是短时间连续高频发送时钉钉会返回errcode190360提示调用超过频率限制并且这种限制惩罚是比较严格的通常需要等待几十秒甚至更久才能恢复。所以实战中建议按群拆机器人每个机器人服务各自独立主题的业务。推送到同一个群的消息发送端要做简单的聚合或节流比如5秒内多条告警合并成一条。所有推送逻辑外层都要有降级开关一旦钉钉服务不稳定能快速切断全量推送避免对业务线程池造成额外压力。5. 我在实战中踩过的那些坑以及对应的排查链路这个章节是真正值钱的部分。钉钉机器人接入本身代码不多但很多细节不看文档就掉坑里。下面每一个坑我都亲眼见过。5.1 加签之后消息仍然报310000签名错误我遇到的第一个坑就是签名问题。写完签名工具本地测试发了一条消息结果钉钉返回{errcode:310000,errmsg:sign not match}。一开始我以为Secret抄错了反复核对了好几次都没问题。排查思路如下确认时间戳。钉钉要求签名里的timestamp必须与URL里的timestamp一致。我的代码里生成的String stringToSign timestamp \n secretURL拼的也是同一个timestamp初步认为没问题。确认UrlEncode时机。后来我发现签名计算用的Base64字符串如果不做URLEncode直接拼接有可能碰巧能用但一旦字符串中出现或/字符钉钉就校验失败。这属于典型的偶发性失败。加一层URLEncoder.encode(..., UTF-8)之后稳定跑了几万次再没出过问题。还有一种情况容易被忽略某些网关或代理层可能会把URL里的还原成空格。如果你的服务部署时前面挂了网关要检查它是否会对Query参数做二次解码。遇到这种问题可以把签名里的替换为%20来做兼容。排查这类问题最有效的方式是写一个临时脚本打印出实际请求的URL然后把URL放到钉钉官方调试工具里比对。不要猜拿到真实请求一测就知道问题出在哪一环。5.2 关键字策略下的消息发不出去我有个客户项目原本用的是关键字安全策略他们在钉钉端配置的关键词是告警。结果业务系统某次崩溃时代码里发送的文本内容没有包含告警两个字消息直接被静默丢弃值班人员完全没收到通知。这种问题的坑点在于钉钉对关键字校验失败返回的错误码有时候并不直观经常被排查者误以为是网络或签名问题。而且一旦你走了关键字策略后续任何人新增消息内容时都得想着带上那个词非常容易遗漏。我的建议非常直接能不用关键字策略就别用直接用加签。加签对消息内容零约束你只需要在服务端管理好Secret即可。如果确有需求要限制外人发消息那就加签IP白名单组合只要服务器IP固定安全性和鲁棒性都好得多。5.3 定时推送导致的内存线程堆积我之前接手过一个线上系统每5分钟会执行一次监控任务并推送消息到钉钉群。结果某天钉钉临时故障网络超时重试机制写得很烂——代码里用的是restTemplate默认无超时设置每次请求都挂在那边等而定时任务又是单线程的线程池队列越积越多最后直接把系统内存吃完。这个教训让我调整了三个地方RestTemplate必须显式配置连接超时和读取超时。例如连接超时5秒、读取超时10秒这样钉钉接口故障时不会无限期阻塞线程。推送外层必须做兜底。建议使用try/catch捕获所有异常并对推送失败做计数监控连续失败达到阈值时触发人工介入。削峰填谷。定时任务里推的如果是大段markdown要考虑消息内容大小钉钉对消息体也有长度限制超长内容要截断或拆分。import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; public class RestTemplateConfig { public static RestTemplate buildRestTemplate() { RequestConfig requestConfig RequestConfig.custom() .setConnectTimeout(5000) .setConnectionRequestTimeout(3000) .setSocketTimeout(10000) .build(); CloseableHttpClient httpClient HttpClientBuilder.create() .setDefaultRequestConfig(requestConfig) .build(); HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(httpClient); return new RestTemplate(factory); } }如果你使用的是SpringBoot的默认RestTemplate它底层走的是JDK的HttpURLConnection超时配置比较麻烦。上面这种用Apache HttpClient的方式更可控建议直接照抄。5.4 markdown内容里的特殊字符问题最后提一个偏门但真实存在的事情。有一次我推送的markdown内容里包含了一段JSON日志结果推送到钉钉群里显示特别混乱部分内容直接消失了。排查后发现是因为内容里有反引号和特殊符号钉钉的markdown解析在消息服务端渲染时对部分序列做了转义。后来处理方案就一条不要把任意未转义的用户输入丢进markdown消息。凡是内容来自业务数据库或者日志文件一律做HTML实体转义和markdown保留字符过滤。钉钉官方文档有说明markdown消息支持的标准语法是官方自己的子集不是所有GitHub Flavored Markdown都能100%兼容。我在项目里写了这样一个简单工具public static String escapeMarkdown(String source) { if (source null) return ; return source .replace(\\, \\\\) .replace(, \\) .replace(*, \\*) .replace(_, \\_) .replace({, \\{) .replace(}, \\}) .replace([, \\[) .replace(], \\]) .replace((, \\() .replace(), \\)) .replace(#, \\#) .replace(, \\) .replace(-, \\-) .replace(., \\.) .replace(!, \\!); }这个函数只对内容里的特殊字符做转义不影响标题和正常的markdown语法。虽然代码不算优雅但对生产环境的稳定性很有帮助。只要进入消息体之前统一走一层转义就不会出现内容被解析坏这种莫名其妙的问题。6. 再分享两点我的个人经验整套系统跑稳之后我最大的感触是钉钉机器人只是消息触达的最后一公里真正让消息推送可靠的往往是上游的告警策略和下游的失败兜底。你可以在SpringBoot里把所有推送逻辑都封装得漂漂亮亮但如果没人关心钉钉返回的errcode没人在推送连续失败时做降级切换那么这套东西就是空中楼阁。我建议你在接入之后至少补上两个监控指标一个是推送成功率一个是从发起推送到钉钉返回成功的耗时。这两个指标能直观告诉你消息通道到底靠不靠谱。钉钉整体服务质量在内部群场景下已经相当不错但再好的通道也怕误用和滥用控制好消息频率和内容体量这套方案完全能支撑大多数项目的生产需要。如果有条件还可以把DingTalkRobotClient改进成支持本地消息队列的模式也就是先把要发送的消息放进MQ或内存队列再由消费者线程统一调用钉钉接口。这种设计能天然防削峰也能把推送逻辑和业务逻辑彻底解耦。我目前在几个高并发项目里用的就是这个模式稳定性明显优于直接同步调用。
返回列表