This commit is contained in:
2025-06-17 17:08:14 +08:00
commit 71179324c8
1205 changed files with 798946 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
package org.dromara.modules.monitor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 监控中心
*
* @author ruoyi
*/
@SpringBootApplication
public class RuoYiMonitorApplication {
public static void main(String[] args) {
SpringApplication.run(RuoYiMonitorApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 监控中心启动成功 ლ(´ڡ`ლ)゙ ");
}
}

View File

@@ -0,0 +1,31 @@
package org.dromara.modules.monitor.config;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* springboot-admin server配置类
*
* @author Lion Li
*/
@Configuration
@EnableAdminServer
public class AdminServerConfig {
@Lazy
@Bean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
return builder.build();
}
}

View File

@@ -0,0 +1,54 @@
package org.dromara.modules.monitor.config;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* admin 监控 安全配置
*
* @author Lion Li
*/
@EnableWebSecurity
@Configuration
public class WebSecurityConfigurer {
private final String adminContextPath;
public WebSecurityConfigurer(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
return httpSecurity
.headers((header) ->
header.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(
new AntPathRequestMatcher(adminContextPath + "/assets/**"),
new AntPathRequestMatcher(adminContextPath + "/login")
).permitAll()
.anyRequest().authenticated())
.formLogin((formLogin) ->
formLogin.loginPage(adminContextPath + "/login").successHandler(successHandler))
.logout((logout) ->
logout.logoutUrl(adminContextPath + "/logout"))
.httpBasic(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.build();
}
}

View File

@@ -0,0 +1,71 @@
package org.dromara.modules.monitor.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 对接 prometheus
*
* @author Lion Li
*/
@Slf4j
@RestController
@RequestMapping("/actuator/prometheus")
public class PrometheusController {
@Autowired
private DiscoveryClient discoveryClient;
/**
* 从注册中心获取所有服务组装成 prometheus 的数据结构
*/
@GetMapping("/sd")
public List<Map<String, Object>> sd() {
List<String> services = discoveryClient.getServices();
if (services == null || services.isEmpty()) {
return new ArrayList<>(0);
}
List<Map<String, Object>> list = new ArrayList<>();
for (String service : services) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
List<String> targets = instances.stream().map(i -> i.getHost() + ":" + i.getPort()).collect(Collectors.toList());
Map<String, String> labels = new HashMap<>(2);
// 数据来源(区分异地使用)
// labels.put("__meta_datacenter", "beijing");
// 服务名
labels.put("__meta_prometheus_job", service);
String contextPath = instances.get(0).getMetadata().get("management.context-path");
if (contextPath != null) {
labels.put("__meta_http_sd_context_path", contextPath.replaceAll("/actuator", ""));
}
Map<String, Object> group = new HashMap<>(2);
group.put("targets", targets);
group.put("labels", labels);
list.add(group);
}
return list;
}
/**
* 接收 prometheus 报警消息
*
* @param message 消息体
*/
@PostMapping("/alerts")
public ResponseEntity<Void> alerts(@RequestBody String message) {
log.info("[prometheus] alert =>" + message);
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,57 @@
package org.dromara.modules.monitor.notifier;
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.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import static de.codecentric.boot.admin.server.domain.values.StatusInfo.*;
/**
* 自定义事件通知处理
*
* @author Lion Li
*/
@Slf4j
@Component
public class CustomNotifier extends AbstractEventNotifier {
protected CustomNotifier(InstanceRepository repository) {
super(repository);
}
@Override
@SuppressWarnings("all")
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> {
// 实例状态改变事件
if (event instanceof InstanceStatusChangedEvent) {
// 获取实例注册名称
String registName = instance.getRegistration().getName();
// 获取实例ID
String instanceId = event.getInstance().getValue();
// 获取实例状态
String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
// 获取服务URL
String serviceUrl = instance.getRegistration().getServiceUrl();
String statusName = switch (status) {
case STATUS_UP -> "服务上线"; // 实例成功启动并可以正常处理请求
case STATUS_OFFLINE -> "服务离线"; //实例被手动或自动地从服务中移除
case STATUS_RESTRICTED -> "服务受限"; //表示实例在某些方面受限,可能无法完全提供所有服务
case STATUS_OUT_OF_SERVICE -> "停止服务状态"; //表示实例已被标记为停止提供服务,可能是计划内维护或测试
case STATUS_DOWN -> "服务下线"; //实例因崩溃、错误或其他原因停止运行
case STATUS_UNKNOWN -> "服务未知异常"; //监控系统无法确定实例的当前状态
default -> "未知状态"; //没有匹配的状态
};
log.info("Instance Status Change: 状态名称【{}】, 注册名称【{}】, 实例ID【{}】, 状态【{}】, 服务URL【{}】",
statusName, registName, instanceId, status, serviceUrl);
}
});
}
}

View File

@@ -0,0 +1,33 @@
# Tomcat
server:
port: 9100
# Spring
spring:
application:
# 应用名称
name: ruoyi-monitor
profiles:
# 环境配置
active: @profiles.active@
--- # nacos 配置
spring:
cloud:
nacos:
# nacos 服务地址
server-addr: @nacos.server@
username: @nacos.username@
password: @nacos.password@
discovery:
# 注册组
group: @nacos.discovery.group@
namespace: ${spring.profiles.active}
config:
# 配置组
group: @nacos.config.group@
namespace: ${spring.profiles.active}
config:
import:
- optional:nacos:application-common.yml
- optional:nacos:${spring.application.name}.yml

View File

@@ -0,0 +1,10 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
_ _ _
(_) (_)| |
_ __ _ _ ___ _ _ _ ______ _ __ ___ ___ _ __ _ | |_ ___ _ __
| '__|| | | | / _ \ | | | || ||______|| '_ ` _ \ / _ \ | '_ \ | || __| / _ \ | '__|
| | | |_| || (_) || |_| || | | | | | | || (_) || | | || || |_ | (_) || |
|_| \__,_| \___/ \__, ||_| |_| |_| |_| \___/ |_| |_||_| \__| \___/ |_|
__/ |
|___/

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/${project.artifactId}" />
<!-- 日志输出格式 -->
<property name="console.log.pattern"
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<include resource="logback-common.xml" />
<include resource="logback-logstash.xml" />
<!-- 开启 skywalking 日志收集 -->
<include resource="logback-skylog.xml" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>