springBootAdmin(sba)

在 Spring Boot Admin(SBA)的监控体系中,引入 Nacos 的核心意义是 实现微服务集群的自动化监控管理,解决传统 SBA 模式下的痛点,具体价值体现在这 3 个核心维度:

  1. 自动化服务发现,告别手动配置

    传统 SBA 中,每个微服务客户端都需要手动配置 spring.boot.admin.client.url 才能注册到服务端。接入 Nacos 后,所有微服务只需注册到 Nacos 服务中心,SBA 会自动从 Nacos 拉取全量服务列表,无需为每个微服务单独配置 SBA 地址,新增微服务时也无需修改 SBA 配置,直接实现"即插即监控"。

  2. 配置与服务的集中化管控

    • 一方面,Nacos 配置中心可托管 SBA 和所有微服务的核心配置(如监控规则、告警阈值、Actuator 端点配置),支持动态刷新配置,无需重启 SBA 或微服务即可生效;
    • 另一方面,Nacos 提供统一的服务注册中心,结合 SBA 的监控能力,可实现"服务注册-健康监控-状态告警"的全链路闭环管理。
  3. 适配微服务架构的弹性伸缩

    在微服务集群扩缩容(如 K8s 自动扩缩容)场景下,新启动的微服务实例会自动注册到 Nacos,SBA 能实时感知并纳入监控;实例下线时,Nacos 会自动剔除,SBA 也会同步更新监控列表,无需人工介入维护监控实例,完美适配分布式架构的动态变化特性。

简单来说,没有 Nacos,SBA 更适合监控单个或少量固定的服务;有了 Nacos,SBA 才能高效管理大规模、动态变化的微服务集群

传统 SBA

一.sba 服务端

1.添加依赖
java 复制代码
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.10</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	
	<properties>
		<java.version>17</java.version>
		<!-- 关键修正:将 SBA 版本改为 2.7.x 系列(与 Spring Boot 2.7.10 兼容,推荐 2.7.8) -->
		<spring-boot-admin.version>2.7.8</spring-boot-admin.version>
	</properties>
   
       <dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>2.0.47</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.32</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<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>
			<!-- 无需单独指定版本,由 dependencyManagement 统一管控 -->
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
2.增加启动注解
java 复制代码
@SpringBootApplication
@EnableAdminServer
public class XingyueSbaApplication {

	public static void main(String[] args) {
		SpringApplication.run(XingyueSbaApplication.class, args);
	}

}
3. 钉钉报警

DingTalkNotifierConfiguration

java 复制代码
import com.ruoyi.xingyuesba.notifier.DingRobotNotifier;
import de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration;
import de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(
        prefix = "spring.boot.admin.notify.dingtalk",
        name = {"webhook-token"}
)
@AutoConfigureBefore({AdminServerHazelcastAutoConfiguration.NotifierTriggerConfiguration.class, AdminServerNotifierAutoConfiguration.CompositeNotifierConfiguration.class})
public class DingTalkNotifierConfiguration {
    @Bean
    @ConditionalOnMissingBean
    @ConfigurationProperties(prefix = "spring.boot.admin.notify.dingtalk")
    public DingRobotNotifier dingTalkNotifier(InstanceRepository repository) {
        return new DingRobotNotifier(repository);
    }
}

SecurityConfig

java 复制代码
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final String adminContextPath;

    public SecurityConfig(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");

        http
            .authorizeRequests()
            // 放行客户端注册、健康检查等接口
            .antMatchers(adminContextPath + "/assets/**", adminContextPath + "/login", adminContextPath + "/actuator/**", adminContextPath + "/instances").permitAll()
            // 其他接口需要认证
            .anyRequest().authenticated()
            .and()
            // 表单登录
            .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).permitAll()
            .and()
            // 退出登录
            .logout().logoutUrl(adminContextPath + "/logout").permitAll()
            .and()
            // 关闭跨域保护(便于客户端注册)
            .csrf().disable();
    }
}

ServerStatusInfo

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ServerStatusInfo {
 
    STATUS_UP("UP", "服务上线"),
    STATUS_DOWN("DOWN", "服务下线"),
    STATUS_OFFLINE("OFFLINE", "服务离线"),
    STATUS_RESTRICTED("RESTRICTED", "服务受限"),
    STATUS_OUT_OF_SERVICE("OUT_OF_SERVICE", "无法使用"),
    STATUS_UNKNOWN("UNKNOWN", "未知");
 
    private String status;
 
    private String describe;
 
    public static String statusInfo(String status) {
        for (ServerStatusInfo enumValue : ServerStatusInfo.values()) {
            if (enumValue.getStatus().equals(status)) {
                return enumValue.getDescribe();
            }
        }
        return "未知";
    }
}

