1. 统一包名修改

This commit is contained in:
chenbowen
2025-09-22 11:55:27 +08:00
parent a001fc8f16
commit 0d46897482
2739 changed files with 512 additions and 512 deletions

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.zt.plat</groupId>
<artifactId>zt-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zt-spring-boot-starter-env</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
开发环境拓展,实现类似阿里的特性环境的能力
1. https://segmentfault.com/a/1190000018022987
</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-common</artifactId>
</dependency>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<!-- RPC 相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
package com.zt.plat.framework.env.config;
import com.zt.plat.framework.env.core.fegin.EnvLoadBalancerClientFactory;
import com.zt.plat.framework.env.core.fegin.EnvRequestInterceptor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import java.util.Collections;
import java.util.List;
/**
* 多环境的 RPC 组件的自动配置
*
* @author ZT
*/
@AutoConfiguration
@EnableConfigurationProperties(EnvProperties.class)
public class CloudEnvRpcAutoConfiguration {
// ========== Feign 相关 ==========
/**
* 创建 {@link EnvLoadBalancerClientFactory} Bean
*
* 参考 {@link LoadBalancerAutoConfiguration#loadBalancerClientFactory(LoadBalancerClientsProperties)} 方法
*/
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties,
ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
EnvLoadBalancerClientFactory clientFactory = new EnvLoadBalancerClientFactory(properties);
clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));
return clientFactory;
}
@Bean
public EnvRequestInterceptor envRequestInterceptor() {
return new EnvRequestInterceptor();
}
}

View File

@@ -0,0 +1,32 @@
package com.zt.plat.framework.env.config;
import com.zt.plat.framework.common.enums.WebFilterOrderEnum;
import com.zt.plat.framework.env.core.web.EnvWebFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* 多环境的 Web 组件的自动配置
*
* @author ZT
*/
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableConfigurationProperties(EnvProperties.class)
public class CloudEnvWebAutoConfiguration {
/**
* 创建 {@link EnvWebFilter} Bean
*/
@Bean
public FilterRegistrationBean<EnvWebFilter> envWebFilterFilter() {
EnvWebFilter filter = new EnvWebFilter();
FilterRegistrationBean<EnvWebFilter> bean = new FilterRegistrationBean<>(filter);
bean.setOrder(WebFilterOrderEnum.ENV_TAG_FILTER);
return bean;
}
}

View File

@@ -0,0 +1,50 @@
package com.zt.plat.framework.env.config;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.util.collection.SetUtils;
import com.zt.plat.framework.env.core.util.EnvUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.Set;
import static com.zt.plat.framework.env.core.util.EnvUtils.HOST_NAME_VALUE;
/**
* 多环境的 {@link EnvEnvironmentPostProcessor} 实现类
* 将 cloud.env.tag 设置到 nacos 等组件对应的 tag 配置项,当且仅当它们不存在时
*
* @author ZT
*/
public class EnvEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final Set<String> TARGET_TAG_KEYS = SetUtils.asSet(
"spring.cloud.nacos.discovery.metadata.tag" // Nacos 注册中心
// MQ TODO
);
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 0. 设置 ${HOST_NAME} 兜底的环境变量
String hostNameKey = StrUtil.subBetween(HOST_NAME_VALUE, "{", "}");
if (!environment.containsProperty(hostNameKey)) {
environment.getSystemProperties().put(hostNameKey, EnvUtils.getHostName());
}
// 1.1 如果没有 cloud.env.tag 配置项,则不进行配置项的修改
String tag = EnvUtils.getTag(environment);
if (StrUtil.isEmpty(tag)) {
return;
}
// 1.2 需要修改的配置项
for (String targetTagKey : TARGET_TAG_KEYS) {
String targetTagValue = environment.getProperty(targetTagKey);
if (StrUtil.isNotEmpty(targetTagValue)) {
continue;
}
environment.getSystemProperties().put(targetTagKey, tag);
}
}
}

