feat: initial iShare project code
This commit is contained in:
32
pigx-common/pigx-common-feign/pom.xml
Normal file
32
pigx-common/pigx-common-feign/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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.pig4cloud</groupId>
|
||||
<artifactId>pigx-common</artifactId>
|
||||
<version>5.2.0</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>pigx-common-feign</artifactId>
|
||||
<description>feign 通用组件</description>
|
||||
<dependencies>
|
||||
<!--feign 依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<!-- okhttp 扩展 -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
</dependency>
|
||||
<!-- LB 扩展 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.feign;
|
||||
|
||||
import com.pig4cloud.pigx.common.feign.endpoint.FeignClientEndpoint;
|
||||
import feign.Feign;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.PigxFeignClientsRegistrar;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020/2/8
|
||||
* <p>
|
||||
* feign 自动化配置
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@Import(PigxFeignClientsRegistrar.class)
|
||||
@AutoConfigureAfter(EnableFeignClients.class)
|
||||
public class PigxFeignAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnAvailableEndpoint
|
||||
public FeignClientEndpoint feignClientEndpoint(ApplicationContext context) {
|
||||
return new FeignClientEndpoint(context);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.feign.annotation;
|
||||
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @date 2020-02-08
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@EnableFeignClients
|
||||
public @interface EnablePigxFeignClients {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
|
||||
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
|
||||
* {@code @ComponentScan(basePackages="org.my.pkg")}.
|
||||
* @return the array of 'basePackages'.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for annotated components.
|
||||
* <p>
|
||||
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
|
||||
* <p>
|
||||
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
|
||||
* package names.
|
||||
* @return the array of 'basePackages'.
|
||||
*/
|
||||
String[] basePackages() default { "com.pig4cloud.pigx" };
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
|
||||
* scan for annotated components. The package of each class specified will be scanned.
|
||||
* <p>
|
||||
* Consider creating a special no-op marker class or interface in each package that
|
||||
* serves no purpose other than being referenced by this attribute.
|
||||
* @return the array of 'basePackageClasses'.
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
/**
|
||||
* A custom <code>@Configuration</code> for all feign clients. Can contain override
|
||||
* <code>@Bean</code> definition for the pieces that make up the client, for instance
|
||||
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
|
||||
*
|
||||
* @see FeignClientsConfiguration for the defaults
|
||||
*/
|
||||
Class<?>[] defaultConfiguration() default {};
|
||||
|
||||
/**
|
||||
* List of classes annotated with @FeignClient. If not empty, disables classpath
|
||||
* scanning.
|
||||
* @return
|
||||
*/
|
||||
Class<?>[] clients() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.pig4cloud.pigx.common.feign.endpoint;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Feign client 端点
|
||||
*
|
||||
* @author L.cm
|
||||
*/
|
||||
@Endpoint(id = "feign")
|
||||
public class FeignClientEndpoint implements SmartInitializingSingleton {
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
private final List<FeignClientInfo> clientList;
|
||||
|
||||
public FeignClientEndpoint(ApplicationContext context) {
|
||||
this.context = context;
|
||||
this.clientList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<FeignClientInfo> invoke() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
clientList.addAll(getClientList(context));
|
||||
}
|
||||
|
||||
private static List<FeignClientInfo> getClientList(ApplicationContext context) {
|
||||
Map<String, Object> feignClientMap = context.getBeansWithAnnotation(FeignClient.class);
|
||||
// 1. 解析注解
|
||||
List<FeignClientInfo> feignClientInfoList = new ArrayList<>();
|
||||
Set<Map.Entry<String, Object>> feignClientEntrySet = feignClientMap.entrySet();
|
||||
for (Map.Entry<String, Object> feignClientEntry : feignClientEntrySet) {
|
||||
String beanName = feignClientEntry.getKey();
|
||||
Object feignClientBean = feignClientEntry.getValue();
|
||||
if (feignClientBean == null) {
|
||||
continue;
|
||||
}
|
||||
// 解析注解
|
||||
Class<?> feignClientClass = feignClientBean.getClass();
|
||||
FeignClient feignClientAnn = AnnotationUtils.findAnnotation(feignClientClass, FeignClient.class);
|
||||
if (feignClientAnn == null) {
|
||||
continue;
|
||||
}
|
||||
FeignClientInfo feignClientInfo = new FeignClientInfo();
|
||||
feignClientInfo.setBeanName(beanName);
|
||||
String serviceId = feignClientAnn.value();
|
||||
String contextId = feignClientAnn.contextId();
|
||||
String url = feignClientAnn.url();
|
||||
String path = feignClientAnn.path();
|
||||
feignClientInfo.setServiceId(serviceId);
|
||||
feignClientInfo.setContextId(contextId);
|
||||
feignClientInfo.setUrl(url);
|
||||
feignClientInfo.setPath(path);
|
||||
// 组装客户端信息
|
||||
List<ClientInfo> clientInfoList = new ArrayList<>();
|
||||
Class<?>[] interfaces = feignClientClass.getInterfaces();
|
||||
for (Class<?> clientInterface : interfaces) {
|
||||
Method[] methods = clientInterface.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.isDefault()) {
|
||||
continue;
|
||||
}
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.getMergedAnnotation(method,
|
||||
RequestMapping.class);
|
||||
if (requestMapping == null) {
|
||||
continue;
|
||||
}
|
||||
clientInfoList.add(new ClientInfo(requestMapping.method(), requestMapping.value()));
|
||||
}
|
||||
}
|
||||
feignClientInfo.setClientList(clientInfoList);
|
||||
feignClientInfoList.add(feignClientInfo);
|
||||
}
|
||||
return feignClientInfoList;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class FeignClientInfo {
|
||||
|
||||
private String beanName;
|
||||
|
||||
private String serviceId;
|
||||
|
||||
private String contextId;
|
||||
|
||||
private String url;
|
||||
|
||||
private String path;
|
||||
|
||||
private List<ClientInfo> clientList;
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class ClientInfo {
|
||||
|
||||
private final RequestMethod[] methods;
|
||||
|
||||
private final String[] mappings;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.openfeign;
|
||||
|
||||
import com.pig4cloud.pigx.common.feign.PigxFeignAutoConfiguration;
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.boot.context.annotation.ImportCandidates;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author L.cm
|
||||
* @date 2020/2/8
|
||||
* <p>
|
||||
* feign 自动配置功能 from mica
|
||||
*/
|
||||
public class PigxFeignClientsRegistrar
|
||||
implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware {
|
||||
|
||||
private final static String BASE_URL = "http://127.0.0.1:${server.port}${server.servlet.context-path}";
|
||||
|
||||
@Getter
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
@Getter
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||
registerFeignClients(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
private void registerFeignClients(BeanDefinitionRegistry registry) {
|
||||
List<String> feignClients = new ArrayList<>();
|
||||
|
||||
// 支持 springboot 2.7 + 最新版本的配置方式
|
||||
ImportCandidates.load(FeignClient.class, getBeanClassLoader()).forEach(feignClients::add);
|
||||
|
||||
// 如果 spring.factories 里为空
|
||||
if (feignClients.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (String className : feignClients) {
|
||||
try {
|
||||
Class<?> clazz = beanClassLoader.loadClass(className);
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz,
|
||||
FeignClient.class);
|
||||
if (attributes == null) {
|
||||
continue;
|
||||
}
|
||||
// 如果已经存在该 bean,支持原生的 Feign
|
||||
if (registry.containsBeanDefinition(className)) {
|
||||
continue;
|
||||
}
|
||||
registerClientConfiguration(registry, getClientName(attributes), attributes.get("configuration"));
|
||||
|
||||
validate(attributes);
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(FeignClientFactoryBean.class);
|
||||
definition.addPropertyValue("url", getUrl(attributes));
|
||||
definition.addPropertyValue("path", getPath(attributes));
|
||||
String name = getName(attributes);
|
||||
definition.addPropertyValue("name", name);
|
||||
|
||||
// 兼容最新版本的 spring-cloud-openfeign,尚未发布
|
||||
StringBuilder aliasBuilder = new StringBuilder(18);
|
||||
if (attributes.containsKey("contextId")) {
|
||||
String contextId = getContextId(attributes);
|
||||
aliasBuilder.append(contextId);
|
||||
definition.addPropertyValue("contextId", contextId);
|
||||
}
|
||||
else {
|
||||
aliasBuilder.append(name);
|
||||
}
|
||||
|
||||
definition.addPropertyValue("type", className);
|
||||
definition.addPropertyValue("decode404", attributes.get("decode404"));
|
||||
definition.addPropertyValue("fallback", attributes.get("fallback"));
|
||||
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
|
||||
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
|
||||
|
||||
// alias
|
||||
String alias = aliasBuilder.append("FeignClient").toString();
|
||||
|
||||
// has a default, won't be null
|
||||
boolean primary = (Boolean) attributes.get("primary");
|
||||
|
||||
beanDefinition.setPrimary(primary);
|
||||
|
||||
String qualifier = getQualifier(attributes);
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
alias = qualifier;
|
||||
}
|
||||
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
|
||||
new String[] { alias });
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class used by {@link SpringFactoriesLoader} to load configuration
|
||||
* candidates.
|
||||
* @return the factory class
|
||||
*/
|
||||
private Class<?> getSpringFactoriesLoaderFactoryClass() {
|
||||
return PigxFeignAutoConfiguration.class;
|
||||
}
|
||||
|
||||
private void validate(Map<String, Object> attributes) {
|
||||
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
|
||||
// This blows up if an aliased property is overspecified
|
||||
FeignClientsRegistrar.validateFallback(annotation.getClass("fallback"));
|
||||
FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory"));
|
||||
}
|
||||
|
||||
private String getName(Map<String, Object> attributes) {
|
||||
String name = (String) attributes.get("serviceId");
|
||||
if (!StringUtils.hasText(name)) {
|
||||
name = (String) attributes.get("name");
|
||||
}
|
||||
if (!StringUtils.hasText(name)) {
|
||||
name = (String) attributes.get("value");
|
||||
}
|
||||
name = resolve(name);
|
||||
return FeignClientsRegistrar.getName(name);
|
||||
}
|
||||
|
||||
private String getContextId(Map<String, Object> attributes) {
|
||||
String contextId = (String) attributes.get("contextId");
|
||||
if (!StringUtils.hasText(contextId)) {
|
||||
return getName(attributes);
|
||||
}
|
||||
|
||||
contextId = resolve(contextId);
|
||||
return FeignClientsRegistrar.getName(contextId);
|
||||
}
|
||||
|
||||
private String resolve(String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return this.environment.resolvePlaceholders(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String getUrl(Map<String, Object> attributes) {
|
||||
// 如果是单体项目自动注入 & url 为空
|
||||
String url = (String) attributes.get("url");
|
||||
boolean present = ClassUtils.isPresent("com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient",
|
||||
this.getClass().getClassLoader());
|
||||
if (!StringUtils.hasText(url) && !present) {
|
||||
url = resolve(BASE_URL);
|
||||
}
|
||||
|
||||
return FeignClientsRegistrar.getUrl(url);
|
||||
}
|
||||
|
||||
private String getPath(Map<String, Object> attributes) {
|
||||
String path = resolve((String) attributes.get("path"));
|
||||
return FeignClientsRegistrar.getPath(path);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getQualifier(@Nullable Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
String qualifier = (String) client.get("qualifier");
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
return qualifier;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getClientName(@Nullable Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
String value = (String) client.get("contextId");
|
||||
if (!StringUtils.hasText(value)) {
|
||||
value = (String) client.get("value");
|
||||
}
|
||||
if (!StringUtils.hasText(value)) {
|
||||
value = (String) client.get("name");
|
||||
}
|
||||
if (!StringUtils.hasText(value)) {
|
||||
value = (String) client.get("serviceId");
|
||||
}
|
||||
if (StringUtils.hasText(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
|
||||
}
|
||||
|
||||
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
|
||||
builder.addConstructorArgValue(name);
|
||||
builder.addConstructorArgValue(configuration);
|
||||
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
|
||||
builder.getBeanDefinition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.pig4cloud.pigx.common.feign.PigxFeignAutoConfiguration
|
||||
Reference in New Issue
Block a user