This commit is contained in:
kk
2026-07-08 17:57:11 +08:00
commit f1e6599892
743 changed files with 159169 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.admin;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* admin启动器
*
* @author Chill
*/
@EnableAdminServer
@EnableDiscoveryClient
@SpringBootApplication
public class AdminApplication {
public static void main(String[] args) {
BladeApplication.run(AppConstant.APPLICATION_ADMIN_NAME, AdminApplication.class, args);
}
}

View File

@@ -0,0 +1,41 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.admin.config;
import org.springblade.admin.dingtalk.MonitorProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 启动器
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(MonitorProperties.class)
public class AdminConfiguration {
}

View File

@@ -0,0 +1,61 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.admin.config;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import org.springblade.admin.dingtalk.DingTalkNotifier;
import org.springblade.admin.dingtalk.DingTalkService;
import org.springblade.admin.dingtalk.MonitorProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.reactive.function.client.WebClient;
/**
* 钉钉自动配置
*
* @author L.cm
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "monitor.ding-talk.enabled", havingValue = "true")
public class DingTalkConfiguration {
@Bean
public DingTalkService dingTalkService(MonitorProperties properties,
WebClient.Builder builder) {
return new DingTalkService(properties, builder.build());
}
@Bean
public DingTalkNotifier dingTalkNotifier(MonitorProperties properties,
DingTalkService dingTalkService,
InstanceRepository repository,
Environment environment) {
return new DingTalkNotifier(dingTalkService, properties, environment, repository);
}
}

View File

@@ -0,0 +1,96 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.admin.config;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springblade.admin.security.InternalAuthorizationManager;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import java.net.URI;
/**
* 监控安全配置
*
* @author L.cm
*/
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(AdminServerProperties.class)
public class SecurityConfiguration {
private final String contextPath;
public SecurityConfiguration(AdminServerProperties adminServerProperties) {
this.contextPath = adminServerProperties.getContextPath();
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
RedirectServerAuthenticationSuccessHandler successHandler = new RedirectServerAuthenticationSuccessHandler();
successHandler.setLocation(URI.create(contextPath + "/"));
return http
// 明确调用headers()方法进行配置
.headers(headers -> headers
// 禁用frameOptions
.frameOptions(ServerHttpSecurity.HeaderSpec.FrameOptionsSpec::disable)
)
// 配置授权规则
.authorizeExchange(exchanges -> exchanges
.pathMatchers(
contextPath + "/assets/**",
contextPath + "/login",
contextPath + "/v1/agent/**",
contextPath + "/v1/catalog/**",
contextPath + "/v1/health/**"
).permitAll()
.pathMatchers(contextPath + "/actuator", contextPath + "/actuator/**")
.access(new InternalAuthorizationManager())
.anyExchange().authenticated()
)
// 配置表单登录
.formLogin(formLogin -> formLogin
.loginPage(contextPath + "/login")
.authenticationSuccessHandler(successHandler)
)
// 配置登出
.logout(logout -> logout
.logoutUrl(contextPath + "/logout")
)
// 禁用HTTP Basic认证
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
// 禁用CSRF
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.build();
// @formatter:on
}
}

View File

@@ -0,0 +1,114 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.admin.dingtalk;
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.domain.values.Registration;
import de.codecentric.boot.admin.server.domain.values.StatusInfo;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.lang.NonNull;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
* 服务上下线告警
*
* <p>
* 注意AbstractStatusChangeNotifier 这个事件有毛病
* </p>
*
* @author L.cm
*/
@Slf4j
public class DingTalkNotifier extends AbstractEventNotifier {
private final DingTalkService dingTalkService;
private final MonitorProperties properties;
private final Environment environment;
public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public DingTalkNotifier(DingTalkService dingTalkService, MonitorProperties properties,
Environment environment, InstanceRepository repository) {
super(repository);
this.dingTalkService = dingTalkService;
this.properties = properties;
this.environment = environment;
}
@NonNull
@Override
protected Mono<Void> doNotify(@NonNull InstanceEvent event, @NonNull Instance instance) {
if (event instanceof InstanceStatusChangedEvent) {
// 构造请求结构
return createAndPushMsg(event, instance);
}
return Mono.empty();
}
private Mono<Void> createAndPushMsg(InstanceEvent event, Instance instance) {
Registration registration = instance.getRegistration();
// 服务名
String appName = registration.getName();
// 服务地址
String serviceUrl = registration.getServiceUrl();
StatusInfo status = instance.getStatusInfo();
// 时间
LocalDateTime localDateTime = LocalDateTime.ofInstant(event.getTimestamp(), ZoneId.systemDefault());
MonitorProperties.DingTalk dingTalk = properties.getDingTalk();
String title = dingTalk.getService().getTitle();
String message = "## **" + title + "**\n" +
"#### **【服务】** " + appName + "\n" +
"#### **【环境】** " + environment.getActiveProfiles()[0] + "\n" +
"#### **【地址】** " + serviceUrl + "\n" +
"#### **【状态】** " + statusCn(status) + "\n" +
"#### **【时间】** " + DATETIME_FORMATTER.format(localDateTime) + "\n" +
"#### **【详情】** " + dingTalk.getLink() + "\n";
return dingTalkService.pushMsg(title, message);
}
private String statusCn(StatusInfo status) {
if (status.isUp()) {
return "应用上线IS UP";
} else if (status.isDown()) {
return "应用宕机IS DOWN";
} else if (status.isOffline()) {
return "应用掉线IS OFFLINE";
} else if (status.isUnknown()) {
return "未知状态UNKNOWN";
} else {
return "异常状态";
}
}
}