View File

@@ -0,0 +1,22 @@
package com.zt.plat.framework.env.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 环境配置
*
* @author ZT
*/
@ConfigurationProperties(prefix = "cloud.env")
@Data
public class EnvProperties {
public static final String TAG_KEY = "cloud.env.tag";
/**
* 环境标签
*/
private String tag;
}

View File

@@ -0,0 +1,39 @@
package com.zt.plat.framework.env.core.context;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.ArrayList;
import java.util.List;
/**
* 开发环境上下文
*
* @author ZT
*/
public class EnvContextHolder {
/**
* 标签的上下文
*
* 使用 {@link List} 的原因,可能存在多层设置或者清理
*/
private static final ThreadLocal<List<String>> TAG_CONTEXT = TransmittableThreadLocal.withInitial(ArrayList::new);
public static void setTag(String tag) {
TAG_CONTEXT.get().add(tag);
}
public static String getTag() {
return CollUtil.getLast(TAG_CONTEXT.get());
}
public static void removeTag() {
List<String> tags = TAG_CONTEXT.get();
if (CollUtil.isEmpty(tags)) {
return;
}
tags.remove(tags.size() - 1);
}
}

View File

@@ -0,0 +1,83 @@
package com.zt.plat.framework.env.core.fegin;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.common.util.collection.CollectionUtils;
import com.zt.plat.framework.env.core.context.EnvContextHolder;
import com.zt.plat.framework.env.core.util.EnvUtils;
import com.alibaba.cloud.nacos.balancer.NacosBalancer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* 多环境的 {@link org.springframework.cloud.client.loadbalancer.LoadBalancerClient} 实现类
* 在从服务实例列表选择时,优先选择 tag 匹配的服务实例
*
* @author ZT
*/
@RequiredArgsConstructor
@Slf4j
public class EnvLoadBalancerClient implements ReactorServiceInstanceLoadBalancer {
/**
* 用于获取 serviceId 对应的服务实例的列表
*/
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
/**
* 需要获取的服务实例名
*
* 暂时用于打印 logger 日志
*/
private final String serviceId;
/**
* 被代理的 ReactiveLoadBalancer 对象
*/
private final ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer;
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
// 情况一,没有 tag 时,使用默认的 reactiveLoadBalancer 实现负载均衡
String tag = EnvContextHolder.getTag();
if (StrUtil.isEmpty(tag)) {
return Mono.from(reactiveLoadBalancer.choose(request));
}
// 情况二,有 tag 时,使用 tag 匹配服务实例
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map(list -> getInstanceResponse(list, tag));
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, String tag) {
// 如果服务实例为空,则直接返回
if (CollUtil.isEmpty(instances)) {
log.warn("[getInstanceResponse][serviceId({}) 服务实例列表为空]", serviceId);
return new EmptyResponse();
}
// 筛选满足条件的实例列表
List<ServiceInstance> chooseInstances = CollectionUtils.filterList(instances, instance -> tag.equals(EnvUtils.getTag(instance)));
if (CollUtil.isEmpty(chooseInstances)) {
log.warn("[getInstanceResponse][serviceId({}) 没有满足 tag({}) 的服务实例列表,直接使用所有服务实例列表]", serviceId, tag);
chooseInstances = instances;
}
// TODO 芋艿https://juejin.cn/post/7056770721858469896 想通网段
// 随机 + 权重获取实例列表 TODO 芋艿:目前直接使用 Nacos 提供的方法,如果替换注册中心,需要重新失败该方法
return new DefaultResponse(NacosBalancer.getHostByRandomWeight3(chooseInstances));
}
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.framework.env.core.fegin;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
/**
* 多环境的 {@link LoadBalancerClientFactory} 实现类
* 目的:在创建 {@link ReactiveLoadBalancer} 时,会额外增加 {@link EnvLoadBalancerClient} 代理,用于 tag 过滤服务实例
*
* @author ZT
*/
public class EnvLoadBalancerClientFactory extends LoadBalancerClientFactory {
public EnvLoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
super(properties);
}
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer = super.getInstance(serviceId);
// 参考 {@link com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration#nacosLoadBalancer(Environment, LoadBalancerClientFactory, NacosDiscoveryProperties)} 方法
return new EnvLoadBalancerClient(super.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
serviceId, reactiveLoadBalancer);
}
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.framework.env.core.fegin;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.env.core.context.EnvContextHolder;
import com.zt.plat.framework.env.core.util.EnvUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
* 多环境的 {@link RequestInterceptor} 实现类Feign 请求时,将 tag 设置到 header 中,继续透传给被调用的服务
*
* @author ZT
*/
public class EnvRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
String tag = EnvContextHolder.getTag();
if (StrUtil.isNotEmpty(tag)) {
EnvUtils.setTag(requestTemplate, tag);
}
}
}