DingRobotNotifier

java 复制代码
import com.alibaba.fastjson.JSON;
import com.ruoyi.xingyuesba.enums.ServerStatusInfo;
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.notify.AbstractStatusChangeNotifier;
import lombok.extern.slf4j.Slf4j;
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.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Slf4j
public class DingRobotNotifier extends AbstractStatusChangeNotifier {
 
    private RestTemplate restTemplate = new RestTemplate();
    /**
     * webhook-token
     */
    private String webhookToken;
    /**
     * 是否可用
     */
    private Boolean enable;
    /**
     * 消息类型
     */
    private String msgtype;
    /**
     * 是否推送给所有成员
     */
    private Boolean atAll;
    /**
     * 指定手机号
     */
    private List atMobiles;
    /**
     * 指定人员
     */
    private List atUserIds;
    /**
     * 自定义关键词
     */
    private String title;
 
    public DingRobotNotifier(InstanceRepository repository) {
        super(repository);
    }
 
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
 
        HttpEntity<Map<String, Object>> message = createMessage(instance);
        return Mono.fromRunnable(() -> restTemplate.postForEntity(webhookToken, message, Void.class));
    }
 
 
    private HttpEntity<Map<String, Object>> createMessage(Instance instance) {
 
        //最外层
        HashMap<String, Object> finalMap = new HashMap<>();
        //msgtype
        finalMap.put("msgtype", this.msgtype);
        //text
        HashMap<String, String> contentMap = new HashMap<>();
        contentMap.put("content", this.getMessage(instance));
        finalMap.put("text", contentMap);
        //at
        HashMap<String, Object> atInsideMap = new HashMap<>();
        atInsideMap.put("isAtAll", this.atAll);
        atInsideMap.put("atMobiles", this.atMobiles);
        atInsideMap.put("atUserIds", this.atUserIds);
        finalMap.put("at", atInsideMap);
 
        log.info("finalMap:{}", JSON.toJSONString(finalMap));
 
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new HttpEntity<>(finalMap, headers);
    }
 
    private String getMessage(Instance instance) {
        String status = instance.getStatusInfo().getStatus();
        String serviceName = instance.getRegistration().getName();
        String serviceUrl = instance.getRegistration().getServiceUrl();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        Map<String, Object> details = instance.getStatusInfo().getDetails();
        StringBuilder str = new StringBuilder();
        str.append(">>>>>>>>服务告警<<<<<<<<" + "\n");
        str.append("【服务名称】" + serviceName + "\n");
        str.append("【预警时间】" + formatter.format(LocalDateTime.now()) + "\n");
        str.append("【服务地址】" + serviceUrl + "\n");
        str.append("【服务状态】" + status + "(" + ServerStatusInfo.statusInfo(status) + ")" + "\n");
        str.append("【预警详情】" + JSON.toJSONString(details) + "\n\n\n");
        return str.toString();
    }
 
    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
 
    public String getWebhookToken() {
        return webhookToken;
    }
 
    public void setWebhookToken(String webhookToken) {
        this.webhookToken = webhookToken;
    }
 
    public String getMsgtype() {
        return msgtype;
    }
 
    public void setMsgtype(String msgtype) {
        this.msgtype = msgtype;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public Boolean getEnable() {
        return enable;
    }
 
    public void setEnable(Boolean enable) {
        this.enable = enable;
    }
 
    public Boolean getAtAll() {
        return atAll;
    }
 
    public void setAtAll(Boolean atAll) {
        this.atAll = atAll;
    }
 
    public List getAtUserIds() {
        return atUserIds;
    }
 
    public void setAtUserIds(List atUserIds) {
        this.atUserIds = atUserIds;
    }
 
    public void setAtMobiles(List atMobiles) {
        this.atMobiles = atMobiles;
    }
}
4.配置
yaml 复制代码
server:
  port: 9999

# 可选:安全认证配置(若不添加,SBA 控制台无需登录即可访问)
spring:
  security:
    user:
      name: admin # SBA 控制台登录用户名
      password: admin123 # 登录密码
  application:
    name: xingyue-sba
  boot:
    admin:
      ui:
        title: 服务监控中心
        brand: <span>服务监控中心</span>
      notify:
        dingtalk:
          webhook-token:  webhook
          enabled: true
          msgtype: text
          atAll: false
          atMobiles: 手机号
          atUserIds:

# SBA 服务端日志配置(可选,便于排查问题)
logging:
  level:
    de.codecentric.boot.admin: DEBUG
    org.springframework.web: DEBUG
    

二. spa 客户端(业务代码)

1.添加依赖
java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
        </dependency>
2.添加配置
yaml 复制代码
management:
  endpoints:
    jmx:
      exposure:
        include: '*' # 暴露所有 JMX 端点
    web:
      exposure:
        include: '*' # 暴露所有 Web 端点
        exclude: configprops # 可选:排除 configprops(避免数据库连接释放问题,如你原有配置)
      base-path: /actuator # 端点基础路径(默认,可自定义)
  endpoint:
    health:
      show-details: always # 始终显示健康检查详情(便于 SBA 监控)
  info:
    env:
      enabled: true # 启用环境信息暴露

# 2. Spring Boot Admin 客户端配置
spring:
  boot:
    admin:
      client:
        url: http://localhost:9999 # SBA 服务端地址(与服务端配置一致)
        auto-registration: true # 开启自动注册(核心:确保客户端自动注册到服务端)
        register-on-startup: true # 启动时立即注册
        # 若 SBA 服务端配置了安全认证,需添加认证信息
        username: admin
        password: admin123
        # 客户端实例配置(便于在 SBA 控制台识别)
        instance:
          name: RuoYi-Admin-Backend # 实例名称(自定义,如:RuoYi 后端服务)
          service-base-url: http://localhost:8888 # RuoYi 后端实际访问地址(端口与 RuoYi 一致)
          prefer-ip: true # 优先使用 IP 地址注册(避免主机名解析问题)
3.放行actuator接口
java 复制代码
                requests
                        //springbootadmin
                        .antMatchers("/actuator/**").permitAll()

Nacos 托管 SBA

1.添加依赖
java 复制代码
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.10</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.ruoyi</groupId>
	<artifactId>xingyue-sba</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>xingyue-sba</name>
	<description>xingyue-sba</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>17</java.version>
		<spring-boot-admin.version>2.7.8</spring-boot-admin.version>
		<spring-cloud.version>2021.0.8</spring-cloud.version>
		<!-- 核心修正:替换为兼容版本 -->
		<spring-alibaba.version>2021.0.5.0</spring-alibaba.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>2.0.47</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.32</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<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>
			<!-- 无需单独指定版本,由 dependencyManagement 统一管控 -->
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
2.增加启动注解
java 复制代码
@SpringBootApplication
@EnableAdminServer
public class XingyueSbaApplication {

	public static void main(String[] args) {
		SpringApplication.run(XingyueSbaApplication.class, args);
	}

}
3. 钉钉报警

DingTalkNotifierConfiguration

java 复制代码
import com.ruoyi.xingyuesba.notifier.DingRobotNotifier;
import de.codecentric.boot.admin.server.config.AdminServerHazelcastAutoConfiguration;
import de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(
        prefix = "spring.boot.admin.notify.dingtalk",
        name = {"webhook-token"}
)
@AutoConfigureBefore({AdminServerHazelcastAutoConfiguration.NotifierTriggerConfiguration.class, AdminServerNotifierAutoConfiguration.CompositeNotifierConfiguration.class})
public class DingTalkNotifierConfiguration {
    @Bean
    @ConditionalOnMissingBean
    @ConfigurationProperties(prefix = "spring.boot.admin.notify.dingtalk")
    public DingRobotNotifier dingTalkNotifier(InstanceRepository repository) {
        return new DingRobotNotifier(repository);
    }
}

SecurityConfig

java 复制代码
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final String adminContextPath;

    public SecurityConfig(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");

        http
            .authorizeRequests()
            // 放行客户端注册、健康检查等接口
            .antMatchers(adminContextPath + "/assets/**", adminContextPath + "/login", adminContextPath + "/actuator/**", adminContextPath + "/instances").permitAll()
            // 其他接口需要认证
            .anyRequest().authenticated()
            .and()
            // 表单登录
            .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).permitAll()
            .and()
            // 退出登录
            .logout().logoutUrl(adminContextPath + "/logout").permitAll()
            .and()
            // 关闭跨域保护(便于客户端注册)
            .csrf().disable();
    }
}