View File

@@ -0,0 +1,119 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.admin.dingtalk;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriUtils;
import reactor.core.publisher.Mono;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* 钉钉 服务
*
* @author L.cm
*/
@Slf4j
@RequiredArgsConstructor
public class DingTalkService {
private static final String DING_TALK_ROBOT_URL = "https://oapi.dingtalk.com/robot/send?access_token=";
private final MonitorProperties properties;
private final WebClient webClient;
/**
* 发送消息
*
* @param title title
* @param text 消息
*/
public Mono<Void> pushMsg(String title, String text) {
log.info("钉钉消息:[创建消息体]title:{}, text:{}", title, text);
HashMap<String, String> params = new HashMap<>(2);
params.put("title", title);
params.put("text", text);
Map<String, Object> body = new HashMap<>(2);
body.put("msgtype", "markdown");
body.put("markdown", params);
log.info("创建消息体 json{}", body);
MonitorProperties.DingTalk dingTalk = properties.getDingTalk();
String accessToken = dingTalk.getAccessToken();
if (!StringUtils.hasText(accessToken)) {
log.error("DingTalk alert config accessToken ${monitor.ding-talk.access-token} is blank.");
return Mono.empty();
}
String urlString = DING_TALK_ROBOT_URL + dingTalk.getAccessToken();
// 有私钥要签名
String secret = dingTalk.getSecret();
if (StringUtils.hasText(secret)) {
long timestamp = System.currentTimeMillis();
urlString += String.format("&timestamp=%s&sign=%s", timestamp, getSign(secret, timestamp));
}
return webClient.post()
.uri(URI.create(urlString))
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(body))
.retrieve()
.bodyToMono(String.class)
.doOnSuccess((result) -> log.info("钉钉消息:[消息返回]result:{}", result))
.then();
}
private static String getSign(String secret, long timestamp) {
String stringToSign = timestamp + "\n" + secret;
byte[] hmacSha256Bytes = digestHmac(stringToSign, secret);
return UriUtils.encode(Base64.getEncoder().encodeToString(hmacSha256Bytes), StandardCharsets.UTF_8);
}
public static byte[] digestHmac(String data, String key) {
SecretKey secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
try {
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e.getMessage());
}
}
}

View File

@@ -0,0 +1,76 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.admin.dingtalk;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
/**
* 监控配置
*
* @author L.cm
*/
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("monitor")
public class MonitorProperties {
private DingTalk dingTalk = new DingTalk();
@Getter
@Setter
public static class DingTalk {
/**
* 启用钉钉告警,默认为 true
*/
private boolean enabled = false;
/**
* 钉钉机器人 token
*/
private String accessToken;
/**
* 签名:如果有 secret 则进行签名,兼容老接口
*/
private String secret;
/**
* 地址配置
*/
private String link;
private Service service = new Service();
}
@Getter
@Setter
public static class Service {
/**
* 服务 状态 title
*/
private String title = "服务状态通知";
}
}

View File

@@ -0,0 +1,84 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.admin.security;
import org.springblade.core.launch.utils.INetUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Optional;
/**
* 内网认证管理,内网放行,外网认证
*
* @author L.cm
*/
public class InternalAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
private static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext context) {
return Mono.just(getAuthorizationDecision(context));
}
private static AuthorizationDecision getAuthorizationDecision(AuthorizationContext context) {
return new AuthorizationDecision(isInternalNet(context));
}
/**
* 判断是否内网 ip 请求
*
* @param context AuthorizationContext
* @return 是否内网 ip
*/
private static boolean isInternalNet(AuthorizationContext context) {
ServerHttpRequest request = Optional.ofNullable(context)
.map(AuthorizationContext::getExchange)
.map(ServerWebExchange::getRequest)
.orElse(null);
if (request == null) {
return false;
}
HttpHeaders headers = request.getHeaders();
// 如果没有 X-Forwarded-For 代表为 admin 拉取
if (!headers.containsKey(HEADER_X_FORWARDED_FOR)) {
return true;
}
return Optional.of(request)
.map(ServerHttpRequest::getRemoteAddress)
.map(InetSocketAddress::getAddress)
.map(INetUtil::isInternalIp)
.orElse(false);
}
}