jdk17 commit
This commit is contained in:
52
lingniu-framework-plugin/cache/lingniu-framework-plugin-jetcache/pom.xml
vendored
Normal file
52
lingniu-framework-plugin/cache/lingniu-framework-plugin-jetcache/pom.xml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-jetcache</artifactId>
|
||||
<name>lingniu-framework-plugin-jetcache</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alicp.jetcache</groupId>
|
||||
<artifactId>jetcache-anno</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,95 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.AbstractCacheBuilder;
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.anno.KeyConvertor;
|
||||
import com.alicp.jetcache.anno.support.ParserFunction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created on 2016/11/29.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public abstract class AbstractCacheAutoInit implements InitializingBean {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(AbstractCacheAutoInit.class);
|
||||
|
||||
@Autowired
|
||||
protected ConfigurableEnvironment environment;
|
||||
|
||||
@Autowired
|
||||
protected AutoConfigureBeans autoConfigureBeans;
|
||||
|
||||
protected String[] typeNames;
|
||||
|
||||
private volatile boolean inited = false;
|
||||
|
||||
public AbstractCacheAutoInit(String... cacheTypes) {
|
||||
Objects.requireNonNull(cacheTypes, "cacheTypes can't be null");
|
||||
Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");
|
||||
this.typeNames = cacheTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (!inited) {
|
||||
synchronized (this) {
|
||||
if (!inited) {
|
||||
process("framework.lingniu.jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);
|
||||
process("framework.lingniu.jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void process(String prefix, Map cacheBuilders, boolean local) {
|
||||
ConfigTree resolver = new ConfigTree(environment, prefix);
|
||||
Map<String, Object> m = resolver.getProperties();
|
||||
Set<String> cacheAreaNames = resolver.directChildrenKeys();
|
||||
for (String cacheArea : cacheAreaNames) {
|
||||
final Object configType = m.get(cacheArea + ".type");
|
||||
boolean match = Arrays.stream(typeNames).anyMatch((tn) -> tn.equals(configType));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
ConfigTree ct = resolver.subTree(cacheArea + ".");
|
||||
logger.info("init cache area {} , type= {}", cacheArea, typeNames[0]);
|
||||
CacheBuilder c = initCache(ct, local ? "local." + cacheArea : "remote." + cacheArea);
|
||||
cacheBuilders.put(cacheArea, c);
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
|
||||
AbstractCacheBuilder acb = (AbstractCacheBuilder) builder;
|
||||
acb.keyConvertor(new ParserFunction(ct.getProperty("keyConvertor", KeyConvertor.FASTJSON2)));
|
||||
|
||||
String expireAfterWriteInMillis = ct.getProperty("expireAfterWriteInMillis");
|
||||
if (expireAfterWriteInMillis == null) {
|
||||
// compatible with 2.1
|
||||
expireAfterWriteInMillis = ct.getProperty("defaultExpireInMillis");
|
||||
}
|
||||
if (expireAfterWriteInMillis != null) {
|
||||
acb.setExpireAfterWriteInMillis(Long.parseLong(expireAfterWriteInMillis));
|
||||
}
|
||||
|
||||
String expireAfterAccessInMillis = ct.getProperty("expireAfterAccessInMillis");
|
||||
if (expireAfterAccessInMillis != null) {
|
||||
acb.setExpireAfterAccessInMillis(Long.parseLong(expireAfterAccessInMillis));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected abstract CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created on 2016/12/28.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public class AutoConfigureBeans {
|
||||
|
||||
private Map<String, CacheBuilder> localCacheBuilders = new HashMap<>();
|
||||
|
||||
private Map<String, CacheBuilder> remoteCacheBuilders = new HashMap<>();
|
||||
|
||||
private Map<String, Object> customContainer = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public Map<String, CacheBuilder> getLocalCacheBuilders() {
|
||||
return localCacheBuilders;
|
||||
}
|
||||
|
||||
public void setLocalCacheBuilders(Map<String, CacheBuilder> localCacheBuilders) {
|
||||
this.localCacheBuilders = localCacheBuilders;
|
||||
}
|
||||
|
||||
public Map<String, CacheBuilder> getRemoteCacheBuilders() {
|
||||
return remoteCacheBuilders;
|
||||
}
|
||||
|
||||
public void setRemoteCacheBuilders(Map<String, CacheBuilder> remoteCacheBuilders) {
|
||||
this.remoteCacheBuilders = remoteCacheBuilders;
|
||||
}
|
||||
|
||||
public Map<String, Object> getCustomContainer() {
|
||||
return customContainer;
|
||||
}
|
||||
|
||||
public void setCustomContainer(Map<String, Object> customContainer) {
|
||||
this.customContainer = customContainer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created on 2017/5/5.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public class BeanDependencyManager implements BeanFactoryPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
String[] autoInitBeanNames = beanFactory.getBeanNamesForType(AbstractCacheAutoInit.class, false, false);
|
||||
if (autoInitBeanNames != null) {
|
||||
BeanDefinition bd = beanFactory.getBeanDefinition(JetCacheAutoConfiguration.GLOBAL_CACHE_CONFIG_NAME);
|
||||
String[] dependsOn = bd.getDependsOn();
|
||||
if (dependsOn == null) {
|
||||
dependsOn = new String[0];
|
||||
}
|
||||
int oldLen = dependsOn.length;
|
||||
dependsOn = Arrays.copyOf(dependsOn, dependsOn.length + autoInitBeanNames.length);
|
||||
System.arraycopy(autoInitBeanNames, 0, dependsOn, oldLen, autoInitBeanNames.length);
|
||||
bd.setDependsOn(dependsOn);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.embedded.CaffeineCacheBuilder;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created on 2016/12/2.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
@Component
|
||||
@Conditional(CaffeineAutoConfiguration.CaffeineCondition.class)
|
||||
public class CaffeineAutoConfiguration extends EmbeddedCacheAutoInit {
|
||||
public CaffeineAutoConfiguration() {
|
||||
super("caffeine");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
|
||||
CaffeineCacheBuilder builder = CaffeineCacheBuilder.createCaffeineCacheBuilder();
|
||||
parseGeneralConfig(builder, ct);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static class CaffeineCondition extends JetCacheCondition {
|
||||
public CaffeineCondition() {
|
||||
super("caffeine");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created on 2017/11/20.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public class ConfigTree {
|
||||
private ConfigurableEnvironment environment;
|
||||
private String prefix;
|
||||
|
||||
public ConfigTree(ConfigurableEnvironment environment, String prefix) {
|
||||
Assert.notNull(environment, "environment is required");
|
||||
Assert.notNull(prefix, "prefix is required");
|
||||
this.environment = environment;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public ConfigTree subTree(String prefix) {
|
||||
return new ConfigTree(environment, fullPrefixOrKey(prefix));
|
||||
}
|
||||
|
||||
private String fullPrefixOrKey(String prefixOrKey) {
|
||||
return this.prefix + prefixOrKey;
|
||||
}
|
||||
|
||||
public Map<String, Object> getProperties() {
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
for (PropertySource<?> source : environment.getPropertySources()) {
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
for (String name : ((EnumerablePropertySource<?>) source)
|
||||
.getPropertyNames()) {
|
||||
if (name != null && name.startsWith(prefix)) {
|
||||
String subKey = name.substring(prefix.length());
|
||||
m.put(subKey, environment.getProperty(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
public boolean containsProperty(String key) {
|
||||
key = fullPrefixOrKey(key);
|
||||
return environment.containsProperty(key);
|
||||
}
|
||||
|
||||
public String getProperty(String key) {
|
||||
key = fullPrefixOrKey(key);
|
||||
return environment.getProperty(key);
|
||||
}
|
||||
|
||||
public String getProperty(String key, String defaultValue) {
|
||||
if (containsProperty(key)) {
|
||||
return getProperty(key);
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getProperty(String key, boolean defaultValue) {
|
||||
if (containsProperty(key)) {
|
||||
return Boolean.parseBoolean(getProperty(key));
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public int getProperty(String key, int defaultValue) {
|
||||
if (containsProperty(key)) {
|
||||
return Integer.parseInt(getProperty(key));
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public long getProperty(String key, long defaultValue) {
|
||||
if (containsProperty(key)) {
|
||||
return Long.parseLong(getProperty(key));
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public Set<String> directChildrenKeys() {
|
||||
Map<String, Object> m = getProperties();
|
||||
return m.keySet().stream().map(
|
||||
s -> s.indexOf('.') >= 0 ? s.substring(0, s.indexOf('.')) : null)
|
||||
.filter(s -> s != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.anno.CacheConsts;
|
||||
import com.alicp.jetcache.embedded.EmbeddedCacheBuilder;
|
||||
|
||||
/**
|
||||
* Created on 2016/12/2.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public abstract class EmbeddedCacheAutoInit extends AbstractCacheAutoInit {
|
||||
|
||||
public EmbeddedCacheAutoInit(String... cacheTypes) {
|
||||
super(cacheTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
|
||||
super.parseGeneralConfig(builder, ct);
|
||||
EmbeddedCacheBuilder ecb = (EmbeddedCacheBuilder) builder;
|
||||
|
||||
ecb.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.anno.CacheConsts;
|
||||
import com.alicp.jetcache.anno.support.ParserFunction;
|
||||
import com.alicp.jetcache.external.ExternalCacheBuilder;
|
||||
|
||||
/**
|
||||
* Created on 2016/11/29.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public abstract class ExternalCacheAutoInit extends AbstractCacheAutoInit {
|
||||
public ExternalCacheAutoInit(String... cacheTypes) {
|
||||
super(cacheTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
|
||||
super.parseGeneralConfig(builder, ct);
|
||||
ExternalCacheBuilder ecb = (ExternalCacheBuilder) builder;
|
||||
ecb.setKeyPrefix(ct.getProperty("keyPrefix"));
|
||||
ecb.setBroadcastChannel(parseBroadcastChannel(ct));
|
||||
ecb.setValueEncoder(new ParserFunction(ct.getProperty("valueEncoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
|
||||
ecb.setValueDecoder(new ParserFunction(ct.getProperty("valueDecoder", CacheConsts.DEFAULT_SERIAL_POLICY)));
|
||||
}
|
||||
|
||||
protected String parseBroadcastChannel(ConfigTree ct) {
|
||||
String broadcastChannel = ct.getProperty("broadcastChannel");
|
||||
if (broadcastChannel != null && !"".equals(broadcastChannel.trim())) {
|
||||
return broadcastChannel.trim();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheManager;
|
||||
import com.alicp.jetcache.SimpleCacheManager;
|
||||
import com.alicp.jetcache.anno.support.EncoderParser;
|
||||
import com.alicp.jetcache.anno.support.GlobalCacheConfig;
|
||||
import com.alicp.jetcache.anno.support.JetCacheBaseBeans;
|
||||
import com.alicp.jetcache.anno.support.KeyConvertorParser;
|
||||
import com.alicp.jetcache.anno.support.SpringConfigProvider;
|
||||
import com.alicp.jetcache.support.StatInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Created on 2016/11/17.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(GlobalCacheConfig.class)
|
||||
@ConditionalOnMissingBean(GlobalCacheConfig.class)
|
||||
@EnableConfigurationProperties(JetCacheProperties.class)
|
||||
@Import({CaffeineAutoConfiguration.class,
|
||||
MockRemoteCacheAutoConfiguration.class,
|
||||
LinkedHashMapAutoConfiguration.class,
|
||||
RedissonAutoConfiguration.class})
|
||||
public class JetCacheAutoConfiguration {
|
||||
|
||||
public static final String GLOBAL_CACHE_CONFIG_NAME = "globalCacheConfig";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SpringConfigProvider springConfigProvider(
|
||||
@Autowired ApplicationContext applicationContext,
|
||||
@Autowired GlobalCacheConfig globalCacheConfig,
|
||||
@Autowired(required = false) EncoderParser encoderParser,
|
||||
@Autowired(required = false) KeyConvertorParser keyConvertorParser,
|
||||
@Autowired(required = false) Consumer<StatInfo> metricsCallback) {
|
||||
return new JetCacheBaseBeans().springConfigProvider(applicationContext, globalCacheConfig,
|
||||
encoderParser, keyConvertorParser, metricsCallback);
|
||||
}
|
||||
|
||||
@Bean(name = "jcCacheManager")
|
||||
@ConditionalOnMissingBean
|
||||
public CacheManager cacheManager(@Autowired SpringConfigProvider springConfigProvider) {
|
||||
SimpleCacheManager cacheManager = new SimpleCacheManager();
|
||||
cacheManager.setCacheBuilderTemplate(springConfigProvider.getCacheBuilderTemplate());
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AutoConfigureBeans autoConfigureBeans() {
|
||||
return new AutoConfigureBeans();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static BeanDependencyManager beanDependencyManager() {
|
||||
return new BeanDependencyManager();
|
||||
}
|
||||
|
||||
@Bean(name = GLOBAL_CACHE_CONFIG_NAME)
|
||||
public GlobalCacheConfig globalCacheConfig(AutoConfigureBeans autoConfigureBeans, JetCacheProperties props) {
|
||||
GlobalCacheConfig _globalCacheConfig = new GlobalCacheConfig();
|
||||
_globalCacheConfig = new GlobalCacheConfig();
|
||||
_globalCacheConfig.setHiddenPackages(props.getHiddenPackages());
|
||||
_globalCacheConfig.setStatIntervalMinutes(props.getStatIntervalMinutes());
|
||||
_globalCacheConfig.setAreaInCacheName(props.isAreaInCacheName());
|
||||
_globalCacheConfig.setPenetrationProtect(props.isPenetrationProtect());
|
||||
_globalCacheConfig.setEnableMethodCache(props.isEnableMethodCache());
|
||||
_globalCacheConfig.setLocalCacheBuilders(autoConfigureBeans.getLocalCacheBuilders());
|
||||
_globalCacheConfig.setRemoteCacheBuilders(autoConfigureBeans.getRemoteCacheBuilders());
|
||||
return _globalCacheConfig;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created on 2016/11/28.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
public abstract class JetCacheCondition extends SpringBootCondition {
|
||||
|
||||
private String[] cacheTypes;
|
||||
|
||||
protected JetCacheCondition(String... cacheTypes) {
|
||||
Objects.requireNonNull(cacheTypes, "cacheTypes can't be null");
|
||||
Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");
|
||||
this.cacheTypes = cacheTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
|
||||
ConfigTree ct = new ConfigTree((ConfigurableEnvironment) conditionContext.getEnvironment(), "framework.lingniu.jetcache.");
|
||||
if (match(ct, "local.") || match(ct, "remote.")) {
|
||||
return ConditionOutcome.match();
|
||||
} else {
|
||||
return ConditionOutcome.noMatch("no match for " + cacheTypes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean match(ConfigTree ct, String prefix) {
|
||||
Map<String, Object> m = ct.subTree(prefix).getProperties();
|
||||
Set<String> cacheAreaNames = m.keySet().stream().map((s) -> s.substring(0, s.indexOf('.'))).collect(Collectors.toSet());
|
||||
final List<String> cacheTypesList = Arrays.asList(cacheTypes);
|
||||
return cacheAreaNames.stream().anyMatch((s) -> cacheTypesList.contains(m.get(s + ".type")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Created on 2016/11/23.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "jetcache")
|
||||
public class JetCacheProperties {
|
||||
|
||||
private String[] hiddenPackages;
|
||||
private int statIntervalMinutes;
|
||||
private boolean areaInCacheName = true;
|
||||
private boolean penetrationProtect = false;
|
||||
private boolean enableMethodCache = true;
|
||||
|
||||
public JetCacheProperties() {
|
||||
}
|
||||
|
||||
public String[] getHiddenPackages() {
|
||||
// keep same with GlobalCacheConfig
|
||||
return hiddenPackages;
|
||||
}
|
||||
|
||||
public void setHiddenPackages(String[] hiddenPackages) {
|
||||
// keep same with GlobalCacheConfig
|
||||
this.hiddenPackages = hiddenPackages;
|
||||
}
|
||||
|
||||
public void setHidePackages(String[] hidePackages) {
|
||||
// keep same with GlobalCacheConfig
|
||||
this.hiddenPackages = hidePackages;
|
||||
}
|
||||
|
||||
public int getStatIntervalMinutes() {
|
||||
return statIntervalMinutes;
|
||||
}
|
||||
|
||||
public void setStatIntervalMinutes(int statIntervalMinutes) {
|
||||
this.statIntervalMinutes = statIntervalMinutes;
|
||||
}
|
||||
|
||||
public boolean isAreaInCacheName() {
|
||||
return areaInCacheName;
|
||||
}
|
||||
|
||||
public void setAreaInCacheName(boolean areaInCacheName) {
|
||||
this.areaInCacheName = areaInCacheName;
|
||||
}
|
||||
|
||||
public boolean isPenetrationProtect() {
|
||||
return penetrationProtect;
|
||||
}
|
||||
|
||||
public void setPenetrationProtect(boolean penetrationProtect) {
|
||||
this.penetrationProtect = penetrationProtect;
|
||||
}
|
||||
|
||||
public boolean isEnableMethodCache() {
|
||||
return enableMethodCache;
|
||||
}
|
||||
|
||||
public void setEnableMethodCache(boolean enableMethodCache) {
|
||||
this.enableMethodCache = enableMethodCache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.embedded.LinkedHashMapCacheBuilder;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created on 2016/12/2.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
@Component
|
||||
@Conditional(LinkedHashMapAutoConfiguration.LinkedHashMapCondition.class)
|
||||
public class LinkedHashMapAutoConfiguration extends EmbeddedCacheAutoInit {
|
||||
public LinkedHashMapAutoConfiguration() {
|
||||
super("linkedhashmap");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
|
||||
LinkedHashMapCacheBuilder builder = LinkedHashMapCacheBuilder.createLinkedHashMapCacheBuilder();
|
||||
parseGeneralConfig(builder, ct);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static class LinkedHashMapCondition extends JetCacheCondition {
|
||||
public LinkedHashMapCondition() {
|
||||
super("linkedhashmap");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.anno.CacheConsts;
|
||||
import com.alicp.jetcache.external.MockRemoteCacheBuilder;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Created on 2016/12/2.
|
||||
*
|
||||
* @author <a href="mailto:areyouok@gmail.com">huangli</a>
|
||||
*/
|
||||
@Component
|
||||
@Conditional(MockRemoteCacheAutoConfiguration.MockRemoteCacheCondition.class)
|
||||
public class MockRemoteCacheAutoConfiguration extends ExternalCacheAutoInit {
|
||||
public MockRemoteCacheAutoConfiguration() {
|
||||
super("mock");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix) {
|
||||
MockRemoteCacheBuilder builder = MockRemoteCacheBuilder.createMockRemoteCacheBuilder();
|
||||
parseGeneralConfig(builder, ct);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
|
||||
super.parseGeneralConfig(builder, ct);
|
||||
MockRemoteCacheBuilder b = (MockRemoteCacheBuilder) builder;
|
||||
b.limit(Integer.parseInt(ct.getProperty("limit", String.valueOf(CacheConsts.DEFAULT_LOCAL_LIMIT))));
|
||||
}
|
||||
|
||||
public static class MockRemoteCacheCondition extends JetCacheCondition {
|
||||
public MockRemoteCacheCondition() {
|
||||
super("mock");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.autoconfigure;
|
||||
|
||||
import cn.lingniu.framework.plugin.jetcache.redisson.RedissonCacheBuilder;
|
||||
import com.alicp.jetcache.CacheBuilder;
|
||||
import com.alicp.jetcache.CacheConfigException;
|
||||
import com.alicp.jetcache.external.ExternalCacheBuilder;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Created on 2022/7/12.
|
||||
*
|
||||
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
|
||||
*/
|
||||
@Configuration
|
||||
@Conditional(RedissonAutoConfiguration.RedissonCondition.class)
|
||||
public class RedissonAutoConfiguration {
|
||||
private static final String CACHE_TYPE = "redisson";
|
||||
|
||||
public static class RedissonCondition extends JetCacheCondition {
|
||||
public RedissonCondition() {
|
||||
super(CACHE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedissonAutoInit redissonAutoInit() {
|
||||
return new RedissonAutoInit();
|
||||
}
|
||||
|
||||
public static class RedissonAutoInit extends ExternalCacheAutoInit implements ApplicationContextAware {
|
||||
private ApplicationContext context;
|
||||
|
||||
public RedissonAutoInit() {
|
||||
super(CACHE_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheBuilder initCache(final ConfigTree ct, final String cacheAreaWithPrefix) {
|
||||
final Map<String, RedissonClient> beans = this.context.getBeansOfType(RedissonClient.class);
|
||||
if (beans.isEmpty()) {
|
||||
throw new CacheConfigException("no RedissonClient in spring context");
|
||||
}
|
||||
RedissonClient client = beans.values().iterator().next();
|
||||
if (beans.size() > 1) {
|
||||
final String redissonClientName = ct.getProperty("redissonClient");
|
||||
if (Objects.isNull(redissonClientName) || redissonClientName.isEmpty()) {
|
||||
throw new CacheConfigException("redissonClient is required, because there is multiple RedissonClient in Spring context");
|
||||
}
|
||||
if (!beans.containsKey(redissonClientName)) {
|
||||
throw new CacheConfigException("there is no RedissonClient named " + redissonClientName + " in Spring context");
|
||||
}
|
||||
client = beans.get(redissonClientName);
|
||||
}
|
||||
final ExternalCacheBuilder<?> builder = RedissonCacheBuilder.createBuilder().redissonClient(client);
|
||||
parseGeneralConfig(builder, ct);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(final ApplicationContext context) throws BeansException {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.redisson;
|
||||
|
||||
import com.alicp.jetcache.CacheManager;
|
||||
import com.alicp.jetcache.CacheResult;
|
||||
import com.alicp.jetcache.support.BroadcastManager;
|
||||
import com.alicp.jetcache.support.CacheMessage;
|
||||
import com.alicp.jetcache.support.SquashedLogger;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Created on 2022/7/12.
|
||||
*
|
||||
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
|
||||
*/
|
||||
public class RedissonBroadcastManager extends BroadcastManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedissonBroadcastManager.class);
|
||||
private final RedissonCacheConfig<?, ?> config;
|
||||
private final String channel;
|
||||
private final RedissonClient client;
|
||||
private volatile int subscribeId;
|
||||
|
||||
public RedissonBroadcastManager(final CacheManager cacheManager, final RedissonCacheConfig<?, ?> config) {
|
||||
super(cacheManager);
|
||||
checkConfig(config);
|
||||
this.config = config;
|
||||
this.channel = config.getBroadcastChannel();
|
||||
this.client = config.getRedissonClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void startSubscribe() {
|
||||
if (this.subscribeId == 0 && Objects.nonNull(this.channel) && !this.channel.isEmpty()) {
|
||||
this.subscribeId = this.client.getTopic(this.channel)
|
||||
.addListener(byte[].class, (channel, msg) -> processNotification(msg, this.config.getValueDecoder()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
final int id;
|
||||
if ((id = this.subscribeId) > 0 && Objects.nonNull(this.channel)) {
|
||||
this.subscribeId = 0;
|
||||
try {
|
||||
this.client.getTopic(this.channel).removeListener(id);
|
||||
} catch (Throwable e) {
|
||||
logger.warn("unsubscribe {} fail", this.channel, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheResult publish(final CacheMessage cacheMessage) {
|
||||
try {
|
||||
if (Objects.nonNull(this.channel) && Objects.nonNull(cacheMessage)) {
|
||||
final byte[] msg = this.config.getValueEncoder().apply(cacheMessage);
|
||||
this.client.getTopic(this.channel).publish(msg);
|
||||
return CacheResult.SUCCESS_WITHOUT_MSG;
|
||||
}
|
||||
return CacheResult.FAIL_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
SquashedLogger.getLogger(logger).error("jetcache publish error", e);
|
||||
return new CacheResult(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.redisson;
|
||||
|
||||
import com.alicp.jetcache.CacheConfig;
|
||||
import com.alicp.jetcache.CacheGetResult;
|
||||
import com.alicp.jetcache.CacheResult;
|
||||
import com.alicp.jetcache.CacheResultCode;
|
||||
import com.alicp.jetcache.CacheValueHolder;
|
||||
import com.alicp.jetcache.MultiGetResult;
|
||||
import com.alicp.jetcache.external.AbstractExternalCache;
|
||||
import org.redisson.api.RBatch;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Created on 2022/7/12.
|
||||
*
|
||||
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
|
||||
*/
|
||||
public class RedissonCache<K, V> extends AbstractExternalCache<K, V> {
|
||||
private final RedissonClient client;
|
||||
private final RedissonCacheConfig<K, V> config;
|
||||
|
||||
public RedissonCache(final RedissonCacheConfig<K, V> config) {
|
||||
super(config);
|
||||
this.config = config;
|
||||
this.client = config.getRedissonClient();
|
||||
}
|
||||
|
||||
protected String getCacheKey(final K key) {
|
||||
final byte[] newKey = buildKey(key);
|
||||
return new String(newKey, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheConfig<K, V> config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(final Class<T> clazz) {
|
||||
throw new UnsupportedOperationException("RedissonCache does not support unwrap");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected CacheGetResult<V> do_GET(final K key) {
|
||||
try {
|
||||
final RBucket<CacheValueHolder<V>> rb = this.client.getBucket(getCacheKey(key));
|
||||
final CacheValueHolder<V> holder = rb.get();
|
||||
if (Objects.nonNull(holder)) {
|
||||
final long now = System.currentTimeMillis(), expire = holder.getExpireTime();
|
||||
if (expire > 0 && now >= expire) {
|
||||
return CacheGetResult.EXPIRED_WITHOUT_MSG;
|
||||
}
|
||||
return new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);
|
||||
}
|
||||
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
logError("GET", key, e);
|
||||
return new CacheGetResult<>(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected MultiGetResult<K, V> do_GET_ALL(final Set<? extends K> keys) {
|
||||
try {
|
||||
final Map<K, CacheGetResult<V>> retMap = new HashMap<>(1 << 4);
|
||||
if (Objects.nonNull(keys) && !keys.isEmpty()) {
|
||||
final Map<K, String> keyMap = new HashMap<>(keys.size());
|
||||
for (K k : keys) {
|
||||
if (Objects.nonNull(k)) {
|
||||
final String key = getCacheKey(k);
|
||||
if (Objects.nonNull(key)) {
|
||||
keyMap.put(k, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!keyMap.isEmpty()) {
|
||||
final Map<String, Object> kvMap = this.client.getBuckets().get(keyMap.values().toArray(new String[0]));
|
||||
final long now = System.currentTimeMillis();
|
||||
for (K k : keys) {
|
||||
final String key = keyMap.get(k);
|
||||
if (Objects.nonNull(key) && Objects.nonNull(kvMap)) {
|
||||
final CacheValueHolder<V> holder = (CacheValueHolder<V>) kvMap.get(key);
|
||||
if (Objects.nonNull(holder)) {
|
||||
final long expire = holder.getExpireTime();
|
||||
final CacheGetResult<V> ret = (expire > 0 && now >= expire) ? CacheGetResult.EXPIRED_WITHOUT_MSG :
|
||||
new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);
|
||||
retMap.put(k, ret);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
retMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new MultiGetResult<>(CacheResultCode.SUCCESS, null, retMap);
|
||||
} catch (Throwable e) {
|
||||
logError("GET_ALL", "keys(" + (Objects.nonNull(keys) ? keys.size() : 0) + ")", e);
|
||||
return new MultiGetResult<>(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheResult do_PUT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {
|
||||
try {
|
||||
final CacheValueHolder<V> holder = new CacheValueHolder<>(value, timeUnit.toMillis(expireAfterWrite));
|
||||
this.client.getBucket(getCacheKey(key)).set(holder, expireAfterWrite, timeUnit);
|
||||
return CacheGetResult.SUCCESS_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
logError("PUT", key, e);
|
||||
return new CacheResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheResult do_PUT_ALL(final Map<? extends K, ? extends V> map, final long expireAfterWrite, final TimeUnit timeUnit) {
|
||||
try {
|
||||
if (Objects.nonNull(map) && !map.isEmpty()) {
|
||||
final long expire = timeUnit.toMillis(expireAfterWrite);
|
||||
final RBatch batch = this.client.createBatch();
|
||||
map.forEach((k, v) -> {
|
||||
final CacheValueHolder<V> holder = new CacheValueHolder<>(v, expire);
|
||||
batch.getBucket(getCacheKey(k)).setAsync(holder, expireAfterWrite, timeUnit);
|
||||
});
|
||||
batch.execute();
|
||||
}
|
||||
return CacheResult.SUCCESS_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
logError("PUT_ALL", "map(" + map.size() + ")", e);
|
||||
return new CacheResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheResult do_REMOVE(final K key) {
|
||||
try {
|
||||
final boolean ret = this.client.getBucket(getCacheKey(key)).delete();
|
||||
return ret ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.FAIL_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
logError("REMOVE", key, e);
|
||||
return new CacheResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheResult do_REMOVE_ALL(final Set<? extends K> keys) {
|
||||
try {
|
||||
if (Objects.nonNull(keys) && !keys.isEmpty()) {
|
||||
final RBatch batch = this.client.createBatch();
|
||||
keys.forEach(key -> batch.getBucket(getCacheKey(key)).deleteAsync());
|
||||
batch.execute();
|
||||
}
|
||||
return CacheResult.SUCCESS_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
logError("REMOVE_ALL", "keys(" + keys.size() + ")", e);
|
||||
return new CacheResult(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheResult do_PUT_IF_ABSENT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {
|
||||
try {
|
||||
final CacheValueHolder<V> holder = new CacheValueHolder<>(value, timeUnit.toMillis(expireAfterWrite));
|
||||
final boolean success = this.client.getBucket(getCacheKey(key)).trySet(holder, expireAfterWrite, timeUnit);
|
||||
return success ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.EXISTS_WITHOUT_MSG;
|
||||
} catch (Throwable e) {
|
||||
logError("PUT_IF_ABSENT", key, e);
|
||||
return new CacheResult(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.redisson;
|
||||
|
||||
import com.alicp.jetcache.CacheManager;
|
||||
import com.alicp.jetcache.external.ExternalCacheBuilder;
|
||||
import com.alicp.jetcache.support.BroadcastManager;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
* Created on 2022/7/12.
|
||||
*
|
||||
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
|
||||
*/
|
||||
public class RedissonCacheBuilder<T extends ExternalCacheBuilder<T>> extends ExternalCacheBuilder<T> {
|
||||
|
||||
public static class RedissonDataCacheBuilderImpl extends RedissonCacheBuilder<RedissonDataCacheBuilderImpl> {
|
||||
|
||||
}
|
||||
|
||||
public static RedissonDataCacheBuilderImpl createBuilder() {
|
||||
return new RedissonDataCacheBuilderImpl();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"all"})
|
||||
protected RedissonCacheBuilder() {
|
||||
buildFunc(config -> new RedissonCache((RedissonCacheConfig) config));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"all"})
|
||||
public RedissonCacheConfig getConfig() {
|
||||
if (this.config == null) {
|
||||
this.config = new RedissonCacheConfig();
|
||||
}
|
||||
return (RedissonCacheConfig) this.config;
|
||||
}
|
||||
|
||||
public T redissonClient(final RedissonClient client) {
|
||||
this.getConfig().setRedissonClient(client);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportBroadcast() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastManager createBroadcastManager(final CacheManager cacheManager) {
|
||||
final RedissonCacheConfig<?, ?> c = (RedissonCacheConfig<?, ?>) this.getConfig().clone();
|
||||
return new RedissonBroadcastManager(cacheManager, c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.lingniu.framework.plugin.jetcache.redisson;
|
||||
|
||||
import com.alicp.jetcache.external.ExternalCacheConfig;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
* Created on 2022/7/12.
|
||||
*
|
||||
* @author <a href="mailto:jeason1914@qq.com">yangyong</a>
|
||||
*/
|
||||
public class RedissonCacheConfig<K, V> extends ExternalCacheConfig<K, V> {
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
public RedissonClient getRedissonClient() {
|
||||
return redissonClient;
|
||||
}
|
||||
|
||||
public void setRedissonClient(final RedissonClient redissonClient) {
|
||||
this.redissonClient = redissonClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.jetcache.autoconfigure.JetCacheAutoConfiguration
|
||||
@@ -0,0 +1,122 @@
|
||||
# 【重要】jetcache详细资料---参考官网
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 基于 JetCache 封装的企业级二级缓存解决方案,提供本地 JVM 本地缓存和远程 Redis 缓存的组合缓存能力
|
||||
2. 核心能力
|
||||
* 二级缓存机制:
|
||||
|
||||
- 一级缓存:本地 JVM 缓存(基于 Caffeine等)
|
||||
- 二级缓存:远程 Redis 缓存(redisson 模式)
|
||||
|
||||
* 多样化缓存配置:
|
||||
- 本地缓存:支持 Caffeine 类型,可配置缓存数量限制和过期时间
|
||||
- 远程缓存:支持 redisson 类型
|
||||
- 多实例支持:可配置多个缓存分组(如 default、employee 等)
|
||||
* 注解式缓存操作:
|
||||
- @Cached:标记方法结果需要缓存,支持缓存名称、过期时间等配置
|
||||
- @CacheRefresh:支持缓存自动刷新机制
|
||||
- @EnableMethodCache:启用 JetCache Bean 扫描
|
||||
* 灵活的缓存策略:
|
||||
- 支持不同的 Key 生成策略
|
||||
- 可配置缓存过期时间和时间单位
|
||||
- 支持不同的缓存类型(本地、远程、双向)
|
||||
|
||||
3. 适用场景:该组件特别适用于需要高性能缓存访问的企业级应用,通过本地+远程的二级缓存架构,在保证缓存访问速度的同时,确保缓存数据的共享和一致性。通过注解方式简化了缓存的使用。
|
||||
* 高并发读场景:需要减少数据库访问压力,提升系统响应速度的业务场景
|
||||
* 高可用访问:需要自动感知 Redis 集群变化以及故障自动切换能力的应用场景
|
||||
* 复杂查询结果缓存:对于计算复杂或关联查询较多的数据结果进行缓存
|
||||
* 数据一致性要求较高:需要通过二级缓存机制保证数据访问性能和一致性的场景
|
||||
* 需要缓存自动刷新:对于有一定时效性要求但不需要实时更新的数据
|
||||
|
||||
## 如何配置--更多参数参考:更多配置请参考官网jetcache
|
||||
|
||||
* 此处为了统一中间件配置前缀framework.lingniu.jetcache 和更好的个性化扩展, 其他jetcache所有配置方式未改变
|
||||
* 请先依赖:lingniu-framework-plugin-redisson
|
||||
redissonClient 就是 RedissonConfig配置的key名称
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
lingniu:
|
||||
jetcache:
|
||||
# 需要隐藏的包路径(数组),默认为空
|
||||
hiddenPackages: [ ]
|
||||
# 统计信息输出间隔(分钟),默认为 0(不输出)
|
||||
statIntervalMinutes: 10
|
||||
# 是否在缓存名称中包含区域(area)信息,默认为 true
|
||||
areaInCacheName: true
|
||||
# 是否开启缓存穿透保护,默认为 false
|
||||
penetrationProtect: false
|
||||
# 本地缓存配置
|
||||
local:
|
||||
# cache area 配置, 使用CacheManager或@Cache注解时 默认area为default
|
||||
ca2:
|
||||
# 本地缓存实现类类型,支持:linkedhashmap/caffeine
|
||||
type: linkedhashmap
|
||||
# 缓存key数量限制, 默认100
|
||||
limit: 100
|
||||
default:
|
||||
# 本地缓存实现类类型,支持:linkedhashmap/caffeine
|
||||
type: caffeine
|
||||
# 缓存key数量限制, 默认100
|
||||
limit: 100
|
||||
# 过期时间,0为不失效
|
||||
expireAfterAccessInMillis: 0
|
||||
# key 转换器,用于序列化,目前支持:NONE、FASTJSON、JACKSON、FASTJSON2
|
||||
keyConvertor: FASTJSON2
|
||||
remote:
|
||||
# cache area 配置, 使用CacheManager或@Cache注解时 默认area为default
|
||||
ca2:
|
||||
# 远程缓存实现类类型,目前支持:redisson
|
||||
type: redisson
|
||||
# 远程缓存客户端名称,默认r1 -- todo 需要在application-redisson.yml中配置
|
||||
redissonClient: r1
|
||||
# key 前缀
|
||||
keyPrefix: "cacheKeyPrefix:"
|
||||
# 连接超时时间ms
|
||||
timeout: 2000
|
||||
# 重试次数
|
||||
maxAttempt: 5
|
||||
# 连接池配置
|
||||
poolConfig:
|
||||
maxTotal: 8
|
||||
maxIdle: 8
|
||||
minIdle: 0
|
||||
# key 转换器,用于序列化,目前支持:NONE、FASTJSON、JACKSON、FASTJSON2
|
||||
keyConvertor: FASTJSON2
|
||||
# JAVA, KRYO,KRYO5,自定义spring beanName
|
||||
valueEncoder: JAVA
|
||||
# JAVA, KRYO,KRYO5,自定义spring beanName
|
||||
valueDecoder: JAVA
|
||||
# cache area 配置, 使用CacheManager或@Cache注解时 默认area为default
|
||||
default:
|
||||
# 远程缓存实现类类型,目前支持:redisson
|
||||
type: redisson
|
||||
# 远程缓存客户端名称,默认r1 -- todo 需要在application-redisson.yml中配置
|
||||
redissonClient: r1
|
||||
|
||||
```
|
||||
|
||||
## 如何使用-
|
||||
|
||||
```java
|
||||
//启动 扫描类添加注解
|
||||
@EnableMethodCache(basePackages = "cn.lingniu.*")
|
||||
@SpringBootApplication
|
||||
public class CacheApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CacheApplication.class, args);
|
||||
}
|
||||
}
|
||||
// 多种使用方式参考
|
||||
// 1. 注解方式
|
||||
@CachePenetrationProtect
|
||||
@Cached(name = "orderCache:", key = "#orderId", expire = 3600, cacheType = CacheType.BOTH)
|
||||
@CacheRefresh(refresh = 10, timeUnit = TimeUnit.MINUTES)
|
||||
public DataResult<KeyValueEntity> getOrderById(String orderId) {
|
||||
...
|
||||
}
|
||||
// 2. SimpleCacheManager方式
|
||||
Cache<Object, Object> localCache = simpleCacheManager.getOrCreateCache(QuickConfig.newBuilder(localArea, "user:").cacheType(CacheType.LOCAL).build());
|
||||
```
|
||||
|
||||
34
lingniu-framework-plugin/cache/lingniu-framework-plugin-redisson/pom.xml
vendored
Normal file
34
lingniu-framework-plugin/cache/lingniu-framework-plugin-redisson/pom.xml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-redisson</artifactId>
|
||||
<name>lingniu-framework-plugin-redisson</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class RedissonClientFactory {
|
||||
|
||||
private static volatile Map<String, RedissonClient> redisMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static void putRedissonClient(String beanName, RedissonClient redissonClient) {
|
||||
redisMap.put(beanName, redissonClient);
|
||||
}
|
||||
|
||||
public static RedissonClient getRedissonClient(String beanName) {
|
||||
if (ObjectEmptyUtils.isEmpty(beanName) || !redisMap.containsKey(beanName)) {
|
||||
throw new IllegalStateException("(redisson)bean-对应的RedissonClient未注册:" + beanName);
|
||||
}
|
||||
return redisMap.get(beanName);
|
||||
}
|
||||
|
||||
public static Map<String, RedissonClient> getRedisMap() {
|
||||
return redisMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 锁
|
||||
**/
|
||||
@SuppressWarnings("all")
|
||||
public interface RedissonClusterLockerService {
|
||||
|
||||
default public <T, R> R wrapLock(T source, Function<T, String> fun,
|
||||
Function<T, String> keyGetter, Function<T, R> wrapFun) {
|
||||
return wrapLock(source, fun, keyGetter, wrapFun, 15L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
<T, R> R wrapLock(T source, Function<T, String> fun,
|
||||
Function<T, String> keyGetter, Function<T, R> wrapFun,
|
||||
Long leaseTime, TimeUnit leaseTimeUnit);
|
||||
|
||||
default public <T, R> R wrapLock(T source, Function<T, String> keyGetter, Function<T, R> wrapFun) {
|
||||
return wrapLock(source, (t) -> {
|
||||
String key = keyGetter.apply(source);
|
||||
return delivery(key);
|
||||
}, keyGetter, wrapFun);
|
||||
}
|
||||
|
||||
String delivery(String key);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 支持注解获取锁
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RedissonLockAction {
|
||||
|
||||
/**
|
||||
* RedissonConfig 中 remote 的 key 名字
|
||||
*/
|
||||
String redissonName();
|
||||
/**
|
||||
* 锁的资源,key。支持spring El表达式
|
||||
*/
|
||||
@AliasFor("key")
|
||||
String value() default "'default'";
|
||||
@AliasFor("value")
|
||||
String key() default "'default'";
|
||||
/**
|
||||
* 锁类型
|
||||
*/
|
||||
RedissonLockType lockType() default RedissonLockType.REENTRANT_LOCK;
|
||||
/**
|
||||
* 获取锁等待时间,默认3秒
|
||||
*/
|
||||
long waitTime() default 3000L;
|
||||
/**
|
||||
* 锁自动释放时间,默认10秒
|
||||
*/
|
||||
long leaseTime() default 10000L;
|
||||
/**
|
||||
* 时间单位
|
||||
*/
|
||||
TimeUnit unit() default TimeUnit.MILLISECONDS;
|
||||
/**
|
||||
* 锁失败是否Throw异常
|
||||
*/
|
||||
boolean throwException() default false;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.lingniu.framework.plugin.redisson;
|
||||
|
||||
/**
|
||||
* 锁类型
|
||||
*/
|
||||
public enum RedissonLockType {
|
||||
/**
|
||||
* 读锁
|
||||
*/
|
||||
READ_LOCK,
|
||||
/**
|
||||
* 写锁
|
||||
*/
|
||||
WRITE_LOCK,
|
||||
/**
|
||||
* 可重入锁
|
||||
*/
|
||||
REENTRANT_LOCK,
|
||||
/**
|
||||
* 公平锁
|
||||
*/
|
||||
FAIR_LOCK;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.lingniu.framework.plugin.redisson.builder;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.config.Config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public abstract class RedissonAbstractClientBuilder {
|
||||
|
||||
@Getter(AccessLevel.PUBLIC)
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
protected RedissonAbstractClientBuilder(RedissonProperties redissonProperties) {
|
||||
this.redissonProperties = redissonProperties;
|
||||
}
|
||||
|
||||
protected abstract void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts);
|
||||
|
||||
public Config build() {
|
||||
Config redissionConfig = new Config();
|
||||
Set<RedisHostAndPort> redisHostAndPorts = parseRedisHostAndPort(redissonProperties.getRedisAddresses());
|
||||
config(redissionConfig, redisHostAndPorts);
|
||||
return redissionConfig;
|
||||
}
|
||||
|
||||
public Set<RedisHostAndPort> parseRedisHostAndPort(String redisAddress) {
|
||||
Set<RedisHostAndPort> redisHostAndPorts = new HashSet<>();
|
||||
// 分离地址部分和密码部分
|
||||
String[] parts = redisAddress.split(";");
|
||||
String addressPart = parts[0];
|
||||
String password = parts.length > 1 ? parts[1] : null;
|
||||
// 解析host:port格式的地址
|
||||
String[] hosts = addressPart.split(",");
|
||||
for (String hostPort : hosts) {
|
||||
hostPort = hostPort.trim();
|
||||
if (hostPort.contains(":")) {
|
||||
int colonIndex = hostPort.indexOf(":");
|
||||
String host = hostPort.substring(0, colonIndex);
|
||||
int port = Integer.parseInt(hostPort.substring(colonIndex + 1));
|
||||
RedisHostAndPort redisHostAndPort = new RedisHostAndPort();
|
||||
redisHostAndPort.setHost(host);
|
||||
redisHostAndPort.setPort(port);
|
||||
if (password != null) {
|
||||
redisHostAndPort.setPkey(password);
|
||||
}
|
||||
redisHostAndPorts.add(redisHostAndPort);
|
||||
}
|
||||
}
|
||||
return redisHostAndPorts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lingniu.framework.plugin.redisson.builder;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import cn.lingniu.framework.plugin.util.ip.IpUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.config.ClusterServersConfig;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.ReadMode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @description: 集群类型
|
||||
**/
|
||||
public class RedissonClusterClientBuilder extends RedissonAbstractClientBuilder {
|
||||
|
||||
public RedissonClusterClientBuilder(RedissonProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts) {
|
||||
ClusterServersConfig clusterServersConfig = redssionConfig.useClusterServers();
|
||||
if (StringUtils.isEmpty(getRedissonProperties().getClientName())) {
|
||||
clusterServersConfig.setClientName(ApplicationNameContext.getApplicationName() + ":" + IpUtil.getIp());
|
||||
}
|
||||
clusterServersConfig.setMasterConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
|
||||
clusterServersConfig.setSlaveConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
|
||||
clusterServersConfig.setMasterConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
|
||||
clusterServersConfig.setSlaveConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
|
||||
clusterServersConfig.setIdleConnectionTimeout(getRedissonProperties().getIdleConnectionTimeout());
|
||||
clusterServersConfig.setConnectTimeout(getRedissonProperties().getConnectTimeout());
|
||||
clusterServersConfig.setTimeout(getRedissonProperties().getTimeout());
|
||||
clusterServersConfig.setRetryAttempts(getRedissonProperties().getRetryAttempts());
|
||||
RedisHostAndPort redisHostAndPort = redisHostAndPorts.stream().findAny().get();
|
||||
clusterServersConfig.setPassword(redisHostAndPort.getPkey());
|
||||
clusterServersConfig.setCheckSlotsCoverage(getRedissonProperties().getCheckSlotsCoverage());
|
||||
clusterServersConfig.setReadMode(ReadMode.valueOf(getRedissonProperties().getReadMode()));
|
||||
redisHostAndPorts.stream().forEach(h -> {
|
||||
clusterServersConfig.addNodeAddress(String.format("redis://%s/", h.getHost() + ":" + h.getPort()));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.lingniu.framework.plugin.redisson.builder;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisHostAndPort;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import cn.lingniu.framework.plugin.util.ip.IpUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.redisson.config.Config;
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* @description: 单节点类型
|
||||
**/
|
||||
public class RedissonStandaloneClientBuilder extends RedissonAbstractClientBuilder {
|
||||
|
||||
|
||||
public RedissonStandaloneClientBuilder(RedissonProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void config(Config redssionConfig, Set<RedisHostAndPort> redisHostAndPorts) {
|
||||
SingleServerConfig singleServerConfig = redssionConfig.useSingleServer();
|
||||
if (StringUtils.isEmpty(getRedissonProperties().getClientName())) {
|
||||
singleServerConfig.setClientName(ApplicationNameContext.getApplicationName() + ":" + IpUtil.getIp());
|
||||
}
|
||||
singleServerConfig.setDatabase(getRedissonProperties().getDatabase());
|
||||
singleServerConfig.setConnectionMinimumIdleSize(getRedissonProperties().getConnectionMinimumIdleSize());
|
||||
singleServerConfig.setConnectionPoolSize(getRedissonProperties().getConnectionPoolSize());
|
||||
singleServerConfig.setIdleConnectionTimeout(getRedissonProperties().getIdleConnectionTimeout());
|
||||
singleServerConfig.setConnectTimeout(getRedissonProperties().getConnectTimeout());
|
||||
singleServerConfig.setTimeout(getRedissonProperties().getTimeout());
|
||||
singleServerConfig.setRetryAttempts(getRedissonProperties().getRetryAttempts());
|
||||
RedisHostAndPort redisHostAndPort = redisHostAndPorts.stream().findAny().get();
|
||||
singleServerConfig.setPassword(redisHostAndPort.getPkey());
|
||||
singleServerConfig.setAddress(String.format("redis://%s/", redisHostAndPort.getHost() + ":" + redisHostAndPort.getPort()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RedisHostAndPort {
|
||||
|
||||
private String host;
|
||||
|
||||
private int port;
|
||||
|
||||
private String pkey;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 集群类型
|
||||
**/
|
||||
public enum RedisType {
|
||||
STANDALONE("standalone", "单节点类型"),
|
||||
CLUSTER("cluster", "集群类型"),
|
||||
SENTINEL("sentinel", "哨兵类型");
|
||||
|
||||
@Getter
|
||||
private String type;
|
||||
|
||||
@Getter
|
||||
private String memo;
|
||||
|
||||
RedisType(String type, String memo) {
|
||||
this.type = type;
|
||||
this.memo = memo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* redisson 配置
|
||||
*/
|
||||
@ConfigurationProperties(prefix = RedissonConfig.PREFIX)
|
||||
@Data
|
||||
public class RedissonConfig {
|
||||
|
||||
public static final String PREFIX = "framework.lingniu.redisson";
|
||||
|
||||
|
||||
private Map<String, RedissonProperties> remote = new HashMap<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.lingniu.framework.plugin.redisson.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 详细配置
|
||||
*/
|
||||
@Data
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* redis连接串信息 host:port,host:port, 多个用,分割,最后一个是;是密码,可以不配置
|
||||
*/
|
||||
private String redisAddresses;
|
||||
|
||||
/**
|
||||
* readMode must be one of SLAVE, MASTER, MASTER_SLAVE
|
||||
*/
|
||||
private String readMode = "MASTER";
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private RedisType storageType = RedisType.CLUSTER;
|
||||
private String clientName = "";
|
||||
|
||||
private int database = 0;
|
||||
|
||||
private int connectionMinimumIdleSize = 12;
|
||||
|
||||
private int connectionPoolSize = 32;
|
||||
|
||||
private int idleConnectionTimeout = 10000;
|
||||
|
||||
private int connectTimeout = 2000;
|
||||
|
||||
private int timeout = 2000;
|
||||
|
||||
private int retryAttempts = 4;
|
||||
//cluster: Slots检查
|
||||
private Boolean checkSlotsCoverage = true;
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.asm.ClassReader;
|
||||
import org.springframework.asm.ClassVisitor;
|
||||
import org.springframework.asm.Label;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.asm.SpringAsmInfo;
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LocalVariableTableParameterNameDiscoverer.class);
|
||||
|
||||
// marker object for classes that do not have any debug info
|
||||
private static final Map<Member, String[]> NO_DEBUG_INFO_MAP = Collections.emptyMap();
|
||||
|
||||
// the cache uses a nested index (value is a map) to keep the top level cache relatively small in size
|
||||
private final Map<Class<?>, Map<Member, String[]>> parameterNamesCache = new ConcurrentHashMap<>(32);
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames(Method method) {
|
||||
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
Class<?> declaringClass = originalMethod.getDeclaringClass();
|
||||
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
|
||||
if (map == null) {
|
||||
map = inspectClass(declaringClass);
|
||||
this.parameterNamesCache.put(declaringClass, map);
|
||||
}
|
||||
if (map != NO_DEBUG_INFO_MAP) {
|
||||
return map.get(originalMethod);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames(Constructor<?> ctor) {
|
||||
Class<?> declaringClass = ctor.getDeclaringClass();
|
||||
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
|
||||
if (map == null) {
|
||||
map = inspectClass(declaringClass);
|
||||
this.parameterNamesCache.put(declaringClass, map);
|
||||
}
|
||||
if (map != NO_DEBUG_INFO_MAP) {
|
||||
return map.get(ctor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the target class. Exceptions will be logged and a maker map returned
|
||||
* to indicate the lack of debug information.
|
||||
*/
|
||||
private Map<Member, String[]> inspectClass(Class<?> clazz) {
|
||||
InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
|
||||
if (is == null) {
|
||||
// We couldn't load the class file, which is not fatal as it
|
||||
// simply means this method of discovering parameter names won't work.
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cannot find '.class' file for class [" + clazz +
|
||||
"] - unable to determine constructor/method parameter names");
|
||||
}
|
||||
return NO_DEBUG_INFO_MAP;
|
||||
}
|
||||
try {
|
||||
ClassReader classReader = new ClassReader(is);
|
||||
Map<Member, String[]> map = new ConcurrentHashMap<>(32);
|
||||
classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);
|
||||
return map;
|
||||
} catch (IOException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Exception thrown while reading '.class' file for class [" + clazz +
|
||||
"] - unable to determine constructor/method parameter names", ex);
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("ASM ClassReader failed to parse class file [" + clazz +
|
||||
"], probably due to a new Java class file version that isn't supported yet " +
|
||||
"- unable to determine constructor/method parameter names", ex);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return NO_DEBUG_INFO_MAP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class that inspects all methods (constructor included) and then
|
||||
* attempts to find the parameter names for that member.
|
||||
*/
|
||||
private static class ParameterNameDiscoveringVisitor extends ClassVisitor {
|
||||
|
||||
private static final String STATIC_CLASS_INIT = "<clinit>";
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
private final Map<Member, String[]> memberMap;
|
||||
|
||||
public ParameterNameDiscoveringVisitor(Class<?> clazz, Map<Member, String[]> memberMap) {
|
||||
super(SpringAsmInfo.ASM_VERSION);
|
||||
this.clazz = clazz;
|
||||
this.memberMap = memberMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
// exclude synthetic + bridged && static class initialization
|
||||
if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) {
|
||||
return new LocalVariableTableVisitor(this.clazz, this.memberMap, name, desc, isStatic(access));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSyntheticOrBridged(int access) {
|
||||
return (((access & Opcodes.ACC_SYNTHETIC) | (access & Opcodes.ACC_BRIDGE)) > 0);
|
||||
}
|
||||
|
||||
private static boolean isStatic(int access) {
|
||||
return ((access & Opcodes.ACC_STATIC) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class LocalVariableTableVisitor extends MethodVisitor {
|
||||
|
||||
private static final String CONSTRUCTOR = "<init>";
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
private final Map<Member, String[]> memberMap;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Type[] args;
|
||||
|
||||
private final String[] parameterNames;
|
||||
|
||||
private final boolean isStatic;
|
||||
|
||||
private boolean hasLvtInfo = false;
|
||||
|
||||
/*
|
||||
* The nth entry contains the slot index of the LVT table entry holding the
|
||||
* argument name for the nth parameter.
|
||||
*/
|
||||
private final int[] lvtSlotIndex;
|
||||
|
||||
public LocalVariableTableVisitor(Class<?> clazz, Map<Member, String[]> map, String name, String desc, boolean isStatic) {
|
||||
super(SpringAsmInfo.ASM_VERSION);
|
||||
this.clazz = clazz;
|
||||
this.memberMap = map;
|
||||
this.name = name;
|
||||
this.args = Type.getArgumentTypes(desc);
|
||||
this.parameterNames = new String[this.args.length];
|
||||
this.isStatic = isStatic;
|
||||
this.lvtSlotIndex = computeLvtSlotIndices(isStatic, this.args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {
|
||||
this.hasLvtInfo = true;
|
||||
for (int i = 0; i < this.lvtSlotIndex.length; i++) {
|
||||
if (this.lvtSlotIndex[i] == index) {
|
||||
this.parameterNames[i] = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
if (this.hasLvtInfo || (this.isStatic && this.parameterNames.length == 0)) {
|
||||
// visitLocalVariable will never be called for static no args methods
|
||||
// which doesn't use any local variables.
|
||||
// This means that hasLvtInfo could be false for that kind of methods
|
||||
// even if the class has local variable info.
|
||||
this.memberMap.put(resolveMember(), this.parameterNames);
|
||||
}
|
||||
}
|
||||
|
||||
private Member resolveMember() {
|
||||
ClassLoader loader = this.clazz.getClassLoader();
|
||||
Class<?>[] argTypes = new Class<?>[this.args.length];
|
||||
for (int i = 0; i < this.args.length; i++) {
|
||||
argTypes[i] = ClassUtils.resolveClassName(this.args[i].getClassName(), loader);
|
||||
}
|
||||
try {
|
||||
if (CONSTRUCTOR.equals(this.name)) {
|
||||
return this.clazz.getDeclaredConstructor(argTypes);
|
||||
}
|
||||
return this.clazz.getDeclaredMethod(this.name, argTypes);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Method [" + this.name +
|
||||
"] was discovered in the .class file but cannot be resolved in the class object", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) {
|
||||
int[] lvtIndex = new int[paramTypes.length];
|
||||
int nextIndex = (isStatic ? 0 : 1);
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
lvtIndex[i] = nextIndex;
|
||||
if (isWideType(paramTypes[i])) {
|
||||
nextIndex += 2;
|
||||
} else {
|
||||
nextIndex++;
|
||||
}
|
||||
}
|
||||
return lvtIndex;
|
||||
}
|
||||
|
||||
private static boolean isWideType(Type aType) {
|
||||
// float is not a wide type
|
||||
return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClusterLockerService;
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 初始化-RedissonClusterLockerService
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableConfigurationProperties({RedissonConfig.class})
|
||||
public class RedissonAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RedissonClusterLockerService redissonClusterLockerService() {
|
||||
return new RedissonClusterLockerService() {
|
||||
@Override
|
||||
public <T, R> R wrapLock(T source, Function<T, String> fun, Function<T, String> keyGetter, Function<T, R> wrapFun, Long leaseTime, TimeUnit leaseTimeUnit) {
|
||||
RedissonClient client = Optional.ofNullable(RedissonClientFactory.getRedissonClient(fun.apply(source))).orElseThrow(() -> new IllegalStateException("Redisson client not found for: " + fun.apply(source)));
|
||||
RLock lock = client.getLock(keyGetter.apply(source));
|
||||
try {
|
||||
lock.lock(leaseTime, leaseTimeUnit);
|
||||
return wrapFun.apply(source);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String delivery(String key) {
|
||||
return new ArrayList<>(RedissonClientFactory.getRedisMap().keySet()).toArray(new String[]{})[Math.abs(key.hashCode() % RedissonClientFactory.getRedisMap().size())];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedissonBeanRegisterPostProcessor redissonBeanRegister() {
|
||||
return new RedissonBeanRegisterPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
|
||||
import cn.lingniu.framework.plugin.redisson.builder.RedissonClusterClientBuilder;
|
||||
import cn.lingniu.framework.plugin.redisson.builder.RedissonStandaloneClientBuilder;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedisType;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonConfig;
|
||||
import cn.lingniu.framework.plugin.redisson.config.RedissonProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.Redisson;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.redisson.config.Config;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedissonBeanRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
RedissonConfig config = Binder.get(applicationContext.getEnvironment()).bind(RedissonConfig.PREFIX, RedissonConfig.class).orElse(new RedissonConfig());
|
||||
Map<String, RedissonProperties> remote = config.getRemote();
|
||||
Optional.ofNullable(remote).ifPresent(b -> b.forEach((k, v) -> {
|
||||
if (applicationContext.containsBean(k)) {
|
||||
throw new IllegalStateException("redisson的实例对象已注册,请查看配置:" + k);
|
||||
}
|
||||
registerContainer(registry, k, v);
|
||||
}));
|
||||
}
|
||||
|
||||
private void registerContainer(BeanDefinitionRegistry registry, String beanName, RedissonProperties redissonProperties) {
|
||||
Config redissonConfig = null;
|
||||
if (RedisType.STANDALONE.equals(redissonProperties.getStorageType())) {
|
||||
redissonConfig = new RedissonStandaloneClientBuilder(redissonProperties).build();
|
||||
} else if (RedisType.CLUSTER.equals(redissonProperties.getStorageType())) {
|
||||
redissonConfig = new RedissonClusterClientBuilder(redissonProperties).build();
|
||||
} else {
|
||||
throw new IllegalArgumentException("不支持的Redisson存储类型:" + redissonProperties.getStorageType());
|
||||
}
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(Redisson.class);
|
||||
builder.addConstructorArgValue(redissonConfig);
|
||||
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
|
||||
// 从ApplicationContext中获取已注册的Bean实例
|
||||
RedissonClient jedisCluster = applicationContext.getBean(beanName, RedissonClient.class);
|
||||
RedissonClientFactory.putRedissonClient(beanName, jedisCluster);
|
||||
if (!registry.containsBeanDefinition("redissonDistributedLockAspectConfiguration")) {
|
||||
registry.registerBeanDefinition("redissonDistributedLockAspectConfiguration",
|
||||
BeanDefinitionBuilder.rootBeanDefinition(RedissonDistributedLockAspectConfiguration.class).getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package cn.lingniu.framework.plugin.redisson.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonLockAction;
|
||||
import cn.lingniu.framework.plugin.redisson.RedissonClientFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Aspect 实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class RedissonDistributedLockAspectConfiguration {
|
||||
|
||||
private ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
@Pointcut("@annotation(cn.lingniu.framework.plugin.redisson.RedissonLockAction)")
|
||||
public void lockPoint() {
|
||||
}
|
||||
|
||||
@Around("lockPoint()")
|
||||
public Object around(ProceedingJoinPoint pjp) throws Throwable {
|
||||
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
|
||||
RedissonLockAction redissonLockAction = method.getAnnotation(RedissonLockAction.class);
|
||||
Object[] args = pjp.getArgs();
|
||||
String key = redissonLockAction.value();
|
||||
key = parse(key, method, args);
|
||||
|
||||
RLock lock = getLock(key, redissonLockAction);
|
||||
if (!lock.tryLock(redissonLockAction.waitTime(), redissonLockAction.leaseTime(), redissonLockAction.unit())) {
|
||||
log.warn("RedissonLockAction-获取锁失败: [{}]", key);
|
||||
if (redissonLockAction.throwException())
|
||||
throw new RuntimeException(String.format("RedissonLockAction-获取锁失败:[%s]", key));
|
||||
return null;
|
||||
}
|
||||
log.info("RedissonLockAction-获取锁成功: [{}]", key);
|
||||
try {
|
||||
return pjp.proceed();
|
||||
} catch (Exception e) {
|
||||
log.error(String.format("RedissonLockAction-方法锁执行异常:[%s]", key), e);
|
||||
throw e;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
log.info("RedissonLockAction-释放锁成功: [{}]", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析spring EL表达式
|
||||
*/
|
||||
private String parse(String key, Method method, Object[] args) {
|
||||
String[] params = discoverer.getParameterNames(method);
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
context.setVariable(params[i], args[i]);
|
||||
}
|
||||
return parser.parseExpression(key).getValue(context, String.class);
|
||||
}
|
||||
|
||||
private RLock getLock(String key, RedissonLockAction redissonLockAction) {
|
||||
RedissonClient redissonClient = RedissonClientFactory.getRedissonClient(redissonLockAction.redissonName());
|
||||
switch (redissonLockAction.lockType()) {
|
||||
case REENTRANT_LOCK:
|
||||
return redissonClient.getLock(key);
|
||||
case FAIR_LOCK:
|
||||
return redissonClient.getFairLock(key);
|
||||
case READ_LOCK:
|
||||
return redissonClient.getReadWriteLock(key).readLock();
|
||||
case WRITE_LOCK:
|
||||
return redissonClient.getReadWriteLock(key).writeLock();
|
||||
default:
|
||||
throw new RuntimeException("RedissonLockAction-不支持的锁类型:" + redissonLockAction.lockType().name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.redisson.init.RedissonAutoConfiguration
|
||||
@@ -0,0 +1,121 @@
|
||||
# 【重要】redisson详细资料-redis搭建---参考官网
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 基于 Redisson 封装的 Redis 集群操作和分布式锁组件,专门用于处理 Redis 集群环境下的分布式锁场景
|
||||
2. 核心能力
|
||||
* 高性能键值存储:基于RocksDB的高效读写能力,支持快速的数据存取操作。
|
||||
* 集群自适应感知:客户端能够感知集群变化并自动刷新配置
|
||||
* Redisson 操作:基于 Redisson 提供 Redis 集群操作能力
|
||||
* 分布式锁支持:支持多种类型的分布式锁:
|
||||
■ 可重入锁(REENTRANT_LOCK)
|
||||
■ 公平锁(FAIR_LOCK)
|
||||
■ 读锁(READ_LOCK)
|
||||
■ 写锁(WRITE_LOCK)
|
||||
* 注解式锁操作:通过 @LockAction 注解简化分布式锁的使用
|
||||
* 灵活配置:支持锁等待时间、自动释放时间、时间单位等参数配置
|
||||
* 异常处理:支持锁获取失败时的异常抛出或返回 null 两种处理方式
|
||||
* 支持多集群操作redis,也可扩展队列消费
|
||||
3. 适用场景:该组件特别适用于需要在 Redis 集群环境下使用分布式锁的企业级应用,通过注解方式简化了分布式锁的使用,提供了多种锁类型以满足不同业务场景的需求
|
||||
* 分布式系统:需要在分布式环境下保证数据一致性的场景
|
||||
* 高并发业务:需要防止并发操作导致数据不一致的业务场景
|
||||
* 集群环境:基于 Redis 集群部署的应用系统
|
||||
* 资源竞争控制:需要对共享资源进行并发访问控制的场景
|
||||
* 定时任务:分布式环境下需要确保任务单实例执行的定时任务
|
||||
* 库存扣减:电商系统中需要防止超卖的库存扣减场景
|
||||
* 幂等性保证:需要保证接口幂等性的业务操作。
|
||||
|
||||
## 如何配置--更多参数参考:RedissonConfig/RedissonProperties
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
lingniu:
|
||||
redisson:
|
||||
remote:
|
||||
r1:
|
||||
# 应用id需在cachecloud申请
|
||||
redisAddresses: xxxx:6387
|
||||
# 客户端名称,默认为应用名 + IP
|
||||
clientName: ""
|
||||
# 单节点 Redis 默认数据库,默认为 0
|
||||
database: 0
|
||||
# 连接池最小空闲连接数,默认为 12
|
||||
connectionMinimumIdleSize: 12
|
||||
# 连接池最大连接数,默认为 32
|
||||
connectionPoolSize: 32
|
||||
# 空闲连接超时时间(毫秒),默认为 10000
|
||||
idleConnectionTimeout: 10000
|
||||
# 连接超时时间(毫秒),默认为 2000
|
||||
connectTimeout: 2000
|
||||
# 操作超时时间(毫秒),默认为 2000
|
||||
timeout: 2000
|
||||
# 操作重试次数,默认为 4
|
||||
retryAttempts: 4
|
||||
# 集群模式下是否检查 Slots 覆盖,默认为 true
|
||||
checkSlotsCoverage: true
|
||||
# 读取模式,默认为 "SLAVE"
|
||||
# 可选值:
|
||||
# - SLAVE: 从节点读取
|
||||
# - MASTER: 主节点读取
|
||||
# - MASTER_SLAVE: 主从节点读取
|
||||
readMode: "SLAVE"
|
||||
# 存储类型,默认为 "CLUSTER"
|
||||
# 可选值:
|
||||
# - CLUSTER: 集群模式
|
||||
# - SINGLE: 单节点模式
|
||||
# - SENTINEL: 哨兵模式
|
||||
# - REPLICATED: 复制模式
|
||||
storageType: "CLUSTER"
|
||||
# 实例名称
|
||||
r2:
|
||||
# 应用id需在cachecloud申请
|
||||
redisAddresses: xxx:6387
|
||||
# 单节点 Redis 默认数据库,默认为 0
|
||||
database: 0
|
||||
|
||||
```
|
||||
|
||||
## 如何使用--正常操作redis
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private RedissonClient test;
|
||||
// 或者
|
||||
RedissonFactory.getRedissonClient("test").api
|
||||
```
|
||||
|
||||
## 如何使用--分布式锁
|
||||
|
||||
```java
|
||||
/**
|
||||
* 使用 #orderId 参数作为锁的key
|
||||
*/
|
||||
@PostMapping("/process/{orderId}")
|
||||
@LockAction(
|
||||
redissonName = "r1",
|
||||
key = "#orderId",
|
||||
lockType = LockType.REENTRANT_LOCK,
|
||||
waitTime = 3000L, leaseTime = 30000L, unit = TimeUnit.MILLISECONDS,
|
||||
throwEx = true
|
||||
)
|
||||
public ResponseEntity<String> processOrder(@PathVariable String orderId) {
|
||||
// 业务逻辑
|
||||
return ResponseEntity.ok("Order processed: " + orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用请求参数
|
||||
*/
|
||||
@GetMapping("/query")
|
||||
@LockAction(
|
||||
redissonName = "defaultRedisson",
|
||||
key = "'query:' + #userId + ':' + #status",
|
||||
throwEx = true
|
||||
)
|
||||
public ResponseEntity<List<Order>> queryOrders(
|
||||
@RequestParam String userId,
|
||||
@RequestParam String status) {
|
||||
// 业务逻辑
|
||||
return ResponseEntity.ok(new ArrayList<>());
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-apollo</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ctrip.framework.apollo</groupId>
|
||||
<artifactId>apollo-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.lingniu.framework.plugin.apollo.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = ApolloConfig.PRE_FIX)
|
||||
public class ApolloConfig {
|
||||
|
||||
public final static String PRE_FIX = "framework.lingniu.apollo";
|
||||
|
||||
/**
|
||||
* 是否开始Apollo配置
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
/**
|
||||
* Meta地址
|
||||
*/
|
||||
private String meta;
|
||||
/**
|
||||
* 配置文件列表 以,分割
|
||||
*/
|
||||
private String namespaces = "";
|
||||
/**
|
||||
* 无需配置 app.id 等于 spring.application.name
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.lingniu.framework.plugin.apollo.extend;
|
||||
|
||||
import com.ctrip.framework.apollo.Config;
|
||||
import com.ctrip.framework.apollo.ConfigChangeListener;
|
||||
import com.ctrip.framework.apollo.ConfigService;
|
||||
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloProcessor;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Apollo Annotation Processor for Spring Application
|
||||
*
|
||||
* @author Jason Song(song_s@ctrip.com)
|
||||
*/
|
||||
public class ApolloAnnotationProcessor extends ApolloProcessor {
|
||||
|
||||
@Override
|
||||
protected void processField(Object bean, String beanName, Field field) {
|
||||
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
|
||||
"Invalid type: %s for field: %s, should be Config", field.getType(), field);
|
||||
|
||||
String namespace = System.getProperty("apollo.bootstrap.namespaces", annotation.value());
|
||||
Config config = ConfigService.getConfig(namespace);
|
||||
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ReflectionUtils.setField(field, bean, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processMethod(final Object bean, String beanName, final Method method) {
|
||||
ApolloConfigChangeListener annotation = AnnotationUtils
|
||||
.findAnnotation(method, ApolloConfigChangeListener.class);
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Preconditions.checkArgument(parameterTypes.length == 1,
|
||||
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
|
||||
method);
|
||||
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
|
||||
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
|
||||
method);
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
|
||||
String namespaceProperties = System.getProperty("apollo.bootstrap.namespaces", String.join(",", annotation.value()));
|
||||
String[] namespaces = namespaceProperties.split(",");
|
||||
String[] annotatedInterestedKeys = annotation.interestedKeys();
|
||||
String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
|
||||
ConfigChangeListener configChangeListener = changeEvent -> ReflectionUtils.invokeMethod(method, bean, changeEvent);
|
||||
|
||||
Set<String> interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
|
||||
Set<String> interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null;
|
||||
|
||||
for (String namespace : namespaces) {
|
||||
Config config = ConfigService.getConfig(namespace);
|
||||
|
||||
if (interestedKeys == null && interestedKeyPrefixes == null) {
|
||||
config.addChangeListener(configChangeListener);
|
||||
} else {
|
||||
config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.lingniu.framework.plugin.apollo.extend;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import com.ctrip.framework.apollo.model.ConfigChange;
|
||||
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScope;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ApolloConfigChangeLogListener implements ApplicationContextAware {
|
||||
|
||||
@Resource
|
||||
RefreshScope refreshScope;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@ApolloConfigChangeListener
|
||||
public void onChange(ConfigChangeEvent changeEvent) {
|
||||
if (log.isInfoEnabled()) {
|
||||
for (String changedKey : changeEvent.changedKeys()) {
|
||||
ConfigChange changeInfo = changeEvent.getChange(changedKey);
|
||||
if (ObjectEmptyUtils.isEmpty(changeInfo)) {
|
||||
continue;
|
||||
}
|
||||
log.info("【apollo 配置变更】 - namespace: {}, property: {}, oldValue: {}, newValue: {}, changeType: {}",
|
||||
changeInfo.getNamespace(), changeInfo.getPropertyName(),
|
||||
changeInfo.getOldValue(), changeInfo.getNewValue(), changeInfo.getChangeType());
|
||||
}
|
||||
}
|
||||
applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
|
||||
refreshProperties(changeEvent);
|
||||
}
|
||||
|
||||
public void refreshProperties(ConfigChangeEvent changeEvent) {
|
||||
try {
|
||||
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
|
||||
if (ObjectEmptyUtils.isNotEmpty(refreshScope)) {
|
||||
refreshScope.refreshAll();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to refresh properties after Apollo config change", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.lingniu.framework.plugin.apollo.extend;
|
||||
|
||||
import javassist.ClassPool;
|
||||
import javassist.LoaderClassPath;
|
||||
|
||||
|
||||
public class ClassPoolUtils {
|
||||
|
||||
private static volatile ClassPool instance;
|
||||
|
||||
private ClassPoolUtils() {
|
||||
}
|
||||
|
||||
public static ClassPool getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (ClassPoolUtils.class) {
|
||||
if (instance == null) {
|
||||
instance = ClassPool.getDefault();
|
||||
instance.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lingniu.framework.plugin.apollo.extend;
|
||||
|
||||
import com.ctrip.framework.apollo.core.spi.Ordered;
|
||||
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
|
||||
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
|
||||
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
|
||||
import com.ctrip.framework.apollo.spring.spi.ConfigPropertySourcesProcessorHelper;
|
||||
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FramewokConfigPropertySourcesProcessorHelper implements ConfigPropertySourcesProcessorHelper {
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
|
||||
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
|
||||
propertySourcesPlaceholderPropertyValues.put("order", 0);
|
||||
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class,
|
||||
propertySourcesPlaceholderPropertyValues);
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, AutoUpdateConfigChangeListener.class);//
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, FrameworkApolloAnnotationProcessor.class); //扩展 Apollo 注解处理器
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class);
|
||||
|
||||
processSpringValueDefinition(registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be instantiated if
|
||||
* it is added in postProcessBeanDefinitionRegistry phase, so we have to manually call the
|
||||
* postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here...
|
||||
*/
|
||||
private void processSpringValueDefinition(BeanDefinitionRegistry registry) {
|
||||
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
|
||||
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 10 ; // 优先级放高
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package cn.lingniu.framework.plugin.apollo.extend;
|
||||
|
||||
import com.ctrip.framework.apollo.Config;
|
||||
import com.ctrip.framework.apollo.ConfigChangeListener;
|
||||
import com.ctrip.framework.apollo.ConfigService;
|
||||
import com.ctrip.framework.apollo.build.ApolloInjector;
|
||||
import com.ctrip.framework.apollo.core.utils.StringUtils;
|
||||
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
|
||||
import com.ctrip.framework.apollo.spring.annotation.ApolloProcessor;
|
||||
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
|
||||
import com.ctrip.framework.apollo.spring.property.SpringValue;
|
||||
import com.ctrip.framework.apollo.spring.property.SpringValueRegistry;
|
||||
import com.ctrip.framework.apollo.spring.util.SpringInjector;
|
||||
import com.ctrip.framework.apollo.util.ConfigUtil;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Apollo Annotation Processor for Spring Application
|
||||
*/
|
||||
public class FrameworkApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware,
|
||||
EnvironmentAware {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApolloAnnotationProcessor.class);
|
||||
|
||||
private static final String NAMESPACE_DELIMITER = ",";
|
||||
|
||||
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(NAMESPACE_DELIMITER)
|
||||
.omitEmptyStrings().trimResults();
|
||||
private static final Map<String, Gson> DATEPATTERN_GSON_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConfigUtil configUtil; // 配置工具类
|
||||
private final PlaceholderHelper placeholderHelper;
|
||||
private final SpringValueRegistry springValueRegistry;
|
||||
|
||||
/**
|
||||
* resolve the expression.
|
||||
*/
|
||||
private ConfigurableBeanFactory configurableBeanFactory;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public FrameworkApolloAnnotationProcessor() {
|
||||
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
|
||||
placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
|
||||
springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processField(Object bean, String beanName, Field field) {
|
||||
this.processApolloConfig(bean, field);
|
||||
this.processApolloJsonValue(bean, beanName, field);
|
||||
}
|
||||
// 处理Bean方法上的Apollo注解
|
||||
@Override
|
||||
protected void processMethod(final Object bean, String beanName, final Method method) {
|
||||
this.processApolloConfigChangeListener(bean, method);
|
||||
this.processApolloJsonValue(bean, beanName, method);
|
||||
}
|
||||
// field-具体注解处理方法--处理@ApolloConfig字段注解
|
||||
private void processApolloConfig(Object bean, Field field) {
|
||||
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
|
||||
"Invalid type: %s for field: %s, should be Config", field.getType(), field);
|
||||
final String appId = StringUtils.defaultIfBlank(annotation.appId(), configUtil.getAppId());
|
||||
final String namespace = annotation.value();
|
||||
final String resolvedAppId = this.environment.resolveRequiredPlaceholders(appId);
|
||||
final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace);
|
||||
Config config = ConfigService.getConfig(resolvedAppId, resolvedNamespace);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ReflectionUtils.setField(field, bean, config);
|
||||
}
|
||||
private void processApolloConfigChangeListener(final Object bean, final Method method) {
|
||||
ApolloConfigChangeListener annotation = AnnotationUtils
|
||||
.findAnnotation(method, ApolloConfigChangeListener.class);
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Preconditions.checkArgument(parameterTypes.length == 1,
|
||||
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
|
||||
method);
|
||||
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
|
||||
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
|
||||
method);
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
String appId = StringUtils.defaultIfBlank(annotation.appId(), configUtil.getAppId());
|
||||
String namespaceProperties =System.getProperty("apollo.bootstrap.namespaces",String.join( "," ,annotation.value())); //todo 默认处理所有-bootstrap.namespaces
|
||||
String[] namespaces = namespaceProperties.split(",");
|
||||
String[] annotatedInterestedKeys = annotation.interestedKeys();
|
||||
String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
|
||||
ConfigChangeListener configChangeListener = changeEvent -> ReflectionUtils.invokeMethod(method, bean, changeEvent);
|
||||
|
||||
Set<String> interestedKeys =
|
||||
annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
|
||||
Set<String> interestedKeyPrefixes =
|
||||
annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes)
|
||||
: null;
|
||||
|
||||
Set<String> resolvedNamespaces = processResolveNamespaceValue(namespaces);
|
||||
|
||||
for (String namespace : resolvedNamespaces) {
|
||||
Config config = ConfigService.getConfig(appId, namespace);
|
||||
|
||||
if (interestedKeys == null && interestedKeyPrefixes == null) {
|
||||
config.addChangeListener(configChangeListener);
|
||||
} else {
|
||||
config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate and resolve namespaces from env/properties.
|
||||
* Split delimited namespaces
|
||||
* @param namespaces
|
||||
* @return resolved namespaces
|
||||
*/
|
||||
private Set<String> processResolveNamespaceValue(String[] namespaces) {
|
||||
|
||||
Set<String> resolvedNamespaces = new HashSet<>();
|
||||
|
||||
for (String namespace : namespaces) {
|
||||
final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace);
|
||||
|
||||
if (resolvedNamespace.contains(NAMESPACE_DELIMITER)) {
|
||||
resolvedNamespaces.addAll(NAMESPACE_SPLITTER.splitToList(resolvedNamespace));
|
||||
} else {
|
||||
resolvedNamespaces.add(resolvedNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedNamespaces;
|
||||
}
|
||||
|
||||
private void processApolloJsonValue(Object bean, String beanName, Field field) {
|
||||
ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class);
|
||||
if (apolloJsonValue == null) {
|
||||
return; // 处理方法上的@ApolloJsonValue注解
|
||||
}
|
||||
|
||||
String placeholder = apolloJsonValue.value();
|
||||
String datePattern = apolloJsonValue.datePattern();
|
||||
Object propertyValue = this.resolvePropertyValue(beanName, placeholder);
|
||||
if (propertyValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean accessible = field.isAccessible();
|
||||
field.setAccessible(true);
|
||||
ReflectionUtils
|
||||
.setField(field, bean, parseJsonValue((String) propertyValue, field.getGenericType(), datePattern));
|
||||
field.setAccessible(accessible);
|
||||
|
||||
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
|
||||
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
|
||||
for (String key : keys) {
|
||||
SpringValue springValue = new SpringValue(key, placeholder, bean, beanName, field, true);
|
||||
springValueRegistry.register(this.configurableBeanFactory, key, springValue);
|
||||
logger.debug("Monitoring {}", springValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processApolloJsonValue(Object bean, String beanName, Method method) {
|
||||
ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(method, ApolloJsonValue.class);
|
||||
if (apolloJsonValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String placeHolder = apolloJsonValue.value();
|
||||
String datePattern = apolloJsonValue.datePattern();
|
||||
Object propertyValue = this.resolvePropertyValue(beanName, placeHolder);
|
||||
if (propertyValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Type[] types = method.getGenericParameterTypes();
|
||||
Preconditions.checkArgument(types.length == 1,
|
||||
"Ignore @ApolloJsonValue setter {}.{}, expecting 1 parameter, actual {} parameters",
|
||||
bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
|
||||
|
||||
boolean accessible = method.isAccessible();
|
||||
method.setAccessible(true); // 解析占位符获取JSON值
|
||||
ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String) propertyValue, types[0], datePattern));
|
||||
method.setAccessible(accessible);
|
||||
|
||||
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
|
||||
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder);
|
||||
for (String key : keys) {
|
||||
SpringValue springValue = new SpringValue(key, placeHolder, bean, beanName, method, true);
|
||||
springValueRegistry.register(this.configurableBeanFactory, key, springValue);
|
||||
logger.debug("Monitoring {}", springValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object resolvePropertyValue(String beanName, String placeHolder) {
|
||||
Object propertyValue = placeholderHelper
|
||||
.resolvePropertyValue(this.configurableBeanFactory, beanName, placeHolder);
|
||||
|
||||
// propertyValue will never be null, as @ApolloJsonValue will not allow that
|
||||
if (!(propertyValue instanceof String)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
private Object parseJsonValue(String json, Type targetType, String datePattern) {
|
||||
try {
|
||||
return DATEPATTERN_GSON_MAP.computeIfAbsent(datePattern, this::buildGson).fromJson(json, targetType);
|
||||
} catch (Throwable ex) {
|
||||
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private Gson buildGson(String datePattern) {
|
||||
if (StringUtils.isBlank(datePattern)) {
|
||||
return new Gson();
|
||||
}
|
||||
return new GsonBuilder().setDateFormat(datePattern).create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package cn.lingniu.framework.plugin.apollo.extend;
|
||||
|
||||
import com.ctrip.framework.apollo.build.ApolloInjector;
|
||||
import com.ctrip.framework.apollo.core.spi.Ordered;
|
||||
import com.ctrip.framework.apollo.core.utils.StringUtils;
|
||||
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
|
||||
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
|
||||
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
|
||||
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
|
||||
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
|
||||
import com.ctrip.framework.apollo.spring.spi.DefaultApolloConfigRegistrarHelper;
|
||||
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
|
||||
import com.ctrip.framework.apollo.util.ConfigUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FrameworkApolloConfigRegistrarHelper implements com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper, Ordered {
|
||||
private static final Logger logger = LoggerFactory.getLogger(
|
||||
DefaultApolloConfigRegistrarHelper.class);
|
||||
|
||||
private final ConfigUtil configUtil = ApolloInjector.getInstance(ConfigUtil.class);
|
||||
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
AnnotationAttributes attributes = AnnotationAttributes
|
||||
.fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
|
||||
final String[] namespaces = System.getProperty("apollo.bootstrap.namespaces","application").split(","); //todo 默认处理所有-bootstrap.namespaces
|
||||
final int order = attributes.getNumber("order");
|
||||
|
||||
// put main appId
|
||||
PropertySourcesProcessor.addNamespaces(configUtil.getAppId(), Lists.newArrayList(this.resolveNamespaces(namespaces)), order);
|
||||
|
||||
// put multiple appId into
|
||||
AnnotationAttributes[] multipleConfigs = attributes.getAnnotationArray("multipleConfigs");
|
||||
if (multipleConfigs != null) {
|
||||
for (AnnotationAttributes multipleConfig : multipleConfigs) {
|
||||
String appId = multipleConfig.getString("appId");
|
||||
String[] multipleNamespaces = this.resolveNamespaces(multipleConfig.getStringArray("namespaces"));
|
||||
String secret = resolveSecret(multipleConfig.getString("secret"));
|
||||
int multipleOrder = multipleConfig.getNumber("order");
|
||||
|
||||
// put multiple secret into system property
|
||||
if (!StringUtils.isBlank(secret)) {
|
||||
System.setProperty("apollo.accesskey." + appId + ".secret", secret);
|
||||
}
|
||||
PropertySourcesProcessor.addNamespaces(appId, Lists.newArrayList(multipleNamespaces), multipleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
|
||||
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
|
||||
propertySourcesPlaceholderPropertyValues.put("order", 0);
|
||||
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class,
|
||||
propertySourcesPlaceholderPropertyValues); // 属性占位符配置器
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, AutoUpdateConfigChangeListener.class);
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class);
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, FrameworkApolloAnnotationProcessor.class); //todo 扩展的Apollo 注解处理器
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class);
|
||||
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class);
|
||||
}
|
||||
|
||||
private String[] resolveNamespaces(String[] namespaces) {
|
||||
// no support for Spring version prior to 3.2.x, see https://github.com/apolloconfig/apollo/issues/4178
|
||||
if (this.environment == null) {
|
||||
logNamespacePlaceholderNotSupportedMessage(namespaces);
|
||||
return namespaces;
|
||||
}
|
||||
String[] resolvedNamespaces = new String[namespaces.length];
|
||||
for (int i = 0; i < namespaces.length; i++) {
|
||||
// throw IllegalArgumentException if given text is null or if any placeholders are unresolvable
|
||||
resolvedNamespaces[i] = this.environment.resolveRequiredPlaceholders(namespaces[i]);
|
||||
}
|
||||
return resolvedNamespaces;
|
||||
}
|
||||
|
||||
private String resolveSecret(String secret){
|
||||
if (this.environment == null) {
|
||||
if (secret != null && secret.contains("${")) {
|
||||
logger.warn("secret placeholder {} is not supported for Spring version prior to 3.2.x", secret);
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
return this.environment.resolveRequiredPlaceholders(secret);
|
||||
}
|
||||
|
||||
private void logNamespacePlaceholderNotSupportedMessage(String[] namespaces) {
|
||||
for (String namespace : namespaces) {
|
||||
if (namespace.contains("${")) {
|
||||
logger.warn("Namespace placeholder {} is not supported for Spring version prior to 3.2.x,"
|
||||
+ " see https://github.com/apolloconfig/apollo/issues/4178 for more details.",
|
||||
namespace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.lingniu.framework.plugin.apollo.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.apollo.extend.ApolloConfigChangeLogListener;
|
||||
import cn.lingniu.framework.plugin.apollo.config.ApolloConfig;
|
||||
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
|
||||
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableApolloConfig
|
||||
@ConditionalOnProperty(prefix = ApolloConfig.PRE_FIX, name = "enabled", havingValue = "true")
|
||||
@Import({ApolloConfigChangeLogListener.class})
|
||||
public class ApolloAutoConfiguration implements InitializingBean {
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
log.info("Apollo configuration enabled(启动), meta URL: {}", PropertyUtils.getProperty("apollo.meta"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cn.lingniu.framework.plugin.apollo.init;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.apollo.config.ApolloConfig;
|
||||
import cn.lingniu.framework.plugin.apollo.extend.ClassPoolUtils;
|
||||
import cn.lingniu.framework.plugin.core.config.CommonConstant;
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
/**
|
||||
* apollo 初始化
|
||||
*/
|
||||
@Slf4j
|
||||
@Order(Integer.MIN_VALUE + 100)
|
||||
public class ApolloInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
|
||||
private String applicationName;
|
||||
|
||||
private final static String AppId = "app.id";
|
||||
private final static String ApolloMeta = "apollo.meta";
|
||||
private final static String ApolloBootstrapEnabled = "apollo.bootstrap.enabled";
|
||||
private final static String ApolloBootstrapEagerLoadEnabled = "apollo.bootstrap.eagerLoad.enabled";
|
||||
private final static String UserDir = "user.dir";
|
||||
|
||||
public final static String NameSpaces = "framework.lingniu.apollo.namespaces";
|
||||
public final static String FrameworkMeta = "framework.lingniu.apollo.meta";
|
||||
public final static String FrameworkEnabled = "framework.lingniu.apollo.enabled";
|
||||
|
||||
|
||||
private final static String APOLLO_NAME = ApolloConfig.PRE_FIX + ".name";
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
ConfigurableEnvironment environment = applicationContext.getEnvironment();
|
||||
applicationName = environment.getProperty(APOLLO_NAME, environment.getProperty(CommonConstant.SPRING_APP_NAME_KEY));
|
||||
String profile = environment.getProperty(CommonConstant.ACTIVE_PROFILES_PROPERTY);
|
||||
this.initApolloConfig(environment, applicationName, profile);
|
||||
}
|
||||
|
||||
|
||||
void initApolloConfig(ConfigurableEnvironment environment, String appName, String profile) {
|
||||
//优先原始配置
|
||||
if (!ObjectEmptyUtils.isEmpty(environment.getProperty(ApolloBootstrapEnabled))
|
||||
&& !ObjectEmptyUtils.isEmpty(environment.getProperty(ApolloMeta))
|
||||
&& !ObjectEmptyUtils.isEmpty(environment.getProperty(AppId))) {
|
||||
return;
|
||||
}
|
||||
if (ObjectEmptyUtils.isEmpty(environment.getProperty(FrameworkEnabled)) || environment.getProperty(FrameworkEnabled).equalsIgnoreCase("false")) {
|
||||
setDefaultProperty(ApolloBootstrapEnabled, "false");
|
||||
setDefaultProperty(ApolloBootstrapEagerLoadEnabled, "false");
|
||||
return;
|
||||
}
|
||||
PropertyUtils.setDefaultInitProperty("app.id", "test-ln");
|
||||
// PropertyUtils.setDefaultInitProperty("app.id", appName);
|
||||
setDefaultProperty("apollo.meta", environment.getProperty(FrameworkMeta));
|
||||
setDefaultProperty("apollo.bootstrap.enabled", "true");
|
||||
setDefaultProperty("apollo.bootstrap.namespaces", environment.getProperty(NameSpaces, "application"));
|
||||
setDefaultProperty("apollo.bootstrap.eagerLoad.enabled", "true");
|
||||
setDefaultProperty("spring.boot.enableautoconfiguration", "true");
|
||||
setDefaultProperty("env",profile.toUpperCase());
|
||||
setDefaultProperty("apollo.cache-dir", System.getProperty(UserDir) + File.separator + "apolloConfig" + File.separator);
|
||||
|
||||
}
|
||||
|
||||
void setDefaultProperty(String key, String defaultPropertyValue) {
|
||||
PropertyUtils.setDefaultInitProperty(key, defaultPropertyValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.apollo.extend.FrameworkApolloConfigRegistrarHelper
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.apollo.extend.FramewokConfigPropertySourcesProcessorHelper
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
cn.lingniu.framework.plugin.apollo.init.ApolloInit
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.apollo.init.ApolloAutoConfiguration
|
||||
@@ -0,0 +1,37 @@
|
||||
# 【重要】apllo服务搭建、和详细demo使用教程---参考官网
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 定位:基于 Apollo 封装的分布式配置管理中心组件
|
||||
2. 核心能力
|
||||
* 集中化配置管理:统一管理不同环境、不同集群的配置
|
||||
* 实时推送更新:配置变更后实时推送到应用端,无需重启服务
|
||||
* 多环境支持:支持开发、测试、生产等多套环境配置隔离
|
||||
3. 适用场景
|
||||
* 微服务架构下的配置管理
|
||||
* 多环境、多集群的应用配置管理
|
||||
* 需要动态调整配置参数的业务场
|
||||
* 对配置变更实时性要求较高的系统
|
||||
|
||||
## 如何配置--参考:ApolloConfig类
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
lingniu:
|
||||
apollo:
|
||||
#namespaces必须配置,配置文件列表,以逗号分割
|
||||
namespaces: application,applicationTest
|
||||
# 是否开启 Apollo 配置,默认true
|
||||
enabled: true
|
||||
# appId 等于 spring.application.name,无需配置
|
||||
appId: lingniu-framework-demo
|
||||
# meta 地址,框架自动获取,无需配置
|
||||
meta: http://xxx:8180
|
||||
```
|
||||
|
||||
## 核心参数描述
|
||||
|
||||
- app.id:此参数未配置,默认采用:spring.application.name
|
||||
- framework.lingniu.apollo.meta: 为apollo的eurka注册地址
|
||||
- framework.lingniu.apollo.enabled:是否启用
|
||||
- framework.lingniu.apollo.namespaces 可配置多个配置文件
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-mybatis</artifactId>
|
||||
<name>lingniu-framework-plugin-mybatis</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<tk.mapper-spring-boot.version>4.2.0</tk.mapper-spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-typehandlers-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||
<version>${dynamic-datasource-starter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus-boot-starter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tk.mybatis</groupId>
|
||||
<artifactId>mapper-spring-boot-starter</artifactId>
|
||||
<version>${tk.mapper-spring-boot.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<groupId>org.mybatis</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,183 @@
|
||||
package cn.lingniu.framework.plugin.mybatis.base;
|
||||
|
||||
import cn.lingniu.framework.plugin.mybatis.config.DataSourceConfig;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||
import org.apache.ibatis.session.ResultHandler;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 输出SQL 语句及其执行时间
|
||||
*/
|
||||
@Slf4j
|
||||
@Intercepts({
|
||||
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
|
||||
@Signature(type = StatementHandler.class, method = "update", args = Statement.class),
|
||||
@Signature(type = StatementHandler.class, method = "batch", args = Statement.class)
|
||||
})
|
||||
@RequiredArgsConstructor
|
||||
public class DBSqlLogInterceptor implements Interceptor {
|
||||
private static final String DRUID_POOLED_PREPARED_STATEMENT = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
|
||||
private static final String T4C_PREPARED_STATEMENT = "oracle.jdbc.driver.T4CPreparedStatement";
|
||||
private static final String ORACLE_PREPARED_STATEMENT_WRAPPER = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
|
||||
private Method oracleGetOriginalSqlMethod;
|
||||
private Method druidGetSqlMethod;
|
||||
private final DataSourceConfig dataSourceConfig;
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
if (!dataSourceConfig.isSqlLog()) {
|
||||
return invocation.proceed();
|
||||
}
|
||||
Statement statement;
|
||||
Object firstArg = invocation.getArgs()[0];
|
||||
if (Proxy.isProxyClass(firstArg.getClass())) {
|
||||
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
|
||||
} else {
|
||||
statement = (Statement) firstArg;
|
||||
}
|
||||
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
|
||||
try {
|
||||
statement = (Statement) stmtMetaObj.getValue("stmt.statement");
|
||||
} catch (Exception e) {
|
||||
// do nothing
|
||||
}
|
||||
if (stmtMetaObj.hasGetter("delegate")) {//Hikari
|
||||
try {
|
||||
statement = (Statement) stmtMetaObj.getValue("delegate");
|
||||
} catch (Exception ignored) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
String originalSql = null;
|
||||
String stmtClassName = statement.getClass().getName();
|
||||
if (DRUID_POOLED_PREPARED_STATEMENT.equals(stmtClassName)) {
|
||||
try {
|
||||
if (druidGetSqlMethod == null) {
|
||||
Class<?> clazz = Class.forName(DRUID_POOLED_PREPARED_STATEMENT);
|
||||
druidGetSqlMethod = clazz.getMethod("getSql");
|
||||
}
|
||||
Object stmtSql = druidGetSqlMethod.invoke(statement);
|
||||
if (stmtSql instanceof String) {
|
||||
originalSql = (String) stmtSql;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (T4C_PREPARED_STATEMENT.equals(stmtClassName)
|
||||
|| ORACLE_PREPARED_STATEMENT_WRAPPER.equals(stmtClassName)) {
|
||||
try {
|
||||
if (oracleGetOriginalSqlMethod != null) {
|
||||
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
|
||||
if (stmtSql instanceof String) {
|
||||
originalSql = (String) stmtSql;
|
||||
}
|
||||
} else {
|
||||
Class<?> clazz = Class.forName(stmtClassName);
|
||||
oracleGetOriginalSqlMethod = getMethodRegular(clazz, "getOriginalSql");
|
||||
if (oracleGetOriginalSqlMethod != null) {
|
||||
//OraclePreparedStatementWrapper is not a public class, need set this.
|
||||
oracleGetOriginalSqlMethod.setAccessible(true);
|
||||
if (null != oracleGetOriginalSqlMethod) {
|
||||
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
|
||||
if (stmtSql instanceof String) {
|
||||
originalSql = (String) stmtSql;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
if (originalSql == null) {
|
||||
originalSql = statement.toString();
|
||||
}
|
||||
originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE);
|
||||
int index = indexOfSqlStart(originalSql);
|
||||
if (index > 0) {
|
||||
originalSql = originalSql.substring(index);
|
||||
}
|
||||
long start = SystemClock.now();
|
||||
Object result = invocation.proceed();
|
||||
long timing = SystemClock.now() - start;
|
||||
Object target = PluginUtils.realTarget(invocation.getTarget());
|
||||
MetaObject metaObject = SystemMetaObject.forObject(target);
|
||||
MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||
// 打印 sql
|
||||
String sqlLogger = "\nsql日志执行日志:开始==============" +
|
||||
"\nExecute ID :{}" +
|
||||
"\nExecute SQL :{}" +
|
||||
"\nExecute Time:{} ms" +
|
||||
"\nsql日志执行日志:结束 ==============\n";
|
||||
if (timing > dataSourceConfig.getSqlErrorTimeOut()){
|
||||
log.error(sqlLogger, ms.getId(), originalSql, timing);
|
||||
} else if (log.isInfoEnabled()) {
|
||||
log.info(sqlLogger, ms.getId(), originalSql, timing);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof StatementHandler) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
}
|
||||
|
||||
private Method getMethodRegular(Class<?> clazz, String methodName) {
|
||||
if (Object.class.equals(clazz)) {
|
||||
return null;
|
||||
}
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (method.getName().equals(methodName)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return getMethodRegular(clazz.getSuperclass(), methodName);
|
||||
}
|
||||
|
||||
private int indexOfSqlStart(String sql) {
|
||||
String upperCaseSql = sql.toUpperCase();
|
||||
Set<Integer> set = new HashSet<>();
|
||||
set.add(upperCaseSql.indexOf("SELECT "));
|
||||
set.add(upperCaseSql.indexOf("UPDATE "));
|
||||
set.add(upperCaseSql.indexOf("INSERT "));
|
||||
set.add(upperCaseSql.indexOf("DELETE "));
|
||||
set.remove(-1);
|
||||
if (CollectionUtils.isEmpty(set)) {
|
||||
return -1;
|
||||
}
|
||||
List<Integer> list = new ArrayList<>(set);
|
||||
list.sort(Comparator.naturalOrder());
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.lingniu.framework.plugin.mybatis.base;
|
||||
|
||||
/**
|
||||
* 对应于多数据源中不同数据源配置
|
||||
* <p>
|
||||
* 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。
|
||||
* 注意,默认是 {@link #MASTER} 数据源
|
||||
* <p>
|
||||
* 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
|
||||
*/
|
||||
public interface DataSourceEnum {
|
||||
|
||||
/**
|
||||
* 主库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解
|
||||
*/
|
||||
String MASTER = "master";
|
||||
/**
|
||||
* 从库,推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解
|
||||
*/
|
||||
String SLAVE = "slave";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.lingniu.framework.plugin.mybatis.base;
|
||||
|
||||
import com.alibaba.druid.util.Utils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Druid 底部广告过滤器
|
||||
*
|
||||
*/
|
||||
public class DruidAdRemoveFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* common.js 的路径
|
||||
*/
|
||||
private static final String COMMON_JS_ILE_PATH = "support/http/resources/js/common.js";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
chain.doFilter(request, response);
|
||||
// 重置缓冲区,响应头不会被重置
|
||||
response.resetBuffer();
|
||||
// 获取 common.js
|
||||
String text = Utils.readFromResource(COMMON_JS_ILE_PATH);
|
||||
// 正则替换 banner, 除去底部的广告信息
|
||||
text = text.replaceAll("<a.*?banner\"></a><br/>", "");
|
||||
text = text.replaceAll("powered.*?shrek.wang</a>", "");
|
||||
response.getWriter().write(text);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.lingniu.framework.plugin.mybatis.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 数据源配置--mybatis, mybatis-plus, dynamic-datasource, tk-mapper 配置参数整合
|
||||
**/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = DataSourceConfig.PRE_FIX)
|
||||
public class DataSourceConfig {
|
||||
|
||||
public static final String PRE_FIX = "framework.lingniu.datasource";
|
||||
|
||||
/**
|
||||
* sql分析日志打印, 默认关闭
|
||||
*/
|
||||
private boolean sqlLog = false;
|
||||
/**
|
||||
* druid 账号, 默认 SpringApplicationConfig#name
|
||||
*/
|
||||
private String druidUsername;
|
||||
/**
|
||||
* druid 密码, 默认 SpringApplicationConfig#name + 123
|
||||
*/
|
||||
private String druidPassword;
|
||||
|
||||
/**
|
||||
* 超过3s 的sql 打印error日志
|
||||
*/
|
||||
private Long sqlErrorTimeOut = 3000l;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.lingniu.framework.plugin.mybatis.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.mybatis.base.DBSqlLogInterceptor;
|
||||
import cn.lingniu.framework.plugin.mybatis.base.DruidAdRemoveFilter;
|
||||
import cn.lingniu.framework.plugin.mybatis.config.DataSourceConfig;
|
||||
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
|
||||
import com.alibaba.druid.support.jakarta.ResourceServlet;
|
||||
import com.alibaba.druid.support.jakarta.StatViewServlet;
|
||||
import com.alibaba.druid.support.jakarta.WebStatFilter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({DataSourceConfig.class})
|
||||
public class DataSourceAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DBSqlLogInterceptor sqlLogInterceptor(DataSourceConfig dataSourceConfig) {
|
||||
return new DBSqlLogInterceptor(dataSourceConfig);
|
||||
}
|
||||
|
||||
@Bean(name = "druidStatView")
|
||||
public ServletRegistrationBean druidStatView(DataSourceConfig dataSourceConfig) {
|
||||
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
|
||||
registrationBean.addInitParameter(ResourceServlet.PARAM_NAME_ALLOW, "");
|
||||
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_DENY, "");
|
||||
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_USERNAME, Optional.ofNullable(dataSourceConfig.getDruidUsername()).orElseGet(()-> ApplicationNameContext.getApplicationName()));
|
||||
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_PASSWORD, Optional.ofNullable(dataSourceConfig.getDruidPassword()).orElseGet(() -> ApplicationNameContext.getApplicationName() + "123"));
|
||||
registrationBean.addInitParameter(StatViewServlet.PARAM_NAME_RESET_ENABLE, "false");
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
@Bean(name = "druidWebStatFilter")
|
||||
public FilterRegistrationBean druidWebStatFilter() {
|
||||
FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(new WebStatFilter());
|
||||
registrationBean.addUrlPatterns("/*");
|
||||
registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建 DruidAdRemoveFilter 过滤器,过滤 common.js 的广告
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
|
||||
public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {
|
||||
// 获取 druid web 监控页面的参数
|
||||
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
|
||||
// 提取 common.js 的配置路径
|
||||
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
|
||||
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
|
||||
// 创建 DruidAdRemoveFilter Bean
|
||||
FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new DruidAdRemoveFilter());
|
||||
registrationBean.addUrlPatterns(commonJsPattern);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.lingniu.framework.plugin.mybatis.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* 数据源属性初始化--处理监控开关
|
||||
*/
|
||||
@Order(Integer.MIN_VALUE + 100)
|
||||
public class DataSourceInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
PropertyUtils.setDefaultInitProperty("management.health.db.enabled", "false");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
cn.lingniu.framework.plugin.mybatis.init.DataSourceInit
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.mybatis.init.DataSourceAutoConfiguration
|
||||
@@ -0,0 +1,65 @@
|
||||
# 【重要】基于 Mybatis-Plus + dynamic-datasource + tk 详细使用参考官网---参考官网
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 基于 Mybatis-Plus + dynamic-datasource + tk 封装的数据源交互工具
|
||||
2. 核心能力
|
||||
* 数据源CRUD操作,支持mysql, oracle, clickhouse 等
|
||||
* 多数据源支持
|
||||
* 分页查询
|
||||
* Sql语句执行分析
|
||||
* 默认集成druid
|
||||
3. 适用场景:
|
||||
* 依赖外部数据源场景
|
||||
* 关系型数据库场景
|
||||
|
||||
## 如何配置--更多参数参考:RedissonConfig/RedissonProperties
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
application:
|
||||
name: lingniu-framework-demo
|
||||
profiles:
|
||||
active: dev,redisson,jetcache,xxljob,db
|
||||
#todo db配置
|
||||
datasource:
|
||||
dynamic:
|
||||
primary: master
|
||||
datasource:
|
||||
#如需连接mysql,需修改下面的配置
|
||||
master:
|
||||
driver-class-name: org.h2.Driver
|
||||
url: jdbc:h2:mem:testdb
|
||||
username: sa
|
||||
password: sa
|
||||
slaver:
|
||||
driver-class-name: org.h2.Driver
|
||||
url: jdbc:h2:mem:testdb
|
||||
username: sa
|
||||
password: sa
|
||||
# http://localhost:8080/h2-console 访问 h2 数据库 账号: sa/sa
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
path: /h2-console
|
||||
framework:
|
||||
lingniu:
|
||||
# 框架数据源配置
|
||||
datasource:
|
||||
# 开启框架 sql执行分析
|
||||
sqlLog: true
|
||||
# druid 后台地址:http://localhost:8080/druid/login.html
|
||||
# druid 账号,默认值为 SpringApplicationProperties#name
|
||||
druidUsername: "your_druid_username"
|
||||
# druid 密码,默认值为 SpringApplicationProperties#name + "123"
|
||||
druidPassword: "your_druid_password"
|
||||
```
|
||||
|
||||
## druid 配置请参考:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
|
||||
|
||||
## 如何使用--Mybatis基本功能
|
||||
|
||||
1. 基本功能使用可参考Mybatis-Plus: https://www.baomidou.com
|
||||
2. 默认配置下 在 src/main/resources/mapper/ 文件夹下定义 xml 文件
|
||||
3. 定义 java 接口 继承 com.baomidou.mybatisplus.core.mapper.BaseMapper
|
||||
4. 使用@DS 注解实现数据源切换:@DS("master")
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-easyexcel</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,3 @@
|
||||
# easyexcel * easyexcel 性能好,api简单,上手更简单
|
||||
|
||||
# https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-xxljob</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xuxueli</groupId>
|
||||
<artifactId>xxl-job-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,64 @@
|
||||
package cn.lingniu.framework.plugin.xxljob.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(XxlJobConfig.PRE_FIX)
|
||||
public class XxlJobConfig {
|
||||
|
||||
public final static String PRE_FIX = "framework.lingniu.xxljob";
|
||||
|
||||
/**
|
||||
* xxl job admin properties.
|
||||
*/
|
||||
private AdminProperties admin = new AdminProperties();
|
||||
/**
|
||||
* xxl job executor properties.
|
||||
*/
|
||||
private ExecutorProperties executor = new ExecutorProperties();
|
||||
|
||||
|
||||
/**
|
||||
* xxl-job admin properties.
|
||||
*/
|
||||
@Data
|
||||
static public class AdminProperties {
|
||||
/**
|
||||
* xxl job admin address.
|
||||
*/
|
||||
private String adminAddresses = "http://xxx:8080/xxl-job-admin";
|
||||
/**
|
||||
* xxl job admin registry access token.
|
||||
*/
|
||||
private String accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* xxl-job executor properties.
|
||||
*/
|
||||
@Data
|
||||
static public class ExecutorProperties {
|
||||
/**
|
||||
* xxl job registry name. [等于 spring.application.name] ApplicationNameContext.getApplicationName()
|
||||
*/
|
||||
private String appName = "xxl-job-executor";
|
||||
/**
|
||||
* xxl job registry ip.
|
||||
*/
|
||||
private String ip;
|
||||
/**
|
||||
* xxl job registry port.
|
||||
*/
|
||||
private Integer port = 9999;
|
||||
/**
|
||||
* xxl job log files path. todo 注意权限问题
|
||||
*/
|
||||
private String logPath = "logs/applogs/xxl-job/jobhandler";
|
||||
/**
|
||||
* xxl job log files retention days.
|
||||
*/
|
||||
private Integer logRetentionDays = 7;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.lingniu.framework.plugin.xxljob.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.util.json.JsonUtil;
|
||||
import cn.lingniu.framework.plugin.xxljob.config.XxlJobConfig;
|
||||
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@EnableConfigurationProperties({XxlJobConfig.class})
|
||||
public class XxlJobAutoConfiguration {
|
||||
|
||||
@Bean(initMethod = "start", destroyMethod = "destroy")
|
||||
public XxlJobSpringExecutor xxlJobSpringExecutor(XxlJobConfig prop) {
|
||||
validateXxlJobConfig(prop);
|
||||
|
||||
XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
|
||||
executor.setAdminAddresses(prop.getAdmin().getAdminAddresses());
|
||||
executor.setAccessToken(prop.getAdmin().getAccessToken());
|
||||
|
||||
executor.setPort(prop.getExecutor().getPort());
|
||||
executor.setLogRetentionDays(prop.getExecutor().getLogRetentionDays());
|
||||
executor.setLogPath(prop.getExecutor().getLogPath());
|
||||
executor.setIp(prop.getExecutor().getIp());
|
||||
executor.setAppname(ApplicationNameContext.getApplicationName());
|
||||
|
||||
log.info("XxlJob configuration initialized: {}", JsonUtil.bean2Json(prop));
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void validateXxlJobConfig(XxlJobConfig prop) {
|
||||
if (prop.getAdmin() == null || prop.getExecutor() == null) {
|
||||
throw new IllegalArgumentException("XxlJob configuration cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.lingniu.framework.plugin.xxljob.init.XxlJobAutoConfiguration
|
||||
@@ -0,0 +1,43 @@
|
||||
# 【重要】xxl-job服务搭建、和详细demo使用教程---参考官网
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 定位: 基于 xxljob 封装的分布式定时任务开发工具
|
||||
2. 核心能力:
|
||||
* 分布式任务调度:基于 XXL-JOB 实现任务的分布式执行和分片处理。
|
||||
* 动态调度:支持任务的动态添加、修改和删除,无需重启服务。
|
||||
* 故障转移:自动检测任务执行失败并重新分配,确保任务高可用。
|
||||
* 监控与管理:提供任务执行日志、状态监控和告警功能。
|
||||
* 插件化集成:通过插件机制与主框架无缝对接,支持动态加载和卸载。
|
||||
3. 适用场景:
|
||||
* 定时任务:如报表生成、数据清理等周期性任务。
|
||||
* 分布式计算:需要分片处理大量数据的场景。
|
||||
* 高可用需求:确保任务在节点故障时自动恢复。
|
||||
* 动态调整:需要灵活调整任务执行策略的场景。
|
||||
|
||||
## 如何配置--参考:XxlJobConfig
|
||||
|
||||
```yaml
|
||||
|
||||
framework:
|
||||
lingniu:
|
||||
# XXL-JOB 配置
|
||||
xxljob:
|
||||
# 调度中心配置
|
||||
admin:
|
||||
# 调度中心部署根地址(多个地址用逗号分隔,为空则关闭自动注册)
|
||||
adminAddresses: "http://xxx:8099/xxl-job-admin"
|
||||
# 调度中心通讯TOKEN(非空时启用)
|
||||
accessToken: "default_token"
|
||||
|
||||
# 执行器配置
|
||||
executor:
|
||||
# 执行器IP(默认为空表示自动获取IP)
|
||||
ip: ""
|
||||
# 执行器端口号(小于等于0则自动获取,默认9999)
|
||||
port: 9999
|
||||
# 执行器日志文件保存天数(大于等于3时生效,-1表示关闭自动清理,默认7天)
|
||||
logRetentionDays: 7
|
||||
# 执行器运行日志文件存储磁盘路径(需对该路径拥有读写权限,为空则使用默认路径)
|
||||
logPath: logs/applogs/xxl-job/jobhandler
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.db4j</groupId>
|
||||
<artifactId>reflectasm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,121 @@
|
||||
package cn.lingniu.framework.plugin.core.base;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.lingniu.framework.plugin.core.exception.ErrorCode;
|
||||
import cn.lingniu.framework.plugin.core.exception.ServiceException;
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.lingniu.framework.plugin.core.exception.util.ServiceExceptionUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 通用返回
|
||||
*
|
||||
* @param <T> 数据泛型
|
||||
*/
|
||||
@Data
|
||||
public class CommonResult<T> implements Serializable {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*
|
||||
* @see ErrorCode#getCode()
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示,用户可阅读
|
||||
*
|
||||
* @see ErrorCode#getMsg() ()
|
||||
*/
|
||||
private String msg;
|
||||
/**
|
||||
* 返回数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
||||
*
|
||||
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
|
||||
*
|
||||
* @param result 传入的 result 对象
|
||||
* @param <T> 返回的泛型
|
||||
* @return 新的 CommonResult 对象
|
||||
*/
|
||||
public static <T> CommonResult<T> error(CommonResult<?> result) {
|
||||
return error(result.getCode(), result.getMsg());
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(Integer code, String message) {
|
||||
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = code;
|
||||
result.msg = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
|
||||
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = errorCode.getCode();
|
||||
result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(ErrorCode errorCode) {
|
||||
return error(errorCode.getCode(), errorCode.getMsg());
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
|
||||
result.data = data;
|
||||
result.msg = "";
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isSuccess(Integer code) {
|
||||
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isSuccess() {
|
||||
return isSuccess(code);
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isError() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
// ========= 和 Exception 异常体系集成 =========
|
||||
|
||||
/**
|
||||
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
|
||||
*/
|
||||
public void checkError() throws ServiceException {
|
||||
if (isSuccess()) {
|
||||
return;
|
||||
}
|
||||
// 业务异常
|
||||
throw new ServiceException(code, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
|
||||
* 如果没有,则返回 {@link #data} 数据
|
||||
*/
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public T getCheckedData() {
|
||||
checkError();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(ServiceException serviceException) {
|
||||
return error(serviceException.getCode(), serviceException.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.lingniu.framework.plugin.core.base;
|
||||
|
||||
import cn.lingniu.framework.plugin.util.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 终端的枚举
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum TerminalEnum implements ArrayValuable<Integer> {
|
||||
|
||||
UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
|
||||
WECHAT_MINI_PROGRAM(10, "微信小程序"),
|
||||
WECHAT_WAP(11, "微信公众号"),
|
||||
H5(20, "H5 网页"),
|
||||
APP(31, "手机 App"),
|
||||
;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TerminalEnum::getTerminal).toArray(Integer[]::new);
|
||||
|
||||
/**
|
||||
* 终端
|
||||
*/
|
||||
private final Integer terminal;
|
||||
/**
|
||||
* 终端名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.lingniu.framework.plugin.core.base;
|
||||
|
||||
/**
|
||||
* Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
|
||||
*
|
||||
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
|
||||
*
|
||||
*/
|
||||
public interface WebFilterOrderEnum {
|
||||
|
||||
int CORS_FILTER = Integer.MIN_VALUE;
|
||||
|
||||
int TRACE_FILTER = CORS_FILTER + 1;
|
||||
|
||||
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
|
||||
|
||||
int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1;
|
||||
|
||||
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
|
||||
|
||||
int FAVICON_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
|
||||
|
||||
int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
|
||||
|
||||
int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面
|
||||
|
||||
// Spring Security Filter 默认为 -100,可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类
|
||||
|
||||
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
|
||||
|
||||
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
|
||||
|
||||
int DEMO_FILTER = Integer.MAX_VALUE;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.lingniu.framework.plugin.core.config;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@UtilityClass
|
||||
public class CommonConstant {
|
||||
//Spring 应用名
|
||||
public static final String SPRING_APP_NAME_KEY = "spring.application.name";
|
||||
//active profiles
|
||||
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.lingniu.framework.plugin.core.context;
|
||||
|
||||
public abstract class ApplicationNameContext {
|
||||
|
||||
private static String applicationName;
|
||||
|
||||
public static String getApplicationName() {
|
||||
return applicationName;
|
||||
}
|
||||
|
||||
public static void setApplicationName(String applicationName) {
|
||||
ApplicationNameContext.applicationName = applicationName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.lingniu.framework.plugin.core.context;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.SpringBootVersion;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* spring bean 的所有bean存储
|
||||
*/
|
||||
@Slf4j
|
||||
public class SpringBeanApplicationContext implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext = null;
|
||||
|
||||
private static final String SPRING_BOOT_VERSION = SpringBootVersion.getVersion();
|
||||
|
||||
|
||||
private static String applicationName;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpringBeanApplicationContext.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static Object getBean(String beanName) throws BeansException {
|
||||
return applicationContext.getBean(beanName);
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> clazz) throws BeansException {
|
||||
return applicationContext.getBean(clazz);
|
||||
}
|
||||
|
||||
public static String getSpringBootVersion() {
|
||||
return SPRING_BOOT_VERSION;
|
||||
}
|
||||
|
||||
public static <T> ObjectProvider<T> getBeanProvider(Class<T> clazz) throws BeansException {
|
||||
return applicationContext.getBeanProvider(clazz);
|
||||
}
|
||||
|
||||
public static <T> T getBean(String beanName, Class<T> clazz) throws BeansException {
|
||||
return applicationContext.getBean(beanName, clazz);
|
||||
}
|
||||
|
||||
public static <T> Map<String, T> getBeans(Class<T> clazz) throws BeansException {
|
||||
return applicationContext.getBeansOfType(clazz);
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.lingniu.framework.plugin.core.exception;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.ServiceErrorCodeRange;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 错误码对象
|
||||
*
|
||||
* 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
|
||||
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
|
||||
*/
|
||||
@Data
|
||||
public class ErrorCode {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private final String msg;
|
||||
|
||||
public ErrorCode(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.msg = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.lingniu.framework.plugin.core.exception;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 服务器异常 Exception
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class ServerException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 全局错误码
|
||||
*
|
||||
* @see GlobalErrorCodeConstants
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 空构造方法,避免反序列化问题
|
||||
*/
|
||||
public ServerException() {
|
||||
}
|
||||
|
||||
public ServerException(ErrorCode errorCode) {
|
||||
this.code = errorCode.getCode();
|
||||
this.message = errorCode.getMsg();
|
||||
}
|
||||
|
||||
public ServerException(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public ServerException setCode(Integer code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public ServerException setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.lingniu.framework.plugin.core.exception;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.ServiceErrorCodeRange;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 业务逻辑异常 Exception
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class ServiceException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 业务错误码
|
||||
*
|
||||
* @see ServiceErrorCodeRange
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 空构造方法,避免反序列化问题
|
||||
*/
|
||||
public ServiceException() {
|
||||
}
|
||||
|
||||
public ServiceException(ErrorCode errorCode) {
|
||||
this.code = errorCode.getCode();
|
||||
this.message = errorCode.getMsg();
|
||||
}
|
||||
|
||||
public ServiceException(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public ServiceException setCode(Integer code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public ServiceException setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.lingniu.framework.plugin.core.exception.enums;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.core.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 全局错误码枚举
|
||||
* 0-999 系统异常编码保留
|
||||
*
|
||||
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
|
||||
* 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
|
||||
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
|
||||
*/
|
||||
public interface GlobalErrorCodeConstants {
|
||||
|
||||
ErrorCode SUCCESS = new ErrorCode(0, "成功");
|
||||
|
||||
// ========== 客户端错误段 ==========
|
||||
|
||||
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
|
||||
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
|
||||
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
|
||||
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
|
||||
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
|
||||
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
|
||||
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
|
||||
ErrorCode TIME_OUT_ERROR = new ErrorCode(430, "请求超时!");
|
||||
|
||||
// ========== 服务端错误段 ==========
|
||||
|
||||
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
|
||||
ErrorCode BAD_GATE_WAY_ERROR = new ErrorCode(502, "服务端Bad Gateway异常");
|
||||
|
||||
// ========== 自定义错误段 ==========
|
||||
|
||||
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
|
||||
ErrorCode FEIGN_DECODE_ERROR = new ErrorCode(900, "Feign异常解码错误");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.lingniu.framework.plugin.core.exception.enums;
|
||||
|
||||
/**
|
||||
* todo 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
|
||||
* 抽象类,业务可自定义继承
|
||||
*/
|
||||
public abstract class ServiceErrorCodeRange {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.lingniu.framework.plugin.core.exception.util;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.exception.ErrorCode;
|
||||
import cn.lingniu.framework.plugin.core.exception.ServiceException;
|
||||
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* {@link ServiceException} 工具类
|
||||
*
|
||||
* 目的在于,格式化异常信息提示。
|
||||
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServiceExceptionUtil {
|
||||
|
||||
// ========== 和 ServiceException 的集成 ==========
|
||||
|
||||
public static ServiceException exception(ErrorCode errorCode) {
|
||||
return exception0(errorCode.getCode(), errorCode.getMsg());
|
||||
}
|
||||
|
||||
public static ServiceException exception(ErrorCode errorCode, Object... params) {
|
||||
return exception0(errorCode.getCode(), errorCode.getMsg(), params);
|
||||
}
|
||||
|
||||
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
|
||||
String message = doFormat(code, messagePattern, params);
|
||||
return new ServiceException(code, message);
|
||||
}
|
||||
|
||||
public static ServiceException invalidParamException(String messagePattern, Object... params) {
|
||||
return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);
|
||||
}
|
||||
|
||||
// ========== 格式化方法 ==========
|
||||
|
||||
/**
|
||||
* 将错误编号对应的消息使用 params 进行格式化。
|
||||
*
|
||||
* @param code 错误编号
|
||||
* @param messagePattern 消息模版
|
||||
* @param params 参数
|
||||
* @return 格式化后的提示
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static String doFormat(int code, String messagePattern, Object... params) {
|
||||
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
|
||||
int i = 0;
|
||||
int j;
|
||||
int l;
|
||||
for (l = 0; l < params.length; l++) {
|
||||
j = messagePattern.indexOf("{}", i);
|
||||
if (j == -1) {
|
||||
log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||
if (i == 0) {
|
||||
return messagePattern;
|
||||
} else {
|
||||
sbuf.append(messagePattern.substring(i));
|
||||
return sbuf.toString();
|
||||
}
|
||||
} else {
|
||||
sbuf.append(messagePattern, i, j);
|
||||
sbuf.append(params[l]);
|
||||
i = j + 2;
|
||||
}
|
||||
}
|
||||
if (messagePattern.indexOf("{}", i) != -1) {
|
||||
log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||
}
|
||||
sbuf.append(messagePattern.substring(i));
|
||||
return sbuf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.lingniu.framework.plugin.core.init;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.config.CommonConstant;
|
||||
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
* 优先级最高
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class FrameworkSystemInitContextInitializer implements EnvironmentPostProcessor, Ordered {
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
private final static String APPLICATION_NAME = CommonConstant.SPRING_APP_NAME_KEY;
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
String applicationName = environment.getProperty(APPLICATION_NAME, "");
|
||||
if (ObjectEmptyUtils.isEmpty(applicationName)) {
|
||||
throw new IllegalArgumentException("[框架异常][" + CommonConstant.SPRING_APP_NAME_KEY + "] is not set-必须配置!");
|
||||
} else {
|
||||
ApplicationNameContext.setApplicationName(applicationName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE - 100000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.lingniu.framework.plugin.core.init.applicationContext;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.SpringBeanApplicationContext;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
|
||||
public class SpringApplicationContextCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
return SpringBeanApplicationContext.getApplicationContext() == null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.lingniu.framework.plugin.core.init.applicationContext;
|
||||
|
||||
import cn.lingniu.framework.plugin.core.context.SpringBeanApplicationContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
@Configuration
|
||||
public class SpringApplicationContextConfiguration implements EnvironmentAware {
|
||||
|
||||
@Bean
|
||||
@Conditional(SpringApplicationContextCondition.class)
|
||||
public SpringBeanApplicationContext springApplicationContext(ApplicationContext applicationContext) {
|
||||
SpringBeanApplicationContext context = new SpringBeanApplicationContext();
|
||||
context.setApplicationContext(applicationContext);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment event) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
cn.lingniu.framework.plugin.core.init.FrameworkSystemInitContextInitializer
|
||||
|
||||
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.lingniu.framework.plugin.core.init.applicationContext.SpringApplicationContextConfiguration
|
||||
@@ -0,0 +1,7 @@
|
||||
# 框架核心模块
|
||||
|
||||
## ApplicationNameContext 初始化
|
||||
|
||||
## 异常定义
|
||||
|
||||
## 等
|
||||
109
lingniu-framework-plugin/lingniu-framework-plugin-util/pom.xml
Normal file
109
lingniu-framework-plugin/lingniu-framework-plugin-util/pom.xml
Normal file
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-util</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.lingniu.framework.plugin.util.cache;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Cache 工具类
|
||||
*/
|
||||
public class CacheUtils {
|
||||
|
||||
/**
|
||||
* 异步刷新的 LoadingCache 最大缓存数量
|
||||
*
|
||||
* @see <a href="">本地缓存 CacheUtils 工具类建议</a>
|
||||
*/
|
||||
private static final Integer CACHE_MAX_SIZE = 10000;
|
||||
|
||||
/**
|
||||
* 构建异步刷新的 LoadingCache 对象
|
||||
*
|
||||
* 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||
*
|
||||
* 或者简单理解:
|
||||
* 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||
* 2、和“全局”、“系统”相关的,使用当前缓存方法
|
||||
*
|
||||
* @param duration 过期时间
|
||||
* @param loader CacheLoader 对象
|
||||
* @return LoadingCache 对象
|
||||
*/
|
||||
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
|
||||
return CacheBuilder.newBuilder()
|
||||
.maximumSize(CACHE_MAX_SIZE)
|
||||
// 只阻塞当前数据加载线程,其他线程返回旧值
|
||||
.refreshAfterWrite(duration)
|
||||
// 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
|
||||
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建同步刷新的 LoadingCache 对象
|
||||
*
|
||||
* @param duration 过期时间
|
||||
* @param loader CacheLoader 对象
|
||||
* @return LoadingCache 对象
|
||||
*/
|
||||
public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
|
||||
return CacheBuilder.newBuilder()
|
||||
.maximumSize(CACHE_MAX_SIZE)
|
||||
// 只阻塞当前数据加载线程,其他线程返回旧值
|
||||
.refreshAfterWrite(duration)
|
||||
.build(loader);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.lingniu.framework.plugin.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.IterUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Array 工具类
|
||||
*/
|
||||
public class ArrayUtils {
|
||||
|
||||
/**
|
||||
* 将 object 和 newElements 合并成一个数组
|
||||
*
|
||||
* @param object 对象
|
||||
* @param newElements 数组
|
||||
* @param <T> 泛型
|
||||
* @return 结果数组
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
|
||||
if (object == null) {
|
||||
return newElements;
|
||||
}
|
||||
Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
|
||||
result[0] = object;
|
||||
System.arraycopy(newElements, 0, result, 1, newElements.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {
|
||||
return toArray(CollectionUtils.convertList(from, mapper));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] toArray(Collection<T> from) {
|
||||
if (CollectionUtil.isEmpty(from)) {
|
||||
return (T[]) (new Object[0]);
|
||||
}
|
||||
return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
|
||||
}
|
||||
|
||||
public static <T> T get(T[] array, int index) {
|
||||
if (null == array || index >= array.length) {
|
||||
return null;
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
package cn.lingniu.framework.plugin.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static cn.hutool.core.convert.Convert.toCollection;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* Collection 工具类
|
||||
*/
|
||||
public class CollectionUtils {
|
||||
|
||||
public static boolean containsAny(Object source, Object... targets) {
|
||||
return asList(targets).contains(source);
|
||||
}
|
||||
|
||||
public static boolean isAnyEmpty(Collection<?>... collections) {
|
||||
return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
|
||||
}
|
||||
|
||||
public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
|
||||
return from.stream().anyMatch(predicate);
|
||||
}
|
||||
|
||||
public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return distinct(from, keyMapper, (t1, t2) -> t1);
|
||||
}
|
||||
|
||||
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
|
||||
if (ArrayUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return convertList(Arrays.asList(from), func);
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
|
||||
Function<T, ? extends Stream<? extends U>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
|
||||
Function<? super T, ? extends U> mapper,
|
||||
Function<U, ? extends Stream<? extends R>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
|
||||
return map.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T> Set<T> convertSet(Collection<T> from) {
|
||||
return convertSet(from, v -> v);
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
|
||||
Function<T, ? extends Stream<? extends U>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
|
||||
Function<? super T, ? extends U> mapper,
|
||||
Function<U, ? extends Stream<? extends R>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, Function.identity());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return supplier.get();
|
||||
}
|
||||
return convertMap(from, keyFunc, Function.identity(), supplier);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return supplier.get();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream()
|
||||
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
|
||||
}
|
||||
|
||||
// 暂时没想好名字,先以 2 结尾噶
|
||||
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
|
||||
from.forEach(item -> builder.put(keyFunc.apply(item), item));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比老、新两个列表,找出新增、修改、删除的数据
|
||||
*
|
||||
* @param oldList 老列表
|
||||
* @param newList 新列表
|
||||
* @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
|
||||
* 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
|
||||
* @return [新增列表、修改列表、删除列表]
|
||||
*/
|
||||
public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
|
||||
BiFunction<T, T, Boolean> sameFunc) {
|
||||
List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
|
||||
List<T> updateList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
|
||||
for (T oldObj : oldList) {
|
||||
// 1. 寻找是否有匹配的
|
||||
T foundObj = null;
|
||||
for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
|
||||
T newObj = iterator.next();
|
||||
// 1.1 不匹配,则直接跳过
|
||||
if (!sameFunc.apply(oldObj, newObj)) {
|
||||
continue;
|
||||
}
|
||||
// 1.2 匹配,则移除,并结束寻找
|
||||
iterator.remove();
|
||||
foundObj = newObj;
|
||||
break;
|
||||
}
|
||||
// 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
|
||||
if (foundObj != null) {
|
||||
updateList.add(foundObj);
|
||||
} else {
|
||||
deleteList.add(oldObj);
|
||||
}
|
||||
}
|
||||
return asList(createList, updateList, deleteList);
|
||||
}
|
||||
|
||||
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
|
||||
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
|
||||
}
|
||||
|
||||
public static <T> T getFirst(List<T> from) {
|
||||
return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
|
||||
}
|
||||
|
||||
public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
|
||||
return findFirst(from, predicate, Function.identity());
|
||||
}
|
||||
|
||||
public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
return from.stream().filter(predicate).findFirst().map(func).orElse(null);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert !from.isEmpty(); // 断言,避免告警
|
||||
T t = from.stream().max(Comparator.comparing(valueFunc)).get();
|
||||
return valueFunc.apply(t);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert from.size() > 0; // 断言,避免告警
|
||||
T t = from.stream().min(Comparator.comparing(valueFunc)).get();
|
||||
return valueFunc.apply(t);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert from.size() > 0; // 断言,避免告警
|
||||
return from.stream().min(Comparator.comparing(valueFunc)).get();
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
|
||||
BinaryOperator<V> accumulator) {
|
||||
return getSumValue(from, valueFunc, accumulator, null);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
|
||||
BinaryOperator<V> accumulator, V defaultValue) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return defaultValue;
|
||||
}
|
||||
assert !from.isEmpty(); // 断言,避免告警
|
||||
return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
|
||||
}
|
||||
|
||||
public static <T> void addIfNotNull(Collection<T> coll, T item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
coll.add(item);
|
||||
}
|
||||
|
||||
public static <T> Collection<T> singleton(T obj) {
|
||||
return obj == null ? Collections.emptyList() : Collections.singleton(obj);
|
||||
}
|
||||
|
||||
public static <T> List<T> newArrayList(List<List<T>> list) {
|
||||
return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 LinkedHashSet
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param elementType 集合中元素类型
|
||||
* @param value 被转换的值
|
||||
* @return {@link LinkedHashSet}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
|
||||
return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.lingniu.framework.plugin.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Map 工具类
|
||||
*/
|
||||
public class MapUtils {
|
||||
|
||||
/**
|
||||
* 从哈希表表中,获得 keys 对应的所有 value 数组
|
||||
*
|
||||
* @param multimap 哈希表
|
||||
* @param keys keys
|
||||
* @return value 数组
|
||||
*/
|
||||
public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {
|
||||
List<V> result = new ArrayList<>();
|
||||
keys.forEach(k -> {
|
||||
Collection<V> values = multimap.get(k);
|
||||
if (CollectionUtil.isEmpty(values)) {
|
||||
return;
|
||||
}
|
||||
result.addAll(values);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从哈希表查找到 key 对应的 value,然后进一步处理
|
||||
* key 为 null 时, 不处理
|
||||
* 注意,如果查找到的 value 为 null 时,不进行处理
|
||||
*
|
||||
* @param map 哈希表
|
||||
* @param key key
|
||||
* @param consumer 进一步处理的逻辑
|
||||
*/
|
||||
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
|
||||
if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
V value = map.get(key);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
consumer.accept(value);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.lingniu.framework.plugin.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Set 工具类
|
||||
*/
|
||||
public class SetUtils {
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> asSet(T... objs) {
|
||||
return CollUtil.newHashSet(objs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.lingniu.framework.plugin.util.config;
|
||||
|
||||
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
/**
|
||||
* 获取上下文 todo 得设置 setApplicationContext
|
||||
**/
|
||||
public class ContextUtils {
|
||||
public static Class MainClass;
|
||||
|
||||
private static ConfigurableApplicationContext applicationContext;
|
||||
|
||||
public static void setApplicationContext(ConfigurableApplicationContext applicationContext) {
|
||||
ContextUtils.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ConfigurableApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> type, boolean required) {
|
||||
if (type != null && applicationContext != null) {
|
||||
if (required) {
|
||||
return applicationContext.getBean(type);
|
||||
} else {
|
||||
if (applicationContext.getBeansOfType(type).size() > 0) {
|
||||
return applicationContext.getBean(type);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object getBean(String type, boolean required) {
|
||||
if (type != null && applicationContext != null) {
|
||||
if (required) {
|
||||
return applicationContext.getBean(type);
|
||||
} else {
|
||||
if (applicationContext.containsBean(type)) {
|
||||
return applicationContext.getBean(type);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ConfigurableWebServerApplicationContext getConfigurableWebServerApplicationContext() {
|
||||
ApplicationContext context = ContextUtils.getApplicationContext();
|
||||
if (context != null && context instanceof ConfigurableWebServerApplicationContext) {
|
||||
return (ConfigurableWebServerApplicationContext) context;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isWeb() {
|
||||
return getConfigurableWebServerApplicationContext() != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.lingniu.framework.plugin.util.config;
|
||||
|
||||
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
|
||||
public class PropertyUtils {
|
||||
public static <T> T getProperty(String key, T defaultvalue) {
|
||||
String value = System.getProperty(key);
|
||||
if (value == null) {
|
||||
value = System.getenv(key);
|
||||
}
|
||||
if (value == null && ContextUtils.getApplicationContext() != null) {
|
||||
value = ContextUtils.getApplicationContext().getEnvironment().getProperty(key);
|
||||
}
|
||||
if (value == null) {
|
||||
return defaultvalue;
|
||||
}
|
||||
return (T) convert(value, defaultvalue.getClass());
|
||||
}
|
||||
|
||||
public static <T> T convert(Object value, Class<T> type) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return (T) ApplicationConversionService.getSharedInstance().convert(value, type);
|
||||
}
|
||||
|
||||
public static Object getProperty(String key) {
|
||||
String value = System.getProperty(key);
|
||||
if (value == null) {
|
||||
value = System.getenv(key);
|
||||
}
|
||||
if (value == null && ContextUtils.getApplicationContext() != null) {
|
||||
value = ContextUtils.getApplicationContext().getEnvironment().getProperty(key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void setDefaultInitProperty(String key, String propertyValue) {
|
||||
System.setProperty(key, propertyValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.lingniu.framework.plugin.util.core;
|
||||
|
||||
/**
|
||||
* 可生成 T 数组的接口
|
||||
*/
|
||||
public interface ArrayValuable<T> {
|
||||
|
||||
/**
|
||||
* @return 数组
|
||||
*/
|
||||
T[] array();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.lingniu.framework.plugin.util.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Key Value 的键值对
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class KeyValue<K, V> implements Serializable {
|
||||
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.lingniu.framework.plugin.util.date;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.lingniu.framework.plugin.util.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 时间间隔的枚举
|
||||
*
|
||||
* @author dhb52
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DateIntervalEnum implements ArrayValuable<Integer> {
|
||||
|
||||
HOUR(0, "小时"), // 特殊:字典里,暂时不会有这个枚举!!!因为大多数情况下,用不到这个间隔
|
||||
DAY(1, "天"),
|
||||
WEEK(2, "周"),
|
||||
MONTH(3, "月"),
|
||||
QUARTER(4, "季度"),
|
||||
YEAR(5, "年")
|
||||
;
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DateIntervalEnum::getInterval).toArray(Integer[]::new);
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer interval;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static DateIntervalEnum valueOf(Integer interval) {
|
||||
return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package cn.lingniu.framework.plugin.util.date;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 时间工具类
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
/**
|
||||
* 时区 - 默认
|
||||
*/
|
||||
public static final String TIME_ZONE_DEFAULT = "GMT+8";
|
||||
|
||||
/**
|
||||
* 秒转换成毫秒
|
||||
*/
|
||||
public static final long SECOND_MILLIS = 1000;
|
||||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
|
||||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 将 LocalDateTime 转换成 Date
|
||||
*
|
||||
* @param date LocalDateTime
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static Date of(LocalDateTime date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
||||
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
||||
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return Date.from(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Date 转换成 LocalDateTime
|
||||
*
|
||||
* @param date Date
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static LocalDateTime of(Date date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
// 转为时间戳
|
||||
Instant instant = date.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public static Date addTime(Duration duration) {
|
||||
return new Date(System.currentTimeMillis() + duration.toMillis());
|
||||
}
|
||||
|
||||
public static boolean isExpired(LocalDateTime time) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return now.isAfter(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param month 月
|
||||
* @param day 日
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int month, int day) {
|
||||
return buildTime(year, month, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param month 月
|
||||
* @param day 日
|
||||
* @param hour 小时
|
||||
* @param minute 分钟
|
||||
* @param second 秒
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int month, int day,
|
||||
int hour, int minute, int second) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.YEAR, year);
|
||||
calendar.set(Calendar.MONTH, month - 1);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, day);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||
calendar.set(Calendar.MINUTE, minute);
|
||||
calendar.set(Calendar.SECOND, second);
|
||||
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
public static Date max(Date a, Date b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
return a.compareTo(b) > 0 ? a : b;
|
||||
}
|
||||
|
||||
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
return a.isAfter(b) ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否今天
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isToday(LocalDateTime date) {
|
||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否昨天
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isYesterday(LocalDateTime date) {
|
||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user