feat: initial iShare project code

This commit is contained in:
purovps
2026-02-16 23:20:59 +08:00
parent 8c83a6fd46
commit 6f270a972e
1910 changed files with 218015 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common</artifactId>
<version>5.2.0</version>
</parent>
<artifactId>pigx-common-gray</artifactId>
<packaging>jar</packaging>
<description>pigx 灰度路由</description>
<dependencies>
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-core</artifactId>
</dependency>
<!-- LB 扩展 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.gray;
import com.pig4cloud.pigx.common.gray.feign.GrayFeignRequestInterceptor;
import com.pig4cloud.pigx.common.gray.rule.GrayLoadBalancerClientConfiguration;
import feign.RequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lengleng
* @date 2020/1/12
*/
@Configuration
@ConditionalOnProperty(value = "gray.rule.enabled", matchIfMissing = true)
@LoadBalancerClients(defaultConfiguration = GrayLoadBalancerClientConfiguration.class)
public class GrayLoadBalancerAutoConfiguration {
@Bean
public RequestInterceptor grayFeignRequestInterceptor() {
return new GrayFeignRequestInterceptor();
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.gray.feign;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import com.pig4cloud.pigx.common.gray.support.NonWebVersionContextHolder;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
/**
* @author lengleng
* @date 2020/1/12
* <p>
* feign 请求VERSION 传递
*/
@Slf4j
public class GrayFeignRequestInterceptor implements RequestInterceptor {
/**
* Called for every request. Add data using methods on the supplied
* {@link RequestTemplate}.
* @param template
*/
@Override
public void apply(RequestTemplate template) {
String reqVersion = WebUtils.getRequest() != null ? WebUtils.getRequest().getHeader(CommonConstants.VERSION)
: NonWebVersionContextHolder.getVersion();
if (StrUtil.isNotBlank(reqVersion)) {
log.debug("feign gray add header version :{}", reqVersion);
template.header(CommonConstants.VERSION, reqVersion);
}
}
}

View File

@@ -0,0 +1,27 @@
package com.pig4cloud.pigx.common.gray.rule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* @author lengleng
* @date 2020/12/29
*/
public class GrayLoadBalancerClientConfiguration extends LoadBalancerClientConfiguration {
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new GrayRoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}

View File

@@ -0,0 +1,133 @@
package com.pig4cloud.pigx.common.gray.rule;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cloud.nacos.NacosServiceInstance;
import com.alibaba.nacos.client.naming.utils.Chooser;
import com.alibaba.nacos.client.naming.utils.Pair;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author lengleng
* @date 2020/11/20
*/
@Slf4j
public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer {
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private String serviceId;
/**
* @param serviceInstanceListSupplierProvider a provider of
* {@link ServiceInstanceListSupplier} that will be used to get available instances
* @param serviceId id of the service for which to choose an instance
*/
public GrayRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
super(serviceInstanceListSupplierProvider, serviceId);
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.serviceId = serviceId;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map(serviceInstances -> getInstanceResponse(serviceInstances, request));
}
Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
// 注册中心无可用实例 抛出异常
if (CollUtil.isEmpty(instances)) {
log.warn("No instance available serviceId: {}", serviceId);
return new EmptyResponse();
}
if (request == null || request.getContext() == null) {
return super.choose(request).block();
}
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
if (!(requestContext.getClientRequest() instanceof RequestData)) {
return super.choose(request).block();
}
RequestData clientRequest = (RequestData) requestContext.getClientRequest();
HttpHeaders headers = clientRequest.getHeaders();
String reqVersion = headers.getFirst(CommonConstants.VERSION);
if (StrUtil.isBlank(reqVersion)) {
// 过滤出不含VERSION实例
List<ServiceInstance> versionInstanceList = instances.stream()
.filter(instance -> !instance.getMetadata().containsKey(CommonConstants.VERSION))
.collect(Collectors.toList());
if (CollUtil.isEmpty(versionInstanceList)) {
// 根据权重获取实例
return new DefaultResponse(randomByWeight(instances));
}
// 根据权重获取实例
return new DefaultResponse(randomByWeight(versionInstanceList));
}
// 遍历可以实例元数据,若匹配则返回此实例
List<ServiceInstance> serviceInstanceList = instances.stream().filter(instance -> {
NacosServiceInstance nacosInstance = (NacosServiceInstance) instance;
Map<String, String> metadata = nacosInstance.getMetadata();
String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION);
return reqVersion.equalsIgnoreCase(targetVersion);
}).collect(Collectors.toList());
// 存在 随机返回
if (CollUtil.isNotEmpty(serviceInstanceList)) {
ServiceInstance instance = randomByWeight(serviceInstanceList);
log.debug("gray instance available serviceId: {} , instanceId: {}", serviceId, instance.getInstanceId());
return new DefaultResponse(instance);
}
else {
// 不存在,降级策略,使用轮询策略
return super.choose(request).block();
}
}
/**
* 根据nacos设置的权重返回实例
* @param serviceInstances 服务实例集合
* @return 服务实例ServiceInstance
*/
protected ServiceInstance randomByWeight(final List<ServiceInstance> serviceInstances) {
if (serviceInstances.size() == 1) {
return serviceInstances.get(0);
}
else {
List<Pair<ServiceInstance>> hostsWithWeight = new ArrayList<>();
for (ServiceInstance serviceInstance : serviceInstances) {
if ("true".equals(serviceInstance.getMetadata().getOrDefault("nacos.healthy", "true"))) {
hostsWithWeight.add(new Pair<>(serviceInstance,
Double.parseDouble(serviceInstance.getMetadata().getOrDefault("nacos.weight", "1"))));
}
}
Chooser<String, ServiceInstance> vipChooser = new Chooser<>("www.taobao.com", hostsWithWeight);
return vipChooser.randomWithWeight();
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.gray.support;
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.experimental.UtilityClass;
/**
* @author lengleng
* @date 2020-10-16
* <p>
* 灰度版本号传递工具 ,在非web 调用feign 传递之前手动setVersion
*/
@UtilityClass
public class NonWebVersionContextHolder {
private final ThreadLocal<String> THREAD_LOCAL_VERSION = new TransmittableThreadLocal<>();
/**
* TTL 设置版本号<br/>
* @param version 版本号
*/
public void setVersion(String version) {
THREAD_LOCAL_VERSION.set(version);
}
/**
* 获取TTL中的版本号
* @return 版本 || null
*/
public String getVersion() {
return THREAD_LOCAL_VERSION.get();
}
public void clear() {
THREAD_LOCAL_VERSION.remove();
}
}

View File

@@ -0,0 +1,12 @@
{
"properties": [
{
"name": "gray.rule.enabled",
"type": "java.lang.Boolean",
"defaultValue": false,
"sourceType": "com.pig4cloud.pigx.common.gray.GrayLoadBalancerAutoConfiguration",
"description": "是否开启灰度路由功能"
}
],
"hints": []
}

View File

@@ -0,0 +1 @@
com.pig4cloud.pigx.common.gray.GrayLoadBalancerAutoConfiguration