ServerStatusInfo

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ServerStatusInfo {
 
    STATUS_UP("UP", "服务上线"),
    STATUS_DOWN("DOWN", "服务下线"),
    STATUS_OFFLINE("OFFLINE", "服务离线"),
    STATUS_RESTRICTED("RESTRICTED", "服务受限"),
    STATUS_OUT_OF_SERVICE("OUT_OF_SERVICE", "无法使用"),
    STATUS_UNKNOWN("UNKNOWN", "未知");
 
    private String status;
 
    private String describe;
 
    public static String statusInfo(String status) {
        for (ServerStatusInfo enumValue : ServerStatusInfo.values()) {
            if (enumValue.getStatus().equals(status)) {
                return enumValue.getDescribe();
            }
        }
        return "未知";
    }
}

DingRobotNotifier

java 复制代码
import com.alibaba.fastjson.JSON;
import com.ruoyi.xingyuesba.enums.ServerStatusInfo;
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.notify.AbstractStatusChangeNotifier;
import lombok.extern.slf4j.Slf4j;
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.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Slf4j
public class DingRobotNotifier extends AbstractStatusChangeNotifier {
 
    private RestTemplate restTemplate = new RestTemplate();
    /**
     * webhook-token
     */
    private String webhookToken;
    /**
     * 是否可用
     */
    private Boolean enable;
    /**
     * 消息类型
     */
    private String msgtype;
    /**
     * 是否推送给所有成员
     */
    private Boolean atAll;
    /**
     * 指定手机号
     */
    private List atMobiles;
    /**
     * 指定人员
     */
    private List atUserIds;
    /**
     * 自定义关键词
     */
    private String title;
 
    public DingRobotNotifier(InstanceRepository repository) {
        super(repository);
    }
 
    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
 
        HttpEntity<Map<String, Object>> message = createMessage(instance);
        return Mono.fromRunnable(() -> restTemplate.postForEntity(webhookToken, message, Void.class));
    }
 
 
    private HttpEntity<Map<String, Object>> createMessage(Instance instance) {
 
        //最外层
        HashMap<String, Object> finalMap = new HashMap<>();
        //msgtype
        finalMap.put("msgtype", this.msgtype);
        //text
        HashMap<String, String> contentMap = new HashMap<>();
        contentMap.put("content", this.getMessage(instance));
        finalMap.put("text", contentMap);
        //at
        HashMap<String, Object> atInsideMap = new HashMap<>();
        atInsideMap.put("isAtAll", this.atAll);
        atInsideMap.put("atMobiles", this.atMobiles);
        atInsideMap.put("atUserIds", this.atUserIds);
        finalMap.put("at", atInsideMap);
 
        log.info("finalMap:{}", JSON.toJSONString(finalMap));
 
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new HttpEntity<>(finalMap, headers);
    }
 
    private String getMessage(Instance instance) {
        String status = instance.getStatusInfo().getStatus();
        String serviceName = instance.getRegistration().getName();
        String serviceUrl = instance.getRegistration().getServiceUrl();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        Map<String, Object> details = instance.getStatusInfo().getDetails();
        StringBuilder str = new StringBuilder();
        str.append(">>>>>>>>服务告警<<<<<<<<" + "\n");
        str.append("【服务名称】" + serviceName + "\n");
        str.append("【预警时间】" + formatter.format(LocalDateTime.now()) + "\n");
        str.append("【服务地址】" + serviceUrl + "\n");
        str.append("【服务状态】" + status + "(" + ServerStatusInfo.statusInfo(status) + ")" + "\n");
        str.append("【预警详情】" + JSON.toJSONString(details) + "\n\n\n");
        return str.toString();
    }
 
    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
 
    public String getWebhookToken() {
        return webhookToken;
    }
 
    public void setWebhookToken(String webhookToken) {
        this.webhookToken = webhookToken;
    }
 
    public String getMsgtype() {
        return msgtype;
    }
 
    public void setMsgtype(String msgtype) {
        this.msgtype = msgtype;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public Boolean getEnable() {
        return enable;
    }
 
    public void setEnable(Boolean enable) {
        this.enable = enable;
    }
 
    public Boolean getAtAll() {
        return atAll;
    }
 
    public void setAtAll(Boolean atAll) {
        this.atAll = atAll;
    }
 
    public List getAtUserIds() {
        return atUserIds;
    }
 
    public void setAtUserIds(List atUserIds) {
        this.atUserIds = atUserIds;
    }
 
    public void setAtMobiles(List atMobiles) {
        this.atMobiles = atMobiles;
    }
}
4.配置
yaml 复制代码
server:
  port: 9999

