支持技术分享,转载或复制,请指出文章来源 此博客作者为Jack__0023
1、简介
项目使用了 Spring Cloud ,由于是公司新项目,刚好做完了亚马逊ERP项目,空闲了,顺便将项目监控模块之一给做了
注意,第一次收不到别的服务的启动提示,只有异常和下线提示,只有在启动了eureka和spring boot admin注册到eureka之后,并且其他服务都启动完,再次启动其他已经启动过的服务,你才会接收到上线通知,这一块我后面会用别的方式作为弥补掉
2、使用工具(5个)
2-1、Eureka(发现和注册中心,你可以用别的)
2-2、Spring Boot Admin2.3.1(监控)
2-3、Spring Boot Starter Security(用于校验)
2-4、钉钉群机器人(用于接收信息和推送),可能还有的邮箱maven,有点忘了不是很确定,你按照我下面的maven配置配一下
2-5、开发用IDEA(我懒得换)
3、流程步骤(四个步骤,第五个送的)
3-1、搭建Spring Boot Admin项目(会的可以不用看这一步)
3-1-1、创建新项目,new Project 然后选择 maven 项目一直选下一步
3-1-2、配置maven的pom文件,内容如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.0-M5</spring-cloud.version>
<log-all>all</log-all>
<log-base.dir>log</log-base.dir>
</properties>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zqf</groupId>
<artifactId>monitoring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>monitoring</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.4.0</version>
</dependency>
<!--安全认证框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.jolokia</groupId>
<artifactId>jolokia-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--邮箱-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
3-1-3、打上注解和yml,配置 Sping Boot Admin 完成
3-2、添加Security
3-2-1、yml文件添加配置,这个是登录账号密码
3-2-2、配置 Security 配置,你可以不用独立一个类,直接配置在application
注意放开actuator,不然你会发现spring boot admin 一直是down,原因是因为没有放开 Security 监控接口,Security 是需要你登录,但是没有携带账号无法登录,spring boot admin 得到的结果就是自己无法使用(如果基于erueka实现的话),然后就会出现一直是down。
@Configuration
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
//授予对所有静态资产和登录页面的公共访问权限
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
.antMatchers(adminContextPath + "/actuator/**").permitAll()//注意放开actuator,不然你会发现spring boot admin 一直是down
.antMatchers(adminContextPath + "/instances").permitAll()
//必须对每个其他请求进行身份验证
.anyRequest().authenticated()
.and()
//配置登录和注销
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
//启用HTTP-Basic支持。这是Spring Boot Admin Client注册所必需的
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
/* .ignoringAntMatchers(
// 禁用CRSF保护Spring引导管理客户端用来注册的端点。
adminContextPath + "/instances",
// 禁用执行器端点的CRSF保护
adminContextPath + "/actuator/**"
)*/;
}
}
3-3、添加 eureka
3-3-1、在 eureka服务者上添加配置
#admin监控,所有的服务都可以看到详细信息
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
3-3-2、在 Spring Boot Admin 项目向 Eureka服务 注册
注意,这里 3-3-1 和 3-3-2不是同一个项目模块,分属于不同微服务
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
serviceUrl:
defaultZone: http://127.0.0.1:8700/eureka/
3-4、添加 钉钉机器人(没网上看到有完整的流程,我写完整点吧)
3-4-1、在钉钉上找个群,建立机器人
点完点添加就完了
3-4-2、配置机器人信息,找一个点进去,配置标题和信息,配完点完成
3-4-3、回到项目配置项目机器人
3-4-4、代码里面配置信息
3-4-5、配置钉钉的消息通知类型,给你们一段我弄完的配置
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.values.StatusInfo;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class DingTalkNotifier extends AbstractStatusChangeNotifier {
private static final String DEFAULT_MESSAGE = "*#{instance.registration.name}* (#{instance.id}) is *#{event.statusInfo.status}**";
private final SpelExpressionParser parser = new SpelExpressionParser();
private RestTemplate restTemplate = new RestTemplate();
private String webhookToken;
private String atMobiles;
private String msgtype = "markdown";
private String title = "服务告警通知";
private Expression message;
public DingTalkNotifier(InstanceRepository repository) {
super(repository);
this.message = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> restTemplate.postForEntity(webhookToken, createMessage(event, instance), Void.class));
}
private HttpEntity<Map<String, Object>> createMessage(InstanceEvent event, Instance instance) {
log.info("createMessage -> event : {},instance : ", event, instance);
Map<String, Object> messageJson = new HashMap<>();
HashMap<String, String> params = new HashMap<>();
transition(instance.getStatusInfo(), params, ("服务名称 : " + this.getMessage(event, instance) + ";\n\n服务状态变动时间 : " + LocalDateTime.now(ZoneOffset.of("+8")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).replace("*",""));
messageJson.put("atMobiles", this.atMobiles);
messageJson.put("msgtype", this.msgtype);
messageJson.put(this.msgtype, params);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
log.info("createMessage -> messageJson : {},params : {}", messageJson, params);
return new HttpEntity<>(messageJson, headers);
}
private String getMessage(InstanceEvent event, Instance instance) {
Map<String, Object> root = new HashMap<>();
root.put("event", event);
root.put("instance", instance);
root.put("lastStatus", getLastStatus(event.getInstance()));
StandardEvaluationContext context = new StandardEvaluationContext(root);
context.addPropertyAccessor(new MapAccessor());
log.info("getMessage -> event : {},instance : {},root : {}", event, instance, root);
return message.getValue(context, String.class);
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getWebhookToken() {
return webhookToken;
}
public void setWebhookToken(String webhookToken) {
this.webhookToken = webhookToken;
}
public String getAtMobiles() {
return atMobiles;
}
public void setAtMobiles(String atMobiles) {
this.atMobiles = atMobiles;
}
public String getMsgtype() {
return msgtype;
}
public void setMsgtype(String msgtype) {
this.msgtype = msgtype;
}
public Expression getMessage() {
return message;
}
public void setMessage(String message) {
this.message = (Expression) this.parser.parseExpression(message, ParserContext.TEMPLATE_EXPRESSION);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
/**
* @Description 转换操作
* @author 姚旭民
* @date 2020/11/27 17:31
*/
public void transition(StatusInfo info, Map<String, String> params, String message){
if (info.isOffline()) {
params.put("text", message.replaceFirst(info.getStatus(), "\n\n 服务状态 : 服务 -> 停止"));
params.put("title", "服务停止通知");
} else if (info.isUp()) {
params.put("text", message.replaceFirst(info.getStatus(), "\n\n 服务状态 : 服务 -> 启动"));
params.put("title", "服务启动通知");
} else if(info.isDown()) {
params.put("text", message.replaceFirst(info.getStatus(), "\n\n 服务状态 : 服务 -> 无法正常使用"));
params.put("title", this.title);
} else if (info.isUnknown()) {
params.put("text", message.replaceFirst(info.getStatus(), "\n\n 服务状态 : 服务 -> 未知异常状态"));
params.put("title", this.title);
}
}
}
3-5、完整yml(我知道你们要什么,因为我也懒)
server:
port: 29002
spring:
application:
name: zqf-monitoring
boot:
admin:
notify:
dingtalk:
enabled: true
webhook-token: 我起了,一枪秒了,有什么可说的
client:
username: admin
password: admin
# boot:
# admin:
# client:
# url: http://127.0.0.1:8888
security:
user:
name: admin
password: admin
redis:
host: 127.0.0.1
port: 6379
password: 123
timeout: 5000
pool:
max-active: 20 # 连接池最大连接数(使用负值表示没有限制
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: true
fetch-registry: true
serviceUrl:
defaultZone: http://127.0.0.1:8700/eureka/
#admin日志,如果没有配置,那么无法在线查看日志
logging:
file:
name: 'logs/output.log'
pattern:
file: "%clr(%d{yyyy-MM-dd HH:mm:ss}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"
4、启动看效果
4-1、Spring Boot Admin
登录界面
应用墙
日志
服务状态通知,钉钉显示的文件内容格式是我自己编写的,钉钉没有为你这样提示的