Skip to content
章节导航

搭建 SpringBoot Admin 项目

新建项目模块

新建项目模块 fly-actuator, 添加 SpringBoot Admin Starter 自动配置依赖

xml
<!-- SpringBoot Admin Starter
         包含 1.spring-boot-admin-server: Server 端
             2.spring-boot-admin-server-ui: UI 界面
             3.spring-boot-admin-server-cloud: 对 Spring Cloud 的接入
             -->
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>3.1.2</version>
        </dependency>

启动类添加启动注解: @EnableAdminServer

微服务注册到 SpringBoot Admin Server

添加 SpringBoot Actuator 依赖

xml
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

暴露 SpringBoot Actuator Endpoints

yaml
# 服务端点详细监控信息
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

指定 actuator 端点的访问路径

yaml
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator

项目完整代码

pom.xml 完整依赖

xml
<dependencies>
        <dependency>
            <groupId>com.github.itdachen.framework</groupId>
            <artifactId>fly-runner</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.itdachen.framework</groupId>
            <artifactId>fly-context</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- SpringBoot Admin Starter
         包含 1.spring-boot-admin-server :Server 端
             2.spring-boot-admin-server-ui : UI 界面
             3.spring-boot-admin-server-cloud :对 Spring Cloud 的接入
             -->
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
        </dependency>

        <!-- nacos discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- 健康检查 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>


    </dependencies>

启动类

@EnableAsync
@RefreshScope
@EnableAdminServer // 启动 SpringBoot Admin
@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan(basePackages = {"com.github.itdachen"})
public class ActuatorBootstrap {

    public static void main(String[] args) {
        SpringBootBootstrap.run(ActuatorBootstrap.class);
    }

}

服务状态变更通知

@Component
public class AdminActuatorEventNotifierTakeNotes extends AbstractStatusChangeNotifier {
    private static final Logger logger = LoggerFactory.getLogger(AdminActuatorEventNotifierTakeNotes.class);

    /**
     * 消息模板
     */
    private static final String TEMPLATE_INFO = "\n <<<%s>>> \n 【服务名】: %s(%s) \n 【状态】: %s(%s) \n 【服务ip】: %s \n 【详情】: %s";

    /**
     * 标题:系统告警
     */
    private final String TITLE_ALARM = "系统告警";

    /**
     * 标题:系统通知
     */
    private final String TITLE_NOTICE = "系统通知";

    /**
     * 状态转换过滤
     */
    private final String[] IGNORE_CHANGES = new String[]{"UNKNOWN:UP", "REGISTERED:UP", "DOWN:UP", "OFFLINE:UP"};

    private final ITakeNotifySendService takeNotesSendNotify;

    public AdminActuatorEventNotifierTakeNotes(InstanceRepository repository,
                                               ITakeNotifySendService takeNotesSendNotify) {
        super(repository);
        this.takeNotesSendNotify = takeNotesSendNotify;
    }