# 可选:安全认证配置(若不添加,SBA 控制台无需登录即可访问)
spring:
  security:
    user:
      name: admin # SBA 控制台登录用户名
      password: admin123 # 登录密码
  application:
    name: xingyue-sba
  boot:
    admin:
      ui:
        title: 服务监控中心
        brand: <span>服务监控中心</span>
      notify:
        dingtalk:
          webhook-token: ***
          enabled: true
          msgtype: text
          atAll: false
          atMobiles: ***
          atUserIds:
  cloud:
    nacos:
      username: ***
      password: ***
      config:
        server-addr: ***
        namespace: ***
        prefix: ${spring.application.name}
        file-extension: yml
        ext-config: #配置公共配置列表
          - dataId: application.yml
            refresh: true
        refreshable-dataids: ${spring.application.name}.${spring.cloud.nacos.config.file-extension}
      discovery:
        server-addr: ***
        namespace: ***
        enabled: true

# SBA 服务端日志配置(可选,便于排查问题)
logging:
  level:
    de.codecentric.boot.admin: DEBUG
    org.springframework.web: DEBUG
    

二. spa 客户端(业务代码)

1.添加依赖
java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
        </dependency>
2.添加配置
yaml 复制代码
management:
  endpoints:
    jmx:
      exposure:
        include: '*' # 暴露所有 JMX 端点
    web:
      exposure:
        include: '*' # 暴露所有 Web 端点
        exclude: configprops # 可选:排除 configprops(避免数据库连接释放问题,如你原有配置)
      base-path: /actuator # 端点基础路径(默认,可自定义)
  endpoint:
    health:
      show-details: always # 始终显示健康检查详情(便于 SBA 监控)
  info:
    env:
      enabled: true # 启用环境信息暴露

# 2. Spring Boot Admin 客户端配置
spring:
  boot:
    admin:
      client:
        url: http://localhost:9999 # SBA 服务端地址(与服务端配置一致)
        auto-registration: true # 开启自动注册(核心:确保客户端自动注册到服务端)
        register-on-startup: true # 启动时立即注册
        # 若 SBA 服务端配置了安全认证,需添加认证信息
        username: admin
        password: admin123
        # 客户端实例配置(便于在 SBA 控制台识别)
        instance:
          name: RuoYi-Admin-Backend # 实例名称(自定义,如:RuoYi 后端服务)
          service-base-url: http://localhost:8888 # RuoYi 后端实际访问地址(端口与 RuoYi 一致)
          prefer-ip: true # 优先使用 IP 地址注册(避免主机名解析问题)
3.放行actuator接口
java 复制代码
                requests
                        //springbootadmin
                        .antMatchers("/actuator/**").permitAll()

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: ALWAYS

限制 /actuator/health 等 Actuator 接口的访问(避免任何人都能随意访问),同时保证 Spring Boot Admin 能正常获取健康状态,以下是分场景的完整解决方案:

一、限制 Actuator 接口访问

限制 Actuator 接口访问的核心是 通过权限框架(Spring Security)做访问控制:拒绝匿名访问(屏蔽未授权用户),仅允许认证用户/指定角色/指定IP访问,同时不影响 Spring Boot Admin 的健康检查。

二、分场景解决方案

场景1:项目已集成 Spring Security(你的微服务已使用,推荐)

在客户端(如 fws-bd-serverfws-mq-server)的 Security 配置类中,通过 hasRole/hasAuthority 限制访问,同时保留放行逻辑(仅允许认证用户访问)。

步骤1:修改 Security 配置类
java 复制代码
package com.ruoyi.xxx.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                // 核心配置:限制 Actuator 接口仅允许 认证用户 + 拥有ADMIN/ACTUATOR角色的用户访问
                // 拒绝匿名访问(屏蔽任何人随意访问)
                .antMatchers("/actuator/**").hasAnyRole("ADMIN", "ACTUATOR")
                // 其他业务接口配置
                .anyRequest().authenticated()
                .and()
            // 表单登录/HTTP Basic 认证(二选一,Spring Boot Admin 支持 Basic 认证)
            .httpBasic(); // 推荐用 HTTP Basic 认证,便于 Spring Boot Admin 配置账号密码访问
    }
}
步骤2:配置 Actuator 访问账号密码