View File

@@ -0,0 +1 @@
package com.zt.plat.framework.env.core;

View File

@@ -0,0 +1,56 @@
package com.zt.plat.framework.env.core.util;
import com.zt.plat.framework.env.config.EnvProperties;
import feign.RequestTemplate;
import lombok.SneakyThrows;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.env.Environment;
import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.util.Objects;
/**
* 环境 Utils
*
* @author ZT
*/
public class EnvUtils {
private static final String HEADER_TAG = "tag";
public static final String HOST_NAME_VALUE = "${HOSTNAME}";
public static String getTag(HttpServletRequest request) {
String tag = request.getHeader(HEADER_TAG);
// 如果请求的是 "${HOSTNAME}",则解析成对应的本地主机名
// 目的:特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做
return Objects.equals(tag, HOST_NAME_VALUE) ? getHostName() : tag;
}
public static String getTag(ServiceInstance instance) {
return instance.getMetadata().get(HEADER_TAG);
}
public static String getTag(Environment environment) {
String tag = environment.getProperty(EnvProperties.TAG_KEY);
// 如果请求的是 "${HOSTNAME}",则解析成对应的本地主机名
// 目的:特殊逻辑,解决 IDEA Rest Client 不支持环境变量的读取,所以就服务器来做
return Objects.equals(tag, HOST_NAME_VALUE) ? getHostName() : tag;
}
public static void setTag(RequestTemplate requestTemplate, String tag) {
requestTemplate.header(HEADER_TAG, tag);
}
/**
* 获得 hostname 主机名
*
* @return 主机名
*/
@SneakyThrows
public static String getHostName() {
return InetAddress.getLocalHost().getHostName();
}
}

View File

@@ -0,0 +1,41 @@
package com.zt.plat.framework.env.core.web;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.framework.env.core.context.EnvContextHolder;
import com.zt.plat.framework.env.core.util.EnvUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 环境的 {@link jakarta.servlet.Filter} 实现类
* 当有 tag 请求头时,设置到 {@link EnvContextHolder} 的标签上下文
*
* @author ZT
*/
public class EnvWebFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 如果没有 tag则走默认的流程
String tag = EnvUtils.getTag(request);
if (StrUtil.isEmpty(tag)) {
chain.doFilter(request, response);
return;
}
// 如果有 tag则设置到上下文
EnvContextHolder.setTag(tag);
try {
chain.doFilter(request, response);
} finally {
EnvContextHolder.removeTag();
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* 开发环境拓展,实现类似阿里的特性环境的能力
* 1. https://segmentfault.com/a/1190000018022987
*
* @author ZT
*/
package com.zt.plat.framework.env;

View File

@@ -0,0 +1,2 @@
org.springframework.boot.env.EnvironmentPostProcessor=\
com.zt.plat.framework.env.config.EnvEnvironmentPostProcessor

View File

@@ -0,0 +1,2 @@
com.zt.plat.framework.env.config.CloudEnvWebAutoConfiguration
com.zt.plat.framework.env.config.CloudEnvRpcAutoConfiguration