在 Spring Boot Admin(SBA)的监控体系中,引入 Nacos 的核心意义是 实现微服务集群的自动化监控管理,解决传统 SBA 模式下的痛点,具体价值体现在这 3 个核心维度:
-
自动化服务发现,告别手动配置
传统 SBA 中,每个微服务客户端都需要手动配置
spring.boot.admin.client.url才能注册到服务端。接入 Nacos 后,所有微服务只需注册到 Nacos 服务中心,SBA 会自动从 Nacos 拉取全量服务列表,无需为每个微服务单独配置 SBA 地址,新增微服务时也无需修改 SBA 配置,直接实现"即插即监控"。 -
配置与服务的集中化管控
- 一方面,Nacos 配置中心可托管 SBA 和所有微服务的核心配置(如监控规则、告警阈值、Actuator 端点配置),支持动态刷新配置,无需重启 SBA 或微服务即可生效;
- 另一方面,Nacos 提供统一的服务注册中心,结合 SBA 的监控能力,可实现"服务注册-健康监控-状态告警"的全链路闭环管理。
-
适配微服务架构的弹性伸缩
在微服务集群扩缩容(如 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-server、fws-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();
}
三、关键说明
- 为什么 gateway 能访问?:gateway 要么未集成 Spring Security,要么其 Security 配置未对 Actuator 做权限限制,按上述方案给 gateway 配置后,也能限制其 Actuator 接口访问。
- 不影响 Admin 监控 :通过
spring.boot.admin.client配置账号密码,Admin 会自动携带认证信息访问客户端 Actuator 接口,无需手动干预。 - 生产环境建议 :
- 不要使用
include: '*',按需暴露端点(如include: health,info),减少安全风险; show-details改为WHEN_AUTHORIZED,仅允许认证用户查看健康详情;- 密码使用加密存储(如 BCrypt),避免明文配置。
- 不要使用
四、验证效果
- 匿名用户直接访问
http://客户端IP:端口/actuator/health:会返回 401 未授权(无法访问,实现限制效果); - 携带账号密码访问(如 Postman 开启 Basic 认证,输入配置的用户名密码):能正常返回健康检查 JSON;
- Spring Boot Admin 页面:客户端状态正常显示为
UP,说明 Admin 能正常访问。