    /**
     * 判断是否需要告警
     *
     * @param event
     * @param instance
     * @return 判断结果
     */
    @Override
    protected boolean shouldNotify(InstanceEvent event, Instance instance) {
        if (!(event instanceof InstanceStatusChangedEvent)) {
            return false;
        } else {
            InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent) event;
            String from = this.getLastStatus(event.getInstance());
            String to = statusChange.getStatusInfo().getStatus();
            return Arrays.binarySearch(this.IGNORE_CHANGES, from + ":" + to) < 0
                    && Arrays.binarySearch(this.IGNORE_CHANGES, "*:" + to) < 0
                    && Arrays.binarySearch(this.IGNORE_CHANGES, from + ":*") < 0;
        }
    }

    /***
     * 实现对事件的通知
     * 状态:
     *   DOWN: 健康检查没通过;
     *   OFFLINE: 服务离线;
     *   UP: 服务上线
     *   UNKNOWN: 服务未知异常
     *   REGISTERED: 注册
     *   ENDPOINTS_DETECTED: 检测到端点
     *
     * @author 王大宸
     * @date 2022/10/12 10:08
     * @param event event
     * @param instance instance
     * @return reactor.core.publisher.Mono<java.lang.Void>
     */
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
        return Mono.fromRunnable(() -> {
            String messageText = "";
            if (event instanceof InstanceStatusChangedEvent) {
                logger.info("Instance Status Change: [{}], [{}], [{}], [{}]",
                        instance.getRegistration().getName(),
                        event.getInstance().getValue(),
                        ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                        instance.getRegistration().getServiceUrl());

                String serverStatus = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();

                JSONObject body = new JSONObject();
                body.put("instanceId", event.getInstance().getValue());
                body.put("appName", instance.getRegistration().getName());
                body.put("appStatus", serverStatus);
                body.put("notifyEvent", serverStatus);
                body.put("notifyDetails", JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                body.put("serverUri", instance.getRegistration().getServiceUrl());

                switch (serverStatus) {
                    // 健康检查没通过
                    case "DOWN" -> {
                        messageText = String.format(TEMPLATE_INFO,
                                TITLE_ALARM,
                                instance.getRegistration().getName(),
                                event.getInstance(),
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                                "健康检查没通过告警",
                                instance.getRegistration().getServiceUrl(),
                                JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                        logger.info("【健康检查没通过告警】:{}", messageText);
                        body.put("notifyEvent", TITLE_ALARM);
                        body.put("appStatusDes", "健康检查没通过告警");

                    }
                    // 服务离线
                    case "OFFLINE" -> {
                        messageText = String.format(TEMPLATE_INFO,
                                TITLE_ALARM,
                                instance.getRegistration().getName(),
                                event.getInstance(),
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                                "服务离线告警",
                                instance.getRegistration().getServiceUrl(),
                                JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                        logger.info("【服务离线告警】:{}", messageText);
                        body.put("notifyEvent", TITLE_ALARM);
                        body.put("appStatusDes", "服务离线告警");
                    }
                    //服务上线
                    case "UP" -> {
                        messageText = String.format(TEMPLATE_INFO,
                                TITLE_NOTICE,
                                instance.getRegistration().getName(),
                                event.getInstance(),
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                                "服务上线通知", instance.getRegistration().getServiceUrl(),
                                JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                        logger.info("【服务上线通知】:{}", messageText);
                        body.put("notifyEvent", TITLE_NOTICE);
                        body.put("appStatusDes", "服务上线通知");
                    }
                    // 服务未知异常
                    case "UNKNOWN" -> {
                        messageText = String.format(TEMPLATE_INFO,
                                TITLE_ALARM,
                                instance.getRegistration().getName(),
                                event.getInstance(),
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                                "服务未知异常告警", instance.getRegistration().getServiceUrl(),
                                JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                        body.put("appStatusDes", "服务未知异常告警");
                        body.put("notifyEvent", TITLE_ALARM);
                        logger.info("【服务未知异常告警】:{}", messageText);
                    }
                    case "REGISTERED" -> {
                        messageText = String.format(TEMPLATE_INFO,
                                TITLE_NOTICE,
                                instance.getRegistration().getName(),
                                event.getInstance(),
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                                "服务注册通知", instance.getRegistration().getServiceUrl(),
                                JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                        body.put("appStatusDes", "服务注册通知");
                        body.put("notifyEvent", TITLE_NOTICE);
                        logger.info("【服务注册通知】:{}", messageText);
                    }
                    default -> {
                        messageText = String.format(TEMPLATE_INFO,
                                TITLE_NOTICE,
                                instance.getRegistration().getName(),
                                event.getInstance(),
                                ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus(),
                                "服务其他通知",
                                instance.getRegistration().getServiceUrl(),
                                JSONObject.toJSONString(instance.getStatusInfo().getDetails()));
                        body.put("appStatusDes", "服务其他通知");
                        body.put("notifyEvent", TITLE_NOTICE);
                        logger.info("【服务其他通知】:{}", messageText);
                    }
                }
                body.put("notifyMsg", messageText);
                body.put("notifyEventTitle", serverStatus + "【" + body.get("appStatusDes") + "】");
                takeNotesSendNotify.sendNotify(body);
            } else {
                logger.info("Instance Info: [{}], [{}], [{}], [{}]",
                        instance.getRegistration().getName(),
                        event.getInstance().getValue(),
                        event.getType(),
                        instance.getRegistration().getServiceUrl());
            }

        });
    }

}

配置文件

bootstrap.yml
yaml
server:
  port: 8005
  servlet:
    context-path: /${spring.application.name}

spring:
  application:
    name: actuator
  main:
    allow-circular-references: true
    allow-bean-definition-overriding: true
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

  boot:
    admin:
      ui:
        title: FLY-CLOUD 微服务应用实例监控
        brand: <img src="assets/img/icon-spring-boot-admin.svg"><span>FLY-CLOUD 微服务应用实例监控</span>
      client:
        instance:
          metadata:
            tags:
              environment: local

  cloud:
    nacos:
      username: admin
      password: 123456
      discovery:
        enabled: true
        username: nacos
        password: nacos
        server-addr: 127.0.0.1:8848
        namespace: 07656b3c-bb69-470a-a942-6ae27b5287cb
        group: FLY_GROUP
        watch-delay: 3000
        watch:
          enabled: true
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator


# 服务端点详细监控信息
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: always

展示

启动项目, 访问: http://127.0.0.1:8005/actuator/applications

查看控制台, 打印下面信息, 说明集成成功

项目地址

https://gitee.com/itdachen/fly-cloud