在客户端 application.yml 中配置认证账号密码(供 Spring Boot Admin 使用):

yaml 复制代码
# 1. 原有 Actuator 配置(不变,保留暴露端点和显示详情)
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: ALWAYS

# 2. 配置 Spring Security 账号密码(限制 Actuator 访问)
spring:
  security:
    user:
      name: actuator_user  # 自定义用户名
      password: actuator_123456  # 自定义密码
      roles: ACTUATOR  # 赋予 ACTUATOR 角色,对应 Security 配置中的权限
步骤3:配置 Spring Boot Admin 客户端认证

为了让 Admin 能正常访问客户端的 Actuator 接口,需要在客户端配置 Admin 的认证信息(告诉客户端:Admin 访问时用指定账号密码):

yaml 复制代码
spring:
  boot:
    admin:
      client:
        url: http://你的Admin服务地址:端口  # 如 http://127.0.0.1:8080
        username: ${spring.security.user.name}  # 对应上面配置的用户名
        password: ${spring.security.user.password}  # 对应上面配置的密码
        instance:
          metadata:
            # 关键:把 Actuator 访问账号密码存入元数据,供 Admin 读取
            user.name: ${spring.security.user.name}
            user.password: ${spring.security.user.password}

场景2:Spring Security 6+(WebSecurityConfigurerAdapter 已过时)

若你的项目是 Spring Boot 3.x(对应 Spring Security 6+),使用新的配置方式:

java 复制代码
package com.ruoyi.xxx.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class ClientSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/**").hasAnyRole("ADMIN", "ACTUATOR")
                .anyRequest().authenticated()
            )
            .httpBasic(); // 开启 HTTP Basic 认证

        return http.build();
    }
}

账号密码配置和 Admin 客户端配置与场景1一致,无需修改。

场景3:更严格的限制(仅允许指定IP访问)

若你想进一步限制(仅允许 Admin 服务的IP访问 Actuator 接口),可在 Security 配置中添加 IP 白名单:

java 复制代码
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            // 仅允许 Admin 服务的IP(如 172.16.0.33)+ 认证用户访问
            .antMatchers("/actuator/**")
                .access("hasAnyRole('ADMIN','ACTUATOR') and hasIpAddress('172.16.0.33')")
            .anyRequest().authenticated()
            .and()
        .httpBasic();
}

三、关键说明

  1. 为什么 gateway 能访问?:gateway 要么未集成 Spring Security,要么其 Security 配置未对 Actuator 做权限限制,按上述方案给 gateway 配置后,也能限制其 Actuator 接口访问。
  2. 不影响 Admin 监控 :通过 spring.boot.admin.client 配置账号密码,Admin 会自动携带认证信息访问客户端 Actuator 接口,无需手动干预。
  3. 生产环境建议
    • 不要使用 include: '*',按需暴露端点(如 include: health,info),减少安全风险;
    • show-details 改为 WHEN_AUTHORIZED,仅允许认证用户查看健康详情;
    • 密码使用加密存储(如 BCrypt),避免明文配置。

四、验证效果

  1. 匿名用户直接访问 http://客户端IP:端口/actuator/health:会返回 401 未授权(无法访问,实现限制效果);
  2. 携带账号密码访问(如 Postman 开启 Basic 认证,输入配置的用户名密码):能正常返回健康检查 JSON;
  3. Spring Boot Admin 页面:客户端状态正常显示为 UP,说明 Admin 能正常访问。
相关推荐
AscendKing2 小时前
接口设计模式的简介 优势和劣势
java
Vincent_Vang2 小时前
多态 、抽象类、抽象类和具体类的区别、抽象方法和具体方法的区别 以及 重载和重写的相同和不同之处
java·开发语言·前端·ide
qualifying2 小时前
JavaEE——多线程(3)
java·开发语言·java-ee
花卷HJ2 小时前
Android 下载管理器封装实战:支持队列下载、取消、进度回调与自动保存相册
android·java
wanghowie2 小时前
01.01 Spring核心|IoC容器深度解析
java·后端·spring
人道领域2 小时前
【零基础学java】(Map集合)
java·开发语言
@淡 定2 小时前
Seata AT模式详细实例:电商下单场景
java
杀死那个蝈坦2 小时前
JUC并发编程day1
java·开发语言
飞Link2 小时前
【Java】Linux(CentOS7)下安装JDK8(Java)教程
java·linux·运维·服务器