Spring boot admin + Eureka 实现钉钉自定义服务消息通知

支持技术分享,转载或复制,请指出文章来源 此博客作者为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

登录界面
在这里插入图片描述
应用墙
在这里插入图片描述

日志在这里插入图片描述
服务状态通知,钉钉显示的文件内容格式是我自己编写的,钉钉没有为你这样提示的
在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值