11 changed files with 489 additions and 159 deletions
@ -1,17 +0,0 @@
|
||||
## SDK下载 |
||||
|
||||
#### Java SDK 下载 |
||||
下载SDK: https://open-doc.dingtalk.com/microapp/faquestions/vzbp02 |
||||
|
||||
## 配置项 |
||||
|
||||
#### bootstrap.yml |
||||
|
||||
``` |
||||
spring: |
||||
boot: |
||||
admin: |
||||
notify: |
||||
dingtalk: |
||||
webhook-token: ${Your DingDing Robot Token} |
||||
``` |
@ -0,0 +1,21 @@
|
||||
## SDK下载 |
||||
|
||||
#### Java SDK 下载 |
||||
下载SDK: https://open-doc.dingtalk.com/microapp/faquestions/vzbp02 |
||||
|
||||
## 配置项 |
||||
|
||||
#### bootstrap.yml |
||||
|
||||
``` |
||||
# 监控的相关配置 |
||||
monitor: |
||||
ding-talk: |
||||
enabled: false |
||||
# 用于自定义域名,默认会自动填充为 http://ip:port |
||||
link: http://localhost:${server.port} |
||||
# 钉钉配置的令牌 |
||||
access-token: xxx |
||||
# 如果采用密钥形式,需要添加,否则需要去掉该参数 |
||||
secret: xxx |
||||
``` |
@ -0,0 +1,52 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.admin.config; |
||||
|
||||
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository; |
||||
import org.springblade.admin.dingtalk.DingTalkNotifier; |
||||
import org.springblade.admin.dingtalk.DingTalkService; |
||||
import org.springblade.admin.dingtalk.MonitorProperties; |
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
|
||||
/** |
||||
* 钉钉自动配置 |
||||
* |
||||
* @author L.cm |
||||
*/ |
||||
@Configuration |
||||
@ConditionalOnProperty(value = "monitor.ding-talk.enabled", havingValue = "true") |
||||
public class DingTalkConfiguration { |
||||
|
||||
@Bean |
||||
public DingTalkService dingTalkService(MonitorProperties properties, |
||||
WebClient.Builder builder) { |
||||
return new DingTalkService(properties, builder.build()); |
||||
} |
||||
|
||||
@Bean |
||||
public DingTalkNotifier dingTalkNotifier(MonitorProperties properties, |
||||
DingTalkService dingTalkService, |
||||
InstanceRepository repository, |
||||
Environment environment) { |
||||
return new DingTalkNotifier(dingTalkService, properties, environment, repository); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,65 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.admin.config; |
||||
|
||||
import de.codecentric.boot.admin.server.config.AdminServerProperties; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; |
||||
import org.springframework.security.config.web.server.ServerHttpSecurity; |
||||
import org.springframework.security.web.server.SecurityWebFilterChain; |
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; |
||||
|
||||
import java.net.URI; |
||||
|
||||
/** |
||||
* 监控安全配置 |
||||
* |
||||
* @author L.cm |
||||
*/ |
||||
@EnableWebFluxSecurity |
||||
@Configuration(proxyBeanMethods = false) |
||||
public class SecurityConfiguration { |
||||
private final String contextPath; |
||||
|
||||
public SecurityConfiguration(AdminServerProperties adminServerProperties) { |
||||
this.contextPath = adminServerProperties.getContextPath(); |
||||
} |
||||
|
||||
@Bean |
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { |
||||
// @formatter:off
|
||||
RedirectServerAuthenticationSuccessHandler successHandler = new RedirectServerAuthenticationSuccessHandler(); |
||||
successHandler.setLocation(URI.create(contextPath + "/")); |
||||
return http.headers().frameOptions().disable().and() |
||||
.authorizeExchange() |
||||
.pathMatchers( |
||||
contextPath + "/assets/**" |
||||
, contextPath + "/login" |
||||
, contextPath + "/actuator/**" |
||||
).permitAll() |
||||
.anyExchange().authenticated().and() |
||||
.formLogin().loginPage(contextPath + "/login") |
||||
.authenticationSuccessHandler(successHandler).and() |
||||
.logout().logoutUrl(contextPath + "/logout").and() |
||||
.httpBasic().disable() |
||||
.csrf().disable() |
||||
.build(); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,105 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.admin.dingtalk; |
||||
|
||||
import de.codecentric.boot.admin.server.domain.entities.Instance; |
||||
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository; |
||||
import de.codecentric.boot.admin.server.domain.events.InstanceEvent; |
||||
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent; |
||||
import de.codecentric.boot.admin.server.domain.values.Registration; |
||||
import de.codecentric.boot.admin.server.domain.values.StatusInfo; |
||||
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.core.env.Environment; |
||||
import org.springframework.lang.NonNull; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.time.LocalDateTime; |
||||
import java.time.ZoneId; |
||||
import java.time.format.DateTimeFormatter; |
||||
|
||||
/** |
||||
* 服务上下线告警 |
||||
* |
||||
* <p> |
||||
* 注意:AbstractStatusChangeNotifier 这个事件有毛病 |
||||
* </p> |
||||
* |
||||
* @author L.cm |
||||
*/ |
||||
@Slf4j |
||||
public class DingTalkNotifier extends AbstractEventNotifier { |
||||
private final DingTalkService dingTalkService; |
||||
private final MonitorProperties properties; |
||||
private final Environment environment; |
||||
public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
||||
|
||||
public DingTalkNotifier(DingTalkService dingTalkService, MonitorProperties properties, |
||||
Environment environment, InstanceRepository repository) { |
||||
super(repository); |
||||
this.dingTalkService = dingTalkService; |
||||
this.properties = properties; |
||||
this.environment = environment; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
protected Mono<Void> doNotify(@NonNull InstanceEvent event, @NonNull Instance instance) { |
||||
if (event instanceof InstanceStatusChangedEvent) { |
||||
// 构造请求结构
|
||||
return createAndPushMsg(event, instance); |
||||
} |
||||
return Mono.empty(); |
||||
} |
||||
|
||||
private Mono<Void> createAndPushMsg(InstanceEvent event, Instance instance) { |
||||
Registration registration = instance.getRegistration(); |
||||
// 服务名
|
||||
String appName = registration.getName(); |
||||
// 服务地址
|
||||
String serviceUrl = registration.getServiceUrl(); |
||||
StatusInfo status = instance.getStatusInfo(); |
||||
// 时间
|
||||
LocalDateTime localDateTime = LocalDateTime.ofInstant(event.getTimestamp(), ZoneId.systemDefault()); |
||||
MonitorProperties.DingTalk dingTalk = properties.getDingTalk(); |
||||
String title = dingTalk.getService().getTitle(); |
||||
|
||||
String message = "## **" + title + "**\n" + |
||||
"#### **【服务】** " + appName + "\n" + |
||||
"#### **【环境】** " + environment.getActiveProfiles()[0] + "\n" + |
||||
"#### **【地址】** " + serviceUrl + "\n" + |
||||
"#### **【状态】** " + statusCn(status) + "\n" + |
||||
"#### **【时间】** " + DATETIME_FORMATTER.format(localDateTime) + "\n" + |
||||
"#### **【详情】** " + dingTalk.getLink() + "\n"; |
||||
|
||||
return dingTalkService.pushMsg(title, message); |
||||
} |
||||
|
||||
private String statusCn(StatusInfo status) { |
||||
if (status.isUp()) { |
||||
return "应用上线(IS UP)"; |
||||
} else if (status.isDown()) { |
||||
return "应用宕机(IS DOWN)"; |
||||
} else if (status.isOffline()) { |
||||
return "应用掉线(IS OFFLINE)"; |
||||
} else if (status.isUnknown()) { |
||||
return "未知状态(UNKNOWN)"; |
||||
} else { |
||||
return "异常状态"; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,110 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.admin.dingtalk; |
||||
|
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.util.Base64Utils; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.reactive.function.BodyInserters; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
import org.springframework.web.util.UriUtils; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import javax.crypto.Mac; |
||||
import javax.crypto.SecretKey; |
||||
import javax.crypto.spec.SecretKeySpec; |
||||
import java.net.URI; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.security.InvalidKeyException; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* 钉钉 服务 |
||||
* |
||||
* @author L.cm |
||||
*/ |
||||
@Slf4j |
||||
@RequiredArgsConstructor |
||||
public class DingTalkService { |
||||
private static final String DING_TALK_ROBOT_URL = "https://oapi.dingtalk.com/robot/send?access_token="; |
||||
private final MonitorProperties properties; |
||||
private final WebClient webClient; |
||||
|
||||
/** |
||||
* 发送消息 |
||||
* |
||||
* @param title title |
||||
* @param text 消息 |
||||
*/ |
||||
public Mono<Void> pushMsg(String title, String text) { |
||||
log.info("钉钉消息:[创建消息体]title:{}, text:{}", title, text); |
||||
|
||||
HashMap<String, String> params = new HashMap<>(2); |
||||
params.put("title", title); |
||||
params.put("text", text); |
||||
|
||||
Map<String, Object> body = new HashMap<>(2); |
||||
body.put("msgtype", "markdown"); |
||||
body.put("markdown", params); |
||||
log.info("创建消息体 json:{}", body); |
||||
|
||||
MonitorProperties.DingTalk dingTalk = properties.getDingTalk(); |
||||
String accessToken = dingTalk.getAccessToken(); |
||||
if (StringUtils.isEmpty(accessToken)) { |
||||
log.error("DingTalk alert config accessToken ${monitor.ding-talk.access-token} is blank."); |
||||
return Mono.empty(); |
||||
} |
||||
|
||||
String urlString = DING_TALK_ROBOT_URL + dingTalk.getAccessToken(); |
||||
// 有私钥要签名
|
||||
String secret = dingTalk.getSecret(); |
||||
if (StringUtils.hasText(secret)) { |
||||
long timestamp = System.currentTimeMillis(); |
||||
urlString += String.format("×tamp=%s&sign=%s", timestamp, getSign(secret, timestamp)); |
||||
} |
||||
return webClient.post() |
||||
.uri(URI.create(urlString)) |
||||
.contentType(MediaType.APPLICATION_JSON) |
||||
.body(BodyInserters.fromValue(body)) |
||||
.retrieve() |
||||
.bodyToMono(String.class) |
||||
.doOnSuccess((result) -> log.info("钉钉消息:[消息返回]result:{}", result)) |
||||
.then(); |
||||
} |
||||
|
||||
private static String getSign(String secret, long timestamp) { |
||||
String stringToSign = timestamp + "\n" + secret; |
||||
byte[] hmacSha256Bytes = digestHmac(stringToSign, secret); |
||||
return UriUtils.encode(Base64Utils.encodeToString(hmacSha256Bytes), StandardCharsets.UTF_8); |
||||
} |
||||
|
||||
public static byte[] digestHmac(String data, String key) { |
||||
SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); |
||||
try { |
||||
Mac mac = Mac.getInstance(secretKey.getAlgorithm()); |
||||
mac.init(secretKey); |
||||
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); |
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) { |
||||
throw new RuntimeException(e.getMessage()); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,67 @@
|
||||
/* |
||||
* Copyright (c) 2018-2028, DreamLu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the dreamlu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: DreamLu 卢春梦 (596392912@qq.com) |
||||
*/ |
||||
package org.springblade.admin.dingtalk; |
||||
|
||||
|
||||
import lombok.Getter; |
||||
import lombok.Setter; |
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
import org.springframework.cloud.context.config.annotation.RefreshScope; |
||||
|
||||
/** |
||||
* 监控配置 |
||||
* |
||||
* @author L.cm |
||||
*/ |
||||
@Getter |
||||
@Setter |
||||
@RefreshScope |
||||
@ConfigurationProperties("monitor") |
||||
public class MonitorProperties { |
||||
private DingTalk dingTalk = new DingTalk(); |
||||
|
||||
@Getter |
||||
@Setter |
||||
public static class DingTalk { |
||||
/** |
||||
* 启用钉钉告警,默认为 true |
||||
*/ |
||||
private boolean enabled = false; |
||||
/** |
||||
* 钉钉机器人 token |
||||
*/ |
||||
private String accessToken; |
||||
/** |
||||
* 签名:如果有 secret 则进行签名,兼容老接口 |
||||
*/ |
||||
private String secret; |
||||
/** |
||||
* 地址配置 |
||||
*/ |
||||
private String link; |
||||
private Service service = new Service(); |
||||
} |
||||
|
||||
@Getter |
||||
@Setter |
||||
public static class Service { |
||||
/** |
||||
* 服务 状态 title |
||||
*/ |
||||
private String title = "服务状态通知"; |
||||
} |
||||
} |
@ -1,123 +0,0 @@
|
||||
/* |
||||
* Copyright (c) 2015-2025, Jeckxu All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* Neither the name of the jeckxu.net developer nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* Author: Jeckxu (chinajeckxu@163.com) |
||||
*/ |
||||
package org.springblade.admin.notifier; |
||||
|
||||
import com.dingtalk.api.DefaultDingTalkClient; |
||||
import com.dingtalk.api.DingTalkClient; |
||||
import com.dingtalk.api.request.OapiRobotSendRequest; |
||||
import com.taobao.api.ApiException; |
||||
import de.codecentric.boot.admin.server.domain.entities.Instance; |
||||
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository; |
||||
import de.codecentric.boot.admin.server.domain.events.InstanceEvent; |
||||
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent; |
||||
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
|
||||
/** |
||||
* DingTalk Notice |
||||
* Can you find these packages ? , Plase Read DINGDING_README.md |
||||
* {@code com.dingtalk.api.DefaultDingTalkClient} |
||||
* {@code com.dingtalk.api.DingTalkClient} |
||||
* {@code com.dingtalk.api.request.OapiRobotSendRequest} |
||||
* {@code com.taobao.api.ApiException} |
||||
* |
||||
* @author jeckxu |
||||
*/ |
||||
@Slf4j |
||||
public class CustomNotifier extends AbstractEventNotifier { |
||||
|
||||
/** |
||||
* massage template |
||||
*/ |
||||
private static final String TEMPLATE = "服务名:%s(%s) n状态:%s(%s) n服务ip:%s"; |
||||
|
||||
@Value("${spring.boot.admin.notify.dingtalk.webhook-token}") |
||||
private String dingTalkToken; |
||||
|
||||
|
||||
public CustomNotifier(InstanceRepository repository) { |
||||
super(repository); |
||||
} |
||||
|
||||
@Override |
||||
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) { |
||||
return Mono.fromRunnable(() -> { |
||||
if (event instanceof InstanceStatusChangedEvent) { |
||||
log.info("Instance {} ({}) is {}", instance.getRegistration().getName(), event.getInstance(), |
||||
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus()); |
||||
|
||||
|
||||
String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(); |
||||
String messageText = null; |
||||
switch (status) { |
||||
// 健康检查没通过
|
||||
case "DOWN": |
||||
log.info("发送 健康检查没通过 的通知!"); |
||||
messageText = String.format(TEMPLATE, instance.getRegistration().getName(), event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "健康检查没通过", instance.getRegistration().getServiceUrl()); |
||||
this.sendMessage(messageText); |
||||
break; |
||||
// 服务离线
|
||||
case "OFFLINE": |
||||
log.info("发送 服务离线 的通知!"); |
||||
messageText = String.format(TEMPLATE, instance.getRegistration().getName(), event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务离线", instance.getRegistration().getServiceUrl()); |
||||
this.sendMessage(messageText); |
||||
break; |
||||
//服务上线
|
||||
case "UP": |
||||
log.info("发送 服务上线 的通知!"); |
||||
messageText = String.format(TEMPLATE, instance.getRegistration().getName(), event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务上线", instance.getRegistration().getServiceUrl()); |
||||
this.sendMessage(messageText); |
||||
break; |
||||
// 服务未知异常
|
||||
case "UNKNOWN": |
||||
log.info("发送 服务未知异常 的通知!"); |
||||
messageText = String.format(TEMPLATE, instance.getRegistration().getName(), event.getInstance(), ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(), "服务未知异常", instance.getRegistration().getServiceUrl()); |
||||
this.sendMessage(messageText); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} else { |
||||
log.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(), |
||||
event.getType()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* massage send |
||||
* |
||||
* @param messageText |
||||
*/ |
||||
private void sendMessage(String messageText) { |
||||
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token=" + dingTalkToken); |
||||
OapiRobotSendRequest request = new OapiRobotSendRequest(); |
||||
request.setMsgtype("text"); |
||||
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text(); |
||||
text.setContent(messageText); |
||||
request.setText(text); |
||||
|
||||
try { |
||||
client.execute(request); |
||||
} catch (ApiException e) { |
||||
log.info("[ERROR] sendMessage", e); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue