- 新建SpringBoot工程,并引入jar包如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.7.0</version>
<exclusions>
<exclusion>
<artifactId>jsr305</artifactId>
<groupId>com.google.code.findbugs</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.0-M5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-dependencies</artifactId>
<version>2.3.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 配置安全策略
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler
= new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl("/");
http.authorizeRequests()
.antMatchers("/assets/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated().and()
.formLogin().loginPage("/login")
.successHandler(successHandler).and()
.logout().logoutUrl("/logout").and()
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(
"/instances",
"/actuator/**"
);
}
}
- 实现SpringBootAdmin中的AbstractEventNotifier接口,对服务状态改变进行监听
@Slf4j
@Component
public class DingTalkNotifier extends AbstractEventNotifier {
private final Long startDate;
private final DateTimeFormatter dateFormat;
@Getter
private final LinkedBlockingQueue<Message> toProcessRecords;
public static final Integer MAX_SIZE = 1024;
public DingTalkNotifier(final InstanceRepository repository) {
super(repository);
this.dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
this.toProcessRecords = new LinkedBlockingQueue<>(MAX_SIZE);
this.startDate = System.currentTimeMillis();
}
@Override
protected Mono<Void> doNotify(final InstanceEvent event, final Instance instance) {
if (Math.abs(System.currentTimeMillis() - this.startDate) < 30 * 1000) {
return Mono.fromRunnable(() -> {
});
}
return Mono.fromRunnable(() -> {
String content = null;
String urlAndPort = getUrlAndPort(instance.getRegistration().getServiceUrl());
String checkTime = null;
Boolean isOk = null;
final String serviceName = instance.getRegistration().getName();
String instanceName = null;
if (event instanceof InstanceStatusChangedEvent) {
final InstanceStatusChangedEvent instanceStatusChangedEvent = (InstanceStatusChangedEvent) event;
final String status = instanceStatusChangedEvent.getStatusInfo().getStatus();
switch (status) {
// 健康检查没通过
case "DOWN":
instanceName = instanceStatusChangedEvent.getInstance().getValue();
checkTime = this.dateFormat.format(instanceStatusChangedEvent.getTimestamp());
content = "健康检查没有通过, 请立刻检查.";
isOk = false;
break;
// 服务离线
case "OFFLINE":
instanceName = instanceStatusChangedEvent.getInstance().getValue();
checkTime = this.dateFormat.format(instanceStatusChangedEvent.getTimestamp());
content = "已离线, 请立刻检查.";
isOk = false;
break;
//服务上线
case "UP":
instanceName = instanceStatusChangedEvent.getInstance().getValue();
checkTime = this.dateFormat.format(instanceStatusChangedEvent.getTimestamp());
content = "已成功上线.";
isOk = true;
break;
// 服务未知异常
case "UNKNOWN":
instanceName = instanceStatusChangedEvent.getInstance().getValue();
checkTime = this.dateFormat.format(instanceStatusChangedEvent.getTimestamp());
content = "发现未知异常, 请立刻检查.";
isOk = false;
break;
default:
break;
}
} else if (event instanceof InstanceDeregisteredEvent) {
final InstanceDeregisteredEvent instanceDeregisteredEvent = (InstanceDeregisteredEvent) event;
if ("DEREGISTERED".equals(instanceDeregisteredEvent.getType())) {
instanceName = instanceDeregisteredEvent.getInstance().getValue();
checkTime = this.dateFormat.format(instanceDeregisteredEvent.getTimestamp());
content = "已成功下线.";
isOk = true;
}
}
if (!ObjectUtils.isEmpty(content) && null != isOk) {
final Message message = new Message();
message.setMsgtype("text");
final StringBuilder stringBuilder = new StringBuilder();
if (!ObjectUtils.isEmpty(urlAndPort)) {
stringBuilder.append(String.format("主机地址:%s\n", urlAndPort));
}
if (!ObjectUtils.isEmpty(serviceName)) {
stringBuilder.append(String.format("服务名称:%s\n", serviceName));
}
if (!ObjectUtils.isEmpty(instanceName)) {
stringBuilder.append(String.format("实例名称:%s\n", instanceName));
}
if (isOk) {
stringBuilder.append("当前状态:OK\n");
stringBuilder.append(String.format("消息详情:%s\n", content));
} else {
stringBuilder.append("告警等级:严重\n");
stringBuilder.append(String.format("问题详情:%s\n", content));
}
if (!ObjectUtils.isEmpty(checkTime)) {
stringBuilder.append(String.format("检查时间:%s", checkTime));
}
message.setText(new Message.Text(stringBuilder.toString()));
int offerTryCount = 0;
try {
while (!this.toProcessRecords.offer(message, 1000, TimeUnit.MILLISECONDS)) {
if (++offerTryCount % 30 == 0) {
log.warn(String.format("DingTalkNotifier: offer message has failed for a period 30 seconds, [%s]", message));
break;
}
}
} catch (final Exception exception) {
log.error(exception.getMessage(), exception);
}
}
});
}
private String getUrlAndPort(final String strUrl) {
try {
final URL url = new URL(strUrl);
return String.format("%s : %s", url.getHost(), url.getPort());
} catch (MalformedURLException e) {
return "";
}
}
}
- 定义定时任务,将消息发送至钉钉
@Slf4j
@Component
public class DingTalkSchedule {
@Value("${dingtalk.accessToken}")
private String accessToken;
@Value("${dingtalk.secret}")
private String secret;
private final DingTalkNotifier dingTalkNotifier;
private final List<Message> messageList;
public DingTalkSchedule(final DingTalkNotifier dingTalkNotifier) {
this.dingTalkNotifier = dingTalkNotifier;
this.messageList = new ArrayList<>(DingTalkNotifier.MAX_SIZE);
}
//每5秒执行一次
@Scheduled(cron = "0/5 * * * * ?")
public void alert() throws Exception {
this.messageList.clear();
this.dingTalkNotifier.getToProcessRecords().drainTo(messageList, 10);
if (!this.messageList.isEmpty()) {
final Message message = new Message();
message.setMsgtype("text");
final StringBuilder content = new StringBuilder();
for (final Message one : this.messageList) {
content.append(one.getText().getContent());
content.append("\n");
content.append("\n");
}
message.setText(new Message.Text(content.toString()));
DingTalkTool.send(message, this.accessToken, this.secret);
}
}
}
- 定义SpringBoot启动类
@EnableScheduling
@SpringBootApplication
@EnableAdminServer
@EnableDiscoveryClient
@EnableApolloConfig
public class SpringBootAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApplication.class, args);
}
}
- 定义application.yml文件
spring:
application:
name: spring-boot-admin-server
cloud:
config:
enabled: false
consul:
host: 192.168.5.21
port: 8500
discovery:
service-name: ${spring.application.name}
prefer-ip-address: true
ip-address: 192.168.12.155
boot:
admin:
discovery:
ignored-services: consul,${spring.application.name}
ui:
title: SpringBootCloud服务
brand: <img src="assets/img/icon-spring-boot-admin.svg"><span>SpringBootCloud</span>
available-languages:
- zh-CN
- en
security:
user:
name: admin
password: admin
server:
port: ${app.port:80}
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
app:
id: spring-boot-admin
apollo:
meta: http://192.168.12.155:8080/
bootstrap:
enabled: true
namespaces: application
eagerLoad:
enabled: true
dingtalk:
accessToken: c7cb6853d7600852bef4eacdb34aeef4d1916baf486603aaa13d479c2bb96880
secret: SECff8e21a4bff2aad655d5bb06b2a6eb1a6b7540df9e2e07c68896a823c04e64e7
直接启动后,截图如下:
完整代码参考github: https://github.com/ZhangNingPegasus/middleware.git