master
commit
baa6ff9cc9
|
@ -0,0 +1,7 @@
|
||||||
|
/**/logs
|
||||||
|
/**/*.iml
|
||||||
|
/**/.idea
|
||||||
|
/**/target
|
||||||
|
/**/.DS_Store
|
||||||
|
/**/.xcodemap
|
||||||
|
/**/.back*
|
|
@ -0,0 +1,40 @@
|
||||||
|
<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>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-cache</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>njzscloud-common-cache</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- junit -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public interface Cache {
|
||||||
|
<T> T get(String key);
|
||||||
|
|
||||||
|
<T> T get(String key, Supplier<T> supplier);
|
||||||
|
|
||||||
|
<T> T get(String key, long timeout, Supplier<T> supplier);
|
||||||
|
|
||||||
|
void put(String key, Object value, long timeout);
|
||||||
|
|
||||||
|
void put(String key, Object value);
|
||||||
|
|
||||||
|
void remove(String key);
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class Caches {
|
||||||
|
|
||||||
|
public static final Cache CACHE = SpringUtil.getBean(Cache.class);
|
||||||
|
|
||||||
|
public static <T> T get(String key) {
|
||||||
|
return CACHE.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
return CACHE.get(key, timeout, supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void put(String key, Object value, long timeout) {
|
||||||
|
CACHE.put(key, value, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void put(String key, Object value) {
|
||||||
|
CACHE.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove(String key) {
|
||||||
|
CACHE.remove(key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class DualCache implements Cache {
|
||||||
|
private final FirstCache FIRST_CACHE;
|
||||||
|
private final SecondCache SECOND_CACHE;
|
||||||
|
private final ReentrantReadWriteLock DUAL_CACHE_LOCK = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
public DualCache(int capacity, long firstTimeout, long secondTimeout) {
|
||||||
|
FIRST_CACHE = new FirstCache(capacity, firstTimeout);
|
||||||
|
SECOND_CACHE = new SecondCache(secondTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(String key) {
|
||||||
|
DUAL_CACHE_LOCK.readLock().lock();
|
||||||
|
try {
|
||||||
|
return FIRST_CACHE.get(key, () -> SECOND_CACHE.get(key));
|
||||||
|
} finally {
|
||||||
|
DUAL_CACHE_LOCK.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(String key, Supplier<T> supplier) {
|
||||||
|
DUAL_CACHE_LOCK.readLock().lock();
|
||||||
|
try {
|
||||||
|
return (T) FIRST_CACHE.get(key, () -> SECOND_CACHE.get(key, supplier));
|
||||||
|
} finally {
|
||||||
|
DUAL_CACHE_LOCK.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
DUAL_CACHE_LOCK.readLock().lock();
|
||||||
|
try {
|
||||||
|
return (T) FIRST_CACHE.get(key, timeout, () -> SECOND_CACHE.get(key, timeout, supplier));
|
||||||
|
} finally {
|
||||||
|
DUAL_CACHE_LOCK.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
DUAL_CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
FIRST_CACHE.put(key, value);
|
||||||
|
SECOND_CACHE.put(key, value);
|
||||||
|
} finally {
|
||||||
|
DUAL_CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value, long timeout) {
|
||||||
|
DUAL_CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
FIRST_CACHE.put(key, value);
|
||||||
|
SECOND_CACHE.put(key, value, timeout);
|
||||||
|
} finally {
|
||||||
|
DUAL_CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(String key) {
|
||||||
|
DUAL_CACHE_LOCK.writeLock().lock();
|
||||||
|
try {
|
||||||
|
FIRST_CACHE.remove(key);
|
||||||
|
SECOND_CACHE.remove(key);
|
||||||
|
} finally {
|
||||||
|
DUAL_CACHE_LOCK.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import cn.hutool.cache.CacheUtil;
|
||||||
|
import cn.hutool.cache.impl.LFUCache;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class FirstCache implements Cache {
|
||||||
|
private final LFUCache<String, Object> CACHE;
|
||||||
|
|
||||||
|
public FirstCache(int capacity, long timeout) {
|
||||||
|
CACHE = CacheUtil.newLFUCache(capacity, timeout);
|
||||||
|
CacheUtil.newNoCache().get("first", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key) {
|
||||||
|
return (T) CACHE.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(String key, Supplier<T> supplier) {
|
||||||
|
return (T) CACHE.get(key, supplier::get);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
return (T) CACHE.get(key, true, timeout, supplier::get);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, Object value, long timeout) {
|
||||||
|
CACHE.put(key, value, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
CACHE.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String key) {
|
||||||
|
CACHE.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class NoCache implements Cache {
|
||||||
|
|
||||||
|
public NoCache() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(String key, Supplier<T> supplier) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, Object value, long timeout) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String key) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import com.njzscloud.common.redis.util.Redis;
|
||||||
|
import io.lettuce.core.SetArgs;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class SecondCache implements Cache {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<String, ReentrantReadWriteLock> LOCKS = new ConcurrentHashMap<>();
|
||||||
|
private final long timeout;
|
||||||
|
|
||||||
|
public SecondCache(long timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public <T> T get(String key) {
|
||||||
|
ReentrantReadWriteLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return Redis.get(key);
|
||||||
|
} finally {
|
||||||
|
LOCKS.remove(key);
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, Supplier<T> supplier) {
|
||||||
|
Object o = Redis.get(key);
|
||||||
|
if (o != null) return (T) o;
|
||||||
|
|
||||||
|
ReentrantReadWriteLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
o = Redis.get(key);
|
||||||
|
if (o != null) return (T) o;
|
||||||
|
o = supplier.get();
|
||||||
|
if (o != null) Redis.set(key, o);
|
||||||
|
return (T) o;
|
||||||
|
} finally {
|
||||||
|
LOCKS.remove(key);
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
Object o = Redis.get(key);
|
||||||
|
if (o != null) return (T) o;
|
||||||
|
|
||||||
|
ReentrantReadWriteLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
o = Redis.get(key);
|
||||||
|
if (o != null) return (T) o;
|
||||||
|
o = supplier.get();
|
||||||
|
if (o != null) Redis.set(key, o, new SetArgs().ex(timeout));
|
||||||
|
return (T) o;
|
||||||
|
} finally {
|
||||||
|
LOCKS.remove(key);
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value, long timeout) {
|
||||||
|
ReentrantReadWriteLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
Redis.set(key, value, new SetArgs().ex(timeout));
|
||||||
|
} finally {
|
||||||
|
LOCKS.remove(key);
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
ReentrantReadWriteLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
if (timeout > 0) Redis.set(key, value, new SetArgs().ex(timeout));
|
||||||
|
else Redis.set(key, value);
|
||||||
|
} finally {
|
||||||
|
LOCKS.remove(key);
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(String key) {
|
||||||
|
ReentrantReadWriteLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantReadWriteLock());
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
Redis.del(key);
|
||||||
|
} finally {
|
||||||
|
LOCKS.remove(key);
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.njzscloud.common.cache.config;
|
||||||
|
|
||||||
|
import com.njzscloud.common.cache.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableConfigurationProperties(CacheProperties.class)
|
||||||
|
public class CacheAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Cache cache(CacheProperties properties) {
|
||||||
|
CacheProperties.FirstCacheProperties first = properties.getFirst();
|
||||||
|
CacheProperties.SecondCacheProperties second = properties.getSecond();
|
||||||
|
Cache cache;
|
||||||
|
if (first.isEnabled()) {
|
||||||
|
if (second.isEnabled()) {
|
||||||
|
cache = new DualCache(first.getCapacity(), first.getTimeout(), second.getTimeout());
|
||||||
|
} else {
|
||||||
|
cache = new FirstCache(first.getCapacity(), first.getTimeout());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (second.isEnabled()) {
|
||||||
|
cache = new SecondCache(second.getTimeout());
|
||||||
|
} else {
|
||||||
|
cache = new NoCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.njzscloud.common.cache.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ConfigurationProperties(prefix = "cache")
|
||||||
|
public class CacheProperties {
|
||||||
|
|
||||||
|
private FirstCacheProperties first = new FirstCacheProperties();
|
||||||
|
|
||||||
|
private SecondCacheProperties second = new SecondCacheProperties();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class FirstCacheProperties {
|
||||||
|
private boolean enabled = true;
|
||||||
|
private int capacity = 100000;
|
||||||
|
private long timeout = 3600 * 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class SecondCacheProperties {
|
||||||
|
private boolean enabled = false;
|
||||||
|
private long timeout = 3600 * 24;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
|
||||||
|
com.njzscloud.common.cache.config.CacheAutoConfiguration
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?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>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- easyexcel -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>easyexcel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- fastjson2 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
|
<artifactId>fastjson2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--<editor-fold desc="jackson">-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-xml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!--</editor-fold>-->
|
||||||
|
|
||||||
|
<!--<editor-fold desc="hutool">-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-captcha</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!--</editor-fold>-->
|
||||||
|
|
||||||
|
<!-- cglib -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cglib</groupId>
|
||||||
|
<artifactId>cglib</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- okhttp -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- slf4j -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- logback -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- junit -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- spring-boot -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- configuration processor -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端异常, 表示客户端参错误
|
||||||
|
*/
|
||||||
|
public class CliException extends SysThrowable {
|
||||||
|
/**
|
||||||
|
* 创建异常
|
||||||
|
*
|
||||||
|
* @param cause 源异常
|
||||||
|
* @param expect 期望响应值
|
||||||
|
* @param msg 异常信息(简明)
|
||||||
|
* @param message 异常信息(详细)
|
||||||
|
*/
|
||||||
|
protected CliException(Throwable cause, Object expect, ExceptionMsg msg, Object message) {
|
||||||
|
super(cause, expect, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return StrUtil.format("客户端错误, 错误码: {}, 错误信息: {}, 详细信息: {}", this.msg.code, this.msg.msg, this.message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.lang.SimpleCache;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>用于两个异常之间的排序</p>
|
||||||
|
* <p>根据当前异常与目标异常在异常继承体系中的层级进行比较</p>
|
||||||
|
*
|
||||||
|
* @see org.springframework.core.ExceptionDepthComparator
|
||||||
|
*/
|
||||||
|
public class ExceptionDepthComparator implements Comparator<Class<? extends Throwable>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较器缓存
|
||||||
|
*/
|
||||||
|
private static final SimpleCache<Class<? extends Throwable>, ExceptionDepthComparator> CACHE = new SimpleCache<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目标异常类型
|
||||||
|
*/
|
||||||
|
private final Class<? extends Throwable> targetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建异常比较器
|
||||||
|
*
|
||||||
|
* @param exception 目标异常
|
||||||
|
*/
|
||||||
|
public ExceptionDepthComparator(Throwable exception) {
|
||||||
|
Assert.notNull(exception, "目标异常不能为空");
|
||||||
|
this.targetException = exception.getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建异常比较器
|
||||||
|
*
|
||||||
|
* @param exceptionType 目标异常类型
|
||||||
|
*/
|
||||||
|
public ExceptionDepthComparator(Class<? extends Throwable> exceptionType) {
|
||||||
|
Assert.notNull(exceptionType, "目标异常类型不能为空");
|
||||||
|
this.targetException = exceptionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从给定异常中获取最接近目标异常的匹配项
|
||||||
|
*
|
||||||
|
* @param exceptionTypes 待匹配的异常列表
|
||||||
|
* @param targetException 目标异常
|
||||||
|
* @return 匹配到的异常
|
||||||
|
*/
|
||||||
|
public static Class<? extends Throwable> findClosestMatch(Collection<Class<? extends Throwable>> exceptionTypes, Throwable targetException) {
|
||||||
|
Assert.notEmpty(exceptionTypes, "不能为空");
|
||||||
|
Assert.notNull(targetException, "不能为空");
|
||||||
|
if (exceptionTypes.size() == 1) {
|
||||||
|
return exceptionTypes.iterator().next();
|
||||||
|
}
|
||||||
|
List<Class<? extends Throwable>> handledExceptions = new ArrayList<>(exceptionTypes);
|
||||||
|
ExceptionDepthComparator comparator = CACHE.get(targetException.getClass(), () -> new ExceptionDepthComparator(targetException));
|
||||||
|
handledExceptions.sort(comparator);
|
||||||
|
return handledExceptions.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
|
||||||
|
int depth1 = getDepth(o1, this.targetException);
|
||||||
|
int depth2 = getDepth(o2, this.targetException);
|
||||||
|
return depth1 - depth2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取异常的层级深度
|
||||||
|
*
|
||||||
|
* @param declaredException 待匹配的异常
|
||||||
|
* @param exceptionToMatch 目标异常
|
||||||
|
* @return 深度(≥0),越近数字越小
|
||||||
|
*/
|
||||||
|
private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch) {
|
||||||
|
int depth = 0;
|
||||||
|
do {
|
||||||
|
if (exceptionToMatch.equals(declaredException)) return depth;
|
||||||
|
if (exceptionToMatch == Throwable.class) return Integer.MAX_VALUE;
|
||||||
|
depth++;
|
||||||
|
exceptionToMatch = exceptionToMatch.getSuperclass();
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常信息
|
||||||
|
* <p>默认:</p>
|
||||||
|
* <p>系统异常:SYS_EXP_MSG</p>
|
||||||
|
* <p>客户端错误:CLI_ERR_MSG</p>
|
||||||
|
* <p>系统错误:SYS_ERR_MSG</p>
|
||||||
|
*/
|
||||||
|
public final class ExceptionMsg {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用系统异常
|
||||||
|
*/
|
||||||
|
public static final ExceptionMsg SYS_EXP_MSG = new ExceptionMsg(ExceptionType.SYS_EXP, 1111, "操作失败");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用客户端错误
|
||||||
|
*/
|
||||||
|
public static final ExceptionMsg CLI_ERR_MSG = new ExceptionMsg(ExceptionType.CLI_ERR, 5555, "操作失败");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用系统错误
|
||||||
|
*/
|
||||||
|
public static final ExceptionMsg SYS_ERR_MSG = new ExceptionMsg(ExceptionType.SYS_ERR, 9999, "系统错误");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编号(0< code ≤9999)
|
||||||
|
*/
|
||||||
|
public final int code;
|
||||||
|
/**
|
||||||
|
* 异常信息(应尽量简略)
|
||||||
|
*/
|
||||||
|
public final String msg;
|
||||||
|
/**
|
||||||
|
* 异常类型 {@link ExceptionType}
|
||||||
|
*/
|
||||||
|
public final ExceptionType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建异常信息
|
||||||
|
*
|
||||||
|
* @param type 异常类型 {@link ExceptionType}
|
||||||
|
* @param code 异常编号(0< code ≤9999)
|
||||||
|
* @param msg 异常信息(应尽量简略)
|
||||||
|
*/
|
||||||
|
private ExceptionMsg(ExceptionType type, int code, String msg) {
|
||||||
|
this.code = code;
|
||||||
|
this.type = type;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常类型
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum ExceptionType {
|
||||||
|
|
||||||
|
SYS_EXP(1, "系统异常"),
|
||||||
|
|
||||||
|
CLI_ERR(5, "客户端错误"),
|
||||||
|
|
||||||
|
SYS_ERR(9, "系统错误");
|
||||||
|
|
||||||
|
public final int code;
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常工具类
|
||||||
|
* 创建异常对象
|
||||||
|
*/
|
||||||
|
public class Exceptions {
|
||||||
|
|
||||||
|
public static SysThrowable clierr(Object message, Object... param) {
|
||||||
|
return build(null, ExpectData.NULL_DATA, ExceptionMsg.CLI_ERR_MSG, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable clierr(Object message) {
|
||||||
|
return build(null, ExpectData.NULL_DATA, ExceptionMsg.CLI_ERR_MSG, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable exception(Object message, Object... param) {
|
||||||
|
return build(null, ExpectData.NULL_DATA, ExceptionMsg.SYS_EXP_MSG, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable exception(Object message) {
|
||||||
|
return build(null, ExpectData.NULL_DATA, ExceptionMsg.SYS_EXP_MSG, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable error(Object message, Object... param) {
|
||||||
|
return build(null, ExpectData.NULL_DATA, ExceptionMsg.SYS_ERR_MSG, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable error(Object message) {
|
||||||
|
return build(null, ExpectData.NULL_DATA, ExceptionMsg.SYS_ERR_MSG, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable error(Throwable cause, Object message, Object... param) {
|
||||||
|
return build(cause, ExpectData.NULL_DATA, ExceptionMsg.SYS_ERR_MSG, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SysThrowable error(Throwable cause, Object message) {
|
||||||
|
return build(cause, ExpectData.NULL_DATA, ExceptionMsg.SYS_ERR_MSG, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>创建异常对象</p>
|
||||||
|
*
|
||||||
|
* @param cause 嵌套异常
|
||||||
|
* @param expect 期望值
|
||||||
|
* @param msg 异常信息
|
||||||
|
* @param message 详细信息(字符串模板, 占位符: {})
|
||||||
|
* @param param 模板参数
|
||||||
|
* @see SysThrowable
|
||||||
|
* @see ExceptionMsg
|
||||||
|
* @see ExpectData
|
||||||
|
*/
|
||||||
|
private static SysThrowable build(Throwable cause, ExpectData expect, ExceptionMsg msg, Object message, Object[] param) {
|
||||||
|
|
||||||
|
if (message instanceof String
|
||||||
|
&& !StrUtil.isBlank((String) message)
|
||||||
|
&& param != null
|
||||||
|
&& param.length > 0) {
|
||||||
|
message = StrUtil.format((String) message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
SysThrowable sysThrowable;
|
||||||
|
|
||||||
|
if (msg.type == ExceptionType.SYS_EXP) {
|
||||||
|
sysThrowable = new SysException(cause, expect, msg, message);
|
||||||
|
} else if (msg.type == ExceptionType.CLI_ERR) {
|
||||||
|
sysThrowable = new CliException(cause, expect, msg, message);
|
||||||
|
} else {
|
||||||
|
sysThrowable = new SysError(cause, expect, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sysThrowable;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 期望值
|
||||||
|
* <p>HTTP 响应时 响应体的 JSON 内容</p>
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ExpectData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: null
|
||||||
|
*/
|
||||||
|
NULL_DATA(null),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: []
|
||||||
|
*/
|
||||||
|
ARR_DATA(Collections.emptyList()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: {}
|
||||||
|
*/
|
||||||
|
KV_DATA(Collections.emptyMap()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: ""
|
||||||
|
*/
|
||||||
|
STR_BLANK_DATA(""),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: 0
|
||||||
|
*/
|
||||||
|
NUM_ZERO_DATA(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: false
|
||||||
|
*/
|
||||||
|
BOOL_FALSE_DATA(Boolean.FALSE),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应: true
|
||||||
|
*/
|
||||||
|
BOOL_TRUE_DATA(Boolean.TRUE),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无响应体
|
||||||
|
*/
|
||||||
|
VOID_DATA(Void.TYPE);
|
||||||
|
|
||||||
|
private final Object data;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T getData() {
|
||||||
|
return (T) data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统错误, 此类异常无法处理或不应该处理
|
||||||
|
*/
|
||||||
|
public class SysError extends SysThrowable {
|
||||||
|
/**
|
||||||
|
* 创建异常
|
||||||
|
*
|
||||||
|
* @param cause 源异常
|
||||||
|
* @param expect 期望响应值
|
||||||
|
* @param msg 异常信息(简明)
|
||||||
|
* @param message 异常信息(详细)
|
||||||
|
*/
|
||||||
|
protected SysError(Throwable cause, Object expect, ExceptionMsg msg, Object message) {
|
||||||
|
super(cause, expect, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return StrUtil.format("内部服务错误, 错误码: {}, 错误信息: {}, 详细信息: {}", this.msg.code, this.msg.msg, this.message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统异常, 表示可预料的错误或可预料的情况
|
||||||
|
*/
|
||||||
|
public class SysException extends SysThrowable {
|
||||||
|
/**
|
||||||
|
* 创建异常
|
||||||
|
*
|
||||||
|
* @param cause 源异常
|
||||||
|
* @param expect 期望响应值
|
||||||
|
* @param msg 异常信息(简明)
|
||||||
|
* @param message 异常信息(详细)
|
||||||
|
*/
|
||||||
|
protected SysException(Throwable cause, Object expect, ExceptionMsg msg, Object message) {
|
||||||
|
super(cause, expect, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return StrUtil.format("系统异常, 错误码: {}, 错误信息: {}, 详细信息: {}", this.msg.code, this.msg.msg, this.message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.njzscloud.common.core.ex;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统异常
|
||||||
|
*/
|
||||||
|
public abstract class SysThrowable extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 希望值
|
||||||
|
*
|
||||||
|
* @see ExpectData
|
||||||
|
*/
|
||||||
|
public final Object expect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常信息(简略)
|
||||||
|
*/
|
||||||
|
public final ExceptionMsg msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常信息(详细)
|
||||||
|
*/
|
||||||
|
public final Object message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建异常
|
||||||
|
*
|
||||||
|
* @param cause 源异常
|
||||||
|
* @param expect 期望响应值
|
||||||
|
* @param msg 异常信息(简明)
|
||||||
|
* @param message 异常信息(详细)
|
||||||
|
*/
|
||||||
|
protected SysThrowable(Throwable cause, Object expect, ExceptionMsg msg, Object message) {
|
||||||
|
super(msg.msg, cause);
|
||||||
|
this.msg = msg;
|
||||||
|
this.message = message == null ? "" : message;
|
||||||
|
this.expect = expect;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Jackson.toJsonStr(MapUtil.builder()
|
||||||
|
.put("code", msg.code)
|
||||||
|
.put("expect", expect)
|
||||||
|
.put("msg", msg.msg)
|
||||||
|
.put("message", message)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package com.njzscloud.common.core.fastjson;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONException;
|
||||||
|
import com.alibaba.fastjson2.JSONReader;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.alibaba.fastjson2.filter.Filter;
|
||||||
|
import com.alibaba.fastjson2.filter.ValueFilter;
|
||||||
|
import com.alibaba.fastjson2.reader.ObjectReader;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
import static com.alibaba.fastjson2.JSONReader.Feature.IgnoreCheckClose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Fastjson 工具</p>
|
||||||
|
* <p>已开启类型序列化/反序列化支持</p>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class Fastjson {
|
||||||
|
private static final JSONWriter.Context JSON_WRITER_CONTEXT;
|
||||||
|
private static final JSONReader.Context JSON_READER_CONTEXT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
JSON_WRITER_CONTEXT = new JSONWriter.Context(
|
||||||
|
JSONWriter.Feature.WriteNulls, // 序列化输出空值字段
|
||||||
|
JSONWriter.Feature.BrowserCompatible, // 在大范围超过JavaScript支持的整数,输出为字符串格式
|
||||||
|
JSONWriter.Feature.WriteClassName, // 序列化时输出类型信息
|
||||||
|
JSONWriter.Feature.PrettyFormat, // 格式化输出
|
||||||
|
JSONWriter.Feature.SortMapEntriesByKeys, // 对Map中的KeyValue按照Key做排序后再输出。在有些验签的场景需要使用这个Feature
|
||||||
|
JSONWriter.Feature.WriteBigDecimalAsPlain, // 序列化BigDecimal使用toPlainString,避免科学计数法
|
||||||
|
JSONWriter.Feature.WriteNullListAsEmpty, // 将List类型字段的空值序列化输出为空数组"[]"
|
||||||
|
JSONWriter.Feature.WriteNullStringAsEmpty, // 将String类型字段的空值序列化输出为空字符串""
|
||||||
|
JSONWriter.Feature.WriteLongAsString // 将 Long 作为字符串输出
|
||||||
|
);
|
||||||
|
|
||||||
|
ZoneId zoneId = ZoneId.of("GMT+8");
|
||||||
|
|
||||||
|
JSON_WRITER_CONTEXT.setZoneId(zoneId);
|
||||||
|
JSON_WRITER_CONTEXT.setDateFormat(DatePattern.NORM_DATETIME_PATTERN);
|
||||||
|
|
||||||
|
JSON_WRITER_CONTEXT.configFilter((ValueFilter) (object, name, value) -> {
|
||||||
|
if (value != null) {
|
||||||
|
Class<?> clazz = value.getClass();
|
||||||
|
if (clazz == LocalDate.class) {
|
||||||
|
return DateTimeFormatter.ofPattern(DatePattern.PURE_DATE_PATTERN).withZone(zoneId).format((LocalDate) value);
|
||||||
|
} else if (clazz == LocalTime.class) {
|
||||||
|
return DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN).withZone(zoneId).format((LocalTime) value);
|
||||||
|
} else if (clazz == BigDecimal.class) {
|
||||||
|
return ((BigDecimal) value).toPlainString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
JSON_READER_CONTEXT = new JSONReader.Context(
|
||||||
|
JSONReader.Feature.IgnoreSetNullValue, // 忽略输入为null的字段
|
||||||
|
JSONReader.Feature.SupportAutoType // 支持自动类型,要读取带"@type"类型信息的JSON数据,需要显示打开SupportAutoType
|
||||||
|
);
|
||||||
|
JSON_READER_CONTEXT.setZoneId(zoneId);
|
||||||
|
JSON_READER_CONTEXT.setDateFormat(DatePattern.NORM_DATETIME_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化为 JSON 字符串
|
||||||
|
*
|
||||||
|
* @param object 数据
|
||||||
|
* @return String
|
||||||
|
* @see JSON#toJSONString(Object, JSONWriter.Context)
|
||||||
|
*/
|
||||||
|
public static String toJsonStr(Object object) {
|
||||||
|
return JSON.toJSONString(object, JSON_WRITER_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化为 JSON 字节数组
|
||||||
|
*
|
||||||
|
* @param object 数据
|
||||||
|
* @return byte[]
|
||||||
|
* @see JSON#toJSONBytes(Object, Filter...)
|
||||||
|
*/
|
||||||
|
public static byte[] toJsonBytes(Object object) {
|
||||||
|
return JSON.toJSONBytes(object, StandardCharsets.UTF_8, JSON_WRITER_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反序列化
|
||||||
|
*
|
||||||
|
* @param json JSON 字符串
|
||||||
|
* @param type 类型
|
||||||
|
* @return T
|
||||||
|
* @see JSON#parseObject(String, Class, JSONReader.Context)
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> T toBean(String json, Type type) {
|
||||||
|
if (json == null || json.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toBean(JSONReader.of(json, JSON_READER_CONTEXT), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toBean(InputStream json, Type type) {
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toBean(JSONReader.of(json, StandardCharsets.UTF_8, JSON_READER_CONTEXT), type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toBean(byte[] json, Type type) {
|
||||||
|
if (json == null || json.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return toBean(JSONReader.of(json, JSON_READER_CONTEXT), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> T toBean(JSONReader reader, Type type) {
|
||||||
|
ObjectReader<T> objectReader = JSON_READER_CONTEXT.getObjectReader(type);
|
||||||
|
try {
|
||||||
|
T object = objectReader.readObject(reader, type, null, 0);
|
||||||
|
reader.handleResolveTasks(object);
|
||||||
|
if (!reader.isEnd() && (JSON_READER_CONTEXT.getFeatures() & IgnoreCheckClose.mask) == 0) {
|
||||||
|
throw new JSONException(reader.info("input not end"));
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
} finally {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.njzscloud.common.core.fastjson.serializer;
|
||||||
|
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONReader;
|
||||||
|
import com.alibaba.fastjson2.reader.ObjectReader;
|
||||||
|
import com.alibaba.fastjson2.util.TypeUtils;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.ienum.Dict;
|
||||||
|
import com.njzscloud.common.core.ienum.DictInt;
|
||||||
|
import com.njzscloud.common.core.ienum.DictStr;
|
||||||
|
import com.njzscloud.common.core.ienum.IEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dict 枚举的 Fastjson 反序列化器<br/>
|
||||||
|
* 使用方式(二选一即可): <br/>
|
||||||
|
* 1、在字段上使用 JSONField 进行指定<br/>
|
||||||
|
* 如,@JSONField(deserializeUsing = DictObjectSerializer.class)<br/>
|
||||||
|
* 2、在枚举类上使用 JSONType 进行指定(在接口上指定无效)<br/>
|
||||||
|
* 如,@JSONType(writeEnumAsJavaBean = true, deserializer = DictObjectSerializer.class)<br/><br/>
|
||||||
|
* JSON 格式见对应的序列化器 {@link DictObjectSerializer}
|
||||||
|
*
|
||||||
|
* @see Dict
|
||||||
|
* @see DictInt
|
||||||
|
* @see DictStr
|
||||||
|
* @see DictObjectSerializer
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DictObjectDeserializer implements ObjectReader<Dict> {
|
||||||
|
|
||||||
|
private static final ClassLoader CLASSLOADER = DictObjectDeserializer.class.getClassLoader();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dict readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
|
||||||
|
try {
|
||||||
|
if (jsonReader.isObject()) {
|
||||||
|
Map<String, Object> map = jsonReader.readObject();
|
||||||
|
|
||||||
|
String typeField = (String) map.get(IEnum.ENUM_TYPE);
|
||||||
|
Object valField = map.get(Dict.ENUM_VAL);
|
||||||
|
|
||||||
|
|
||||||
|
Class<?> clazz = CLASSLOADER.loadClass(typeField);
|
||||||
|
|
||||||
|
if (valField instanceof String) {
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse((String) valField, constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(Integer.parseInt((String) valField), constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (valField instanceof Integer) {
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(String.valueOf(valField), constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse((Integer) valField, constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (jsonReader.isString()) {
|
||||||
|
Class<?> clazz = TypeUtils.getClass(fieldType);
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
String val = jsonReader.readString();
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
String val = jsonReader.readString();
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(Integer.parseInt(val), constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (jsonReader.isInt()) {
|
||||||
|
Class<?> clazz = TypeUtils.getClass(fieldType);
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
String val = jsonReader.readString();
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
Integer val = jsonReader.readInt32();
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("字典枚举反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "字典枚举反序列化失败,类型:{},字段名:{}", fieldType, fieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.njzscloud.common.core.fastjson.serializer;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.alibaba.fastjson2.util.TypeUtils;
|
||||||
|
import com.alibaba.fastjson2.writer.ObjectWriter;
|
||||||
|
import com.njzscloud.common.core.ienum.Dict;
|
||||||
|
import com.njzscloud.common.core.ienum.DictInt;
|
||||||
|
import com.njzscloud.common.core.ienum.DictStr;
|
||||||
|
import com.njzscloud.common.core.ienum.IEnum;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dict 枚举的 Fastjson 序列化器<br/>
|
||||||
|
* 使用方式(二选一即可): <br/>
|
||||||
|
* 1、在字段上使用 JSONField 进行指定<br/>
|
||||||
|
* 如,@JSONField(serializeUsing = DictObjectSerializer.class)<br/>
|
||||||
|
* 2、在枚举类上使用 JSONType 进行指定(在接口上指定无效)<br/>
|
||||||
|
* 如,@JSONType(writeEnumAsJavaBean = true, serializer = DictObjectSerializer.class)<br/><br/>
|
||||||
|
* JSON 格式<br/>
|
||||||
|
* 1、枚举不是其他对象的属性<br/>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "type": "", // 枚举全限定类名, 反序列化时会用到
|
||||||
|
* "name": "", // name 属性
|
||||||
|
* "ordinal": 0, // ordinal 属性
|
||||||
|
* "val": 1, // val 属性(字符串/数字), 反序列化时会用到
|
||||||
|
* "txt": "1" // txt 属性
|
||||||
|
* }</pre>
|
||||||
|
* 2、枚举是其他对象的属性<br/>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* // ... 其他属性
|
||||||
|
* "原字段名称": 1, // val 属性(字符串/数字), 反序列化时会用到
|
||||||
|
* "原字段名称Txt": "1" // txt 属性
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see Dict
|
||||||
|
* @see DictInt
|
||||||
|
* @see DictStr
|
||||||
|
*/
|
||||||
|
public class DictObjectSerializer implements ObjectWriter<Dict> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
|
||||||
|
if (object == null) {
|
||||||
|
jsonWriter.writeNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType == null) {
|
||||||
|
Dict<?> dict = (Dict<?>) object;
|
||||||
|
jsonWriter.startObject();
|
||||||
|
|
||||||
|
jsonWriter.writeName(IEnum.ENUM_TYPE);
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeString(dict.getClass().getName());
|
||||||
|
|
||||||
|
jsonWriter.writeName(IEnum.ENUM_NAME);
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeString(((Enum<?>) dict).name());
|
||||||
|
|
||||||
|
jsonWriter.writeName(IEnum.ENUM_ORDINAL);
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeInt32(((Enum<?>) dict).ordinal());
|
||||||
|
|
||||||
|
jsonWriter.writeName(Dict.ENUM_VAL);
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeAny(dict.getVal());
|
||||||
|
|
||||||
|
jsonWriter.writeName(Dict.ENUM_TXT);
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeString(dict.getTxt());
|
||||||
|
|
||||||
|
jsonWriter.endObject();
|
||||||
|
} else {
|
||||||
|
Class<?> clazz = TypeUtils.getClass(fieldType);
|
||||||
|
|
||||||
|
if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
DictInt dictInt = (DictInt) object;
|
||||||
|
jsonWriter.writeInt32(dictInt.getVal());
|
||||||
|
|
||||||
|
jsonWriter.writeName(fieldName + "Txt");
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeString(dictInt.getTxt());
|
||||||
|
} else if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
DictStr dictStr = (DictStr) object;
|
||||||
|
jsonWriter.writeString(dictStr.getVal());
|
||||||
|
jsonWriter.writeName(fieldName + "Txt");
|
||||||
|
jsonWriter.writeColon();
|
||||||
|
jsonWriter.writeString(dictStr.getTxt());
|
||||||
|
} else {
|
||||||
|
jsonWriter.writeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
package com.njzscloud.common.core.http;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.support.ResponseInfo;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 客户端
|
||||||
|
*
|
||||||
|
* @see okhttp3.OkHttpClient
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public final class HttpClient {
|
||||||
|
private static volatile HttpClient DEFAULT;
|
||||||
|
|
||||||
|
private final OkHttpClient OK_HTTP_CLIENT;
|
||||||
|
|
||||||
|
public HttpClient(HttpClientProperties httpClientProperties) {
|
||||||
|
this(httpClientProperties, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient(OkHttpClient okHttpClient) {
|
||||||
|
OK_HTTP_CLIENT = okHttpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient(HttpClientProperties httpClientProperties, ExecutorService executorService) {
|
||||||
|
Duration callTimeout = httpClientProperties.getCallTimeout();
|
||||||
|
Duration keepAliveTime = httpClientProperties.getKeepAliveTime();
|
||||||
|
long keepAliveTimeSeconds = keepAliveTime.getSeconds();
|
||||||
|
int maxIdleConnections = httpClientProperties.getMaxIdleConnections();
|
||||||
|
Duration readTimeout = httpClientProperties.getReadTimeout();
|
||||||
|
Duration connectTimeout = httpClientProperties.getConnectTimeout();
|
||||||
|
|
||||||
|
ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, keepAliveTimeSeconds, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||||
|
.callTimeout(callTimeout)
|
||||||
|
.readTimeout(readTimeout)
|
||||||
|
.connectTimeout(connectTimeout)
|
||||||
|
.connectionPool(connectionPool);
|
||||||
|
if (executorService != null) {
|
||||||
|
Dispatcher dispatcher = new Dispatcher(executorService);
|
||||||
|
builder.dispatcher(dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.OK_HTTP_CLIENT = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient() {
|
||||||
|
this(HttpClientProperties.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized HttpClient defaultHttpClient() {
|
||||||
|
if (DEFAULT == null) DEFAULT = new HttpClient();
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* private Request buildRequest(HttpServer httpServer,
|
||||||
|
HttpEndpoint httpEndpoint,
|
||||||
|
RequestParamBuilder requestParamBuilder,
|
||||||
|
RequestInterceptor requestInterceptor) {
|
||||||
|
|
||||||
|
|
||||||
|
if (requestInterceptor != null) {
|
||||||
|
RequestParam processed = requestInterceptor.process(httpServer, httpEndpoint, requestParamBuilder);
|
||||||
|
if (processed != null) requestParam = processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpMethod httpMethod = httpEndpoint.httpMethod;
|
||||||
|
String url = httpEndpoint.urlTpl;
|
||||||
|
BodyParam<?> bodyParam = requestParam.bodyParam;
|
||||||
|
|
||||||
|
|
||||||
|
Map<String, Object> pathParamMap = requestParam.pathParam.getParam();
|
||||||
|
if (CollUtil.isNotEmpty(pathParamMap)) {
|
||||||
|
url = StrUtil.format(url, pathParamMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> queryParamMap = requestParam.queryParam.getParam();
|
||||||
|
String query = Param.kvStr(queryParamMap);
|
||||||
|
if (StrUtil.isNotBlank(query)) {
|
||||||
|
query = "?" + query;
|
||||||
|
}
|
||||||
|
url = url + query;
|
||||||
|
|
||||||
|
Headers headers = null;
|
||||||
|
Map<String, Object> headerParamMap = requestParam.headerParam.getParam();
|
||||||
|
if (CollUtil.isNotEmpty(headerParamMap)) {
|
||||||
|
Headers.Builder headerBuilder = new Headers.Builder();
|
||||||
|
Set<Map.Entry<String, Object>> entries = headerParamMap.entrySet();
|
||||||
|
for (Map.Entry<String, Object> entry : entries) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Object value = entry.getValue();
|
||||||
|
if (value != null) headerBuilder.set(key, value.toString());
|
||||||
|
}
|
||||||
|
headers = headerBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RequestBody postBody = null;
|
||||||
|
if (httpMethod != HttpMethod.GET) {
|
||||||
|
byte[] param = bodyParam.getParam();
|
||||||
|
postBody = RequestBody.create(param, MediaType.parse(bodyParam.contentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
Request.Builder requestBuilder = new Request.Builder()
|
||||||
|
.url(httpServer + url)
|
||||||
|
.method(httpMethod.name(), postBody);
|
||||||
|
|
||||||
|
if (headers != null) {
|
||||||
|
requestBuilder.headers(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T execute(
|
||||||
|
HttpServer httpServer,
|
||||||
|
HttpEndpoint endpoint,
|
||||||
|
RequestParamBuilder requestParamBuilder,
|
||||||
|
Type responseType,
|
||||||
|
RequestInterceptor requestInterceptor,
|
||||||
|
ResponseInterceptor<T> responseInterceptor
|
||||||
|
) {
|
||||||
|
ResponseResult.ResponseResultBuilder responseResultBuilder = ResponseResult.builder();
|
||||||
|
|
||||||
|
Response response = null;
|
||||||
|
ResponseBody responseBody = null;
|
||||||
|
Integer code = null;
|
||||||
|
String message = null;
|
||||||
|
Headers headers = null;
|
||||||
|
byte[] body = new byte[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Request request = buildRequest(httpServer, endpoint, requestParamBuilder, requestInterceptor);
|
||||||
|
Call call = this.OK_HTTP_CLIENT.newCall(request);
|
||||||
|
response = call.execute();
|
||||||
|
responseBody = response.body();
|
||||||
|
if (responseBody != null) {
|
||||||
|
body = responseBody.bytes();
|
||||||
|
}
|
||||||
|
code = response.code();
|
||||||
|
message = response.message();
|
||||||
|
headers = response.headers();
|
||||||
|
|
||||||
|
responseResultBuilder.
|
||||||
|
code(code)
|
||||||
|
.status(message)
|
||||||
|
.headers(headers.toMultimap())
|
||||||
|
.body(body)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
responseResultBuilder.e(e);
|
||||||
|
} finally {
|
||||||
|
if (responseBody != null) responseBody.close();
|
||||||
|
if (response != null) response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseInterceptor.process(
|
||||||
|
responseType,
|
||||||
|
httpServer, endpoint, requestParam,
|
||||||
|
responseResultBuilder.build());
|
||||||
|
} */
|
||||||
|
|
||||||
|
|
||||||
|
public ResponseInfo execute(HttpMethod httpMethod, String url, Map<String, String> headers, Tuple2<String, byte[]> body) {
|
||||||
|
Request.Builder requestBuilder = new Request.Builder().url(url);
|
||||||
|
|
||||||
|
if (httpMethod != HttpMethod.GET) {
|
||||||
|
requestBuilder.method(httpMethod.name(),
|
||||||
|
RequestBody.create(body.get_1(),
|
||||||
|
MediaType.parse(body.get_0())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollUtil.isNotEmpty(headers)) {
|
||||||
|
Headers.Builder headerBuilder = new Headers.Builder();
|
||||||
|
headers.forEach(headerBuilder::set);
|
||||||
|
requestBuilder.headers(headerBuilder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
try (
|
||||||
|
Response response = this.OK_HTTP_CLIENT
|
||||||
|
.newCall(requestBuilder.build())
|
||||||
|
.execute();
|
||||||
|
ResponseBody responseBody = response.body()
|
||||||
|
) {
|
||||||
|
int code = response.code();
|
||||||
|
String message = response.message();
|
||||||
|
Map<String, List<String>> responseHeaders = response.headers().toMultimap();
|
||||||
|
byte[] bytes = responseBody == null ? new byte[0] : responseBody.bytes();
|
||||||
|
return ResponseInfo.create(code, message, responseHeaders, bytes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
return ResponseInfo.create(0, "http 请求失败", null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.njzscloud.common.core.http;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(HttpClientProperties.class)
|
||||||
|
public class HttpClientAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public HttpClient httpClient(HttpClientProperties httpClientProperties) {
|
||||||
|
return new HttpClient(httpClientProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HttpClientDecorator httpClientDecorator(HttpClient httpClient) {
|
||||||
|
return new HttpClientDecorator(httpClient);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
package com.njzscloud.common.core.http;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.lang.SimpleCache;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.GetEndpoint;
|
||||||
|
import com.njzscloud.common.core.http.annotation.PostEndpoint;
|
||||||
|
import com.njzscloud.common.core.http.annotation.RemoteServer;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.interceptor.RequestInterceptor;
|
||||||
|
import com.njzscloud.common.core.http.interceptor.ResponseInterceptor;
|
||||||
|
import com.njzscloud.common.core.http.resolver.*;
|
||||||
|
import com.njzscloud.common.core.http.support.RequestInfo;
|
||||||
|
import com.njzscloud.common.core.http.support.ResponseInfo;
|
||||||
|
import com.njzscloud.common.core.http.support.ResponseResult;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务装饰器, 用于 "接口注解式" HTTP 请求配置, 使用 cglib 进行接口装饰
|
||||||
|
* <p>注: 在使用异步请求时, 接口方法的返回值不能是 void, 除非 HTTP 请求确实没有返回值</p>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class HttpClientDecorator {
|
||||||
|
private static final SimpleCache<Class<?>, Object> ENHANCER_CACHE = new SimpleCache<>();
|
||||||
|
|
||||||
|
private final HttpClient HTTP_CLIENT;
|
||||||
|
|
||||||
|
public HttpClientDecorator(HttpClient httpClient) {
|
||||||
|
HTTP_CLIENT = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T decorate(Class<T> clazz) {
|
||||||
|
return (T) ENHANCER_CACHE.get(clazz, () -> {
|
||||||
|
MethodInterceptor methodInterceptor = new MethodInterceptorImpl(clazz, HTTP_CLIENT);
|
||||||
|
Enhancer enhancer = new Enhancer();
|
||||||
|
enhancer.setInterfaces(new Class<?>[]{clazz});
|
||||||
|
enhancer.setCallback(methodInterceptor);
|
||||||
|
return enhancer.create();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantLengthCheck")
|
||||||
|
private static class MethodInterceptorImpl implements MethodInterceptor {
|
||||||
|
private static final Pattern ADDR_PATTERN = Pattern.compile("(?<protocol>http(?:s)?):\\/\\/(?<host>[0-9a-zA-Z_\\-\\.]+)(?::(?<port>[0-9]+))?(?<baseUrl>\\/\\S*)*");
|
||||||
|
private final HttpClient HTTP_CLIENT;
|
||||||
|
private final String baseUrl;
|
||||||
|
private final RequestInterceptor requestInterceptor;
|
||||||
|
private final ResponseInterceptor responseInterceptor;
|
||||||
|
|
||||||
|
public MethodInterceptorImpl(Class<?> clazz, HttpClient httpClient) {
|
||||||
|
RemoteServer anno = clazz.getAnnotation(RemoteServer.class);
|
||||||
|
baseUrl = anno.value();
|
||||||
|
Matcher matcher = ADDR_PATTERN.matcher(baseUrl);
|
||||||
|
Assert.isTrue(matcher.matches(), "地址不合法");
|
||||||
|
Class<? extends RequestInterceptor> requestedInterceptorClazz = anno.requestInterceptor();
|
||||||
|
Class<? extends ResponseInterceptor> responseInterceptorClazz = anno.responseInterceptor();
|
||||||
|
requestInterceptor = ReflectUtil.newInstance(requestedInterceptorClazz);
|
||||||
|
responseInterceptor = ReflectUtil.newInstance(responseInterceptorClazz);
|
||||||
|
|
||||||
|
HTTP_CLIENT = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
HttpMethod httpMethod;
|
||||||
|
String urlTpl;
|
||||||
|
String desc;
|
||||||
|
|
||||||
|
GetEndpoint getEndpoint = method.getAnnotation(GetEndpoint.class);
|
||||||
|
if (getEndpoint == null) {
|
||||||
|
PostEndpoint postEndpoint = method.getAnnotation(PostEndpoint.class);
|
||||||
|
if (postEndpoint == null) {
|
||||||
|
//
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
httpMethod = HttpMethod.POST;
|
||||||
|
urlTpl = baseUrl + postEndpoint.value();
|
||||||
|
desc = postEndpoint.desc();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
httpMethod = HttpMethod.GET;
|
||||||
|
urlTpl = baseUrl + getEndpoint.value();
|
||||||
|
desc = getEndpoint.desc();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] additional = requestInterceptor.process(httpMethod, urlTpl, args);
|
||||||
|
|
||||||
|
Parameter[] parameters = method.getParameters();
|
||||||
|
|
||||||
|
PathParamResolver pathParamResolver = new PathParamResolver();
|
||||||
|
QueryParamResolver queryParamResolver = new QueryParamResolver();
|
||||||
|
HeaderParamResolver headerParamResolver = new HeaderParamResolver();
|
||||||
|
BodyParamResolver bodyParamResolver = null;
|
||||||
|
JsonBodyParamResolver jsonBodyParamResolver = null;
|
||||||
|
XmlBodyParamResolver xmlBodyParamResolver = null;
|
||||||
|
FormBodyParamResolver formBodyParamResolver = null;
|
||||||
|
MultiBodyParamResolver multiBodyParamResolver = null;
|
||||||
|
|
||||||
|
List<ParamResolver<? extends Annotation, ?>> paramResolvers = ListUtil.list(false,
|
||||||
|
pathParamResolver,
|
||||||
|
queryParamResolver,
|
||||||
|
headerParamResolver
|
||||||
|
);
|
||||||
|
|
||||||
|
if (httpMethod != HttpMethod.GET) {
|
||||||
|
jsonBodyParamResolver = new JsonBodyParamResolver();
|
||||||
|
xmlBodyParamResolver = new XmlBodyParamResolver();
|
||||||
|
formBodyParamResolver = new FormBodyParamResolver();
|
||||||
|
multiBodyParamResolver = new MultiBodyParamResolver();
|
||||||
|
bodyParamResolver = new BodyParamResolver();
|
||||||
|
paramResolvers.add(jsonBodyParamResolver);
|
||||||
|
paramResolvers.add(xmlBodyParamResolver);
|
||||||
|
paramResolvers.add(formBodyParamResolver);
|
||||||
|
paramResolvers.add(multiBodyParamResolver);
|
||||||
|
paramResolvers.add(bodyParamResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additional != null) {
|
||||||
|
for (Object o : additional) {
|
||||||
|
for (ParamResolver<? extends Annotation, ?> resolver : paramResolvers) {
|
||||||
|
resolver.resolve(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
|
Parameter parameter = parameters[i];
|
||||||
|
for (ParamResolver<? extends Annotation, ?> resolver : paramResolvers) {
|
||||||
|
resolver.resolve(parameter, args[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urlTpl = pathParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
urlTpl = queryParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
Map<String, String> headers = headerParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
Tuple2<String, byte[]> body = null;
|
||||||
|
if (httpMethod != HttpMethod.GET) {
|
||||||
|
body = jsonBodyParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
if (body == null) body = xmlBodyParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
if (body == null) body = formBodyParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
if (body == null) body = multiBodyParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
if (body == null) body = bodyParamResolver.resolve(httpMethod, urlTpl);
|
||||||
|
if (body == null) body = Tuple2.create("", new byte[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseInfo responseInfo = HTTP_CLIENT.execute(httpMethod, urlTpl, headers, body);
|
||||||
|
|
||||||
|
RequestInfo requestInfo = RequestInfo.create(desc, httpMethod, urlTpl, headers, body);
|
||||||
|
|
||||||
|
Type genericReturnType = method.getGenericReturnType();
|
||||||
|
|
||||||
|
Type returnType;
|
||||||
|
Type rawType;
|
||||||
|
|
||||||
|
if (genericReturnType instanceof ParameterizedType) {
|
||||||
|
ParameterizedType typeWrap = (ParameterizedType) genericReturnType;
|
||||||
|
|
||||||
|
rawType = typeWrap.getRawType();
|
||||||
|
|
||||||
|
if (rawType == ResponseResult.class) {
|
||||||
|
returnType = typeWrap.getActualTypeArguments()[0];
|
||||||
|
} else {
|
||||||
|
returnType = typeWrap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnType = genericReturnType;
|
||||||
|
rawType = genericReturnType;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object res = responseInterceptor.process(requestInfo, responseInfo, returnType);
|
||||||
|
|
||||||
|
if (ResponseResult.class == rawType) {
|
||||||
|
return ResponseResult
|
||||||
|
.builder()
|
||||||
|
.code(responseInfo.code)
|
||||||
|
.status(responseInfo.message)
|
||||||
|
.headers(responseInfo.header)
|
||||||
|
.body(res)
|
||||||
|
.e(responseInfo.e)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.njzscloud.common.core.http;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 客户端配置
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@ConfigurationProperties("okhttp-client")
|
||||||
|
public class HttpClientProperties {
|
||||||
|
|
||||||
|
public static final HttpClientProperties DEFAULT = new HttpClientProperties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用的超时
|
||||||
|
* 覆盖解析 DNS、连接、写入请求正文、服务器处理和读取响应正文等。如果调用需要重定向或重试,所有都必须在一个超时期限内完成。
|
||||||
|
* 默认 0,0 表示不限制
|
||||||
|
*/
|
||||||
|
private Duration callTimeout = Duration.ofSeconds(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取超时
|
||||||
|
* 默认 10s,0 表示不限制
|
||||||
|
*/
|
||||||
|
private Duration readTimeout = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接超时
|
||||||
|
* TCP 套接字连接到目标主机超时时间
|
||||||
|
* 默认 10s,0 表示不限制
|
||||||
|
*/
|
||||||
|
private Duration connectTimeout = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大活跃连接数, IP 和 port 相同的 HTTP 请求通常共享同一个连接
|
||||||
|
* 默认 500
|
||||||
|
*/
|
||||||
|
private int maxIdleConnections = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接空闲时间
|
||||||
|
* 默认 10min
|
||||||
|
*/
|
||||||
|
private Duration keepAliveTime = Duration.ofMinutes(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用 SSL 验证, 默认 false 不禁用
|
||||||
|
*/
|
||||||
|
private boolean disableSslValidation = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许重定向, 默认 true 允许
|
||||||
|
*/
|
||||||
|
private boolean followRedirects = true;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.BodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultBodyParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface BodyParam {
|
||||||
|
String contentType() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends BodyParamProcessor> processor() default DefaultBodyParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.FormBodyParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface FormBodyParam {
|
||||||
|
/**
|
||||||
|
* 字段名
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
String format() default "";
|
||||||
|
|
||||||
|
RoundingMode roundingMode() default RoundingMode.HALF_UP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends FormBodyParamProcessor> processor() default FormBodyParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET 请求端点注解
|
||||||
|
*/
|
||||||
|
@Target({METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface GetEndpoint {
|
||||||
|
/**
|
||||||
|
* 端点地址
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端点描述
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String desc() default "";
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultHeaderParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface HeaderParam {
|
||||||
|
/**
|
||||||
|
* 字段名
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
String format() default "";
|
||||||
|
|
||||||
|
RoundingMode roundingMode() default RoundingMode.HALF_UP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends DefaultHeaderParamProcessor> processor() default DefaultHeaderParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultJsonBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.http.processor.JsonBodyParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface JsonBodyParam {
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends JsonBodyParamProcessor> processor() default DefaultJsonBodyParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.MultiBodyParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface MultiBodyParam {
|
||||||
|
/**
|
||||||
|
* 字段名
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
String format() default "";
|
||||||
|
|
||||||
|
String filename() default "";
|
||||||
|
|
||||||
|
RoundingMode roundingMode() default RoundingMode.HALF_UP;
|
||||||
|
|
||||||
|
String contentType() default "";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends MultiBodyParamProcessor> processor() default MultiBodyParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultPathParamProcessor;
|
||||||
|
import com.njzscloud.common.core.http.processor.PathParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface PathParam {
|
||||||
|
/**
|
||||||
|
* 字段名
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
String format() default "";
|
||||||
|
|
||||||
|
RoundingMode roundingMode() default RoundingMode.HALF_UP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends PathParamProcessor> processor() default DefaultPathParamProcessor.class;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST 请求端点注解
|
||||||
|
*/
|
||||||
|
@Target({METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface PostEndpoint {
|
||||||
|
/**
|
||||||
|
* 端点地址
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端点描述
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String desc() default "";
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultQueryParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface QueryParam {
|
||||||
|
/**
|
||||||
|
* 字段名
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
String format() default "";
|
||||||
|
|
||||||
|
RoundingMode roundingMode() default RoundingMode.HALF_UP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends DefaultQueryParamProcessor> processor() default DefaultQueryParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.interceptor.CompositeInterceptor;
|
||||||
|
import com.njzscloud.common.core.http.interceptor.RequestInterceptor;
|
||||||
|
import com.njzscloud.common.core.http.interceptor.ResponseInterceptor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 服务器配置
|
||||||
|
*/
|
||||||
|
@Target({TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface RemoteServer {
|
||||||
|
/**
|
||||||
|
* 服务器地址, 如, http://localhost:80/api
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求拦截器, 在请求之前触发
|
||||||
|
*
|
||||||
|
* @return RequestInterceptor.class
|
||||||
|
* @see RequestInterceptor
|
||||||
|
* @see CompositeInterceptor
|
||||||
|
*/
|
||||||
|
Class<? extends RequestInterceptor> requestInterceptor() default CompositeInterceptor.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应拦截器, 在请求之后触发
|
||||||
|
*
|
||||||
|
* @return ResponseInterceptor.class
|
||||||
|
* @see ResponseInterceptor
|
||||||
|
* @see CompositeInterceptor
|
||||||
|
*/
|
||||||
|
Class<? extends ResponseInterceptor> responseInterceptor() default CompositeInterceptor.class;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.njzscloud.common.core.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultXmlBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.http.processor.XmlBodyParamProcessor;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
|
||||||
|
@Target({TYPE, FIELD, PARAMETER, METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface XmlBodyParam {
|
||||||
|
/**
|
||||||
|
* 参数处理器
|
||||||
|
*
|
||||||
|
* @return ParamProcessor
|
||||||
|
*/
|
||||||
|
Class<? extends XmlBodyParamProcessor> processor() default DefaultXmlBodyParamProcessor.class;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.njzscloud.common.core.http.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 方法
|
||||||
|
*/
|
||||||
|
public enum HttpMethod {
|
||||||
|
GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, CONNECT, PATCH
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.njzscloud.common.core.http.constant;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME
|
||||||
|
*/
|
||||||
|
public interface Mime {
|
||||||
|
String FORM = "application/x-www-form-urlencoded";
|
||||||
|
String MULTIPART_FORM = "multipart/form-data";
|
||||||
|
String BINARY = "application/octet-stream";
|
||||||
|
String JSON = "application/json";
|
||||||
|
String X_NDJSON = "application/x-ndjson";
|
||||||
|
String XML = "application/xml";
|
||||||
|
String TXT = "text/plain";
|
||||||
|
String HTML = "text/html";
|
||||||
|
String CSS = "text/css";
|
||||||
|
String GIF = "image/gif";
|
||||||
|
String JPG = "image/jpeg";
|
||||||
|
String PNG = "image/png";
|
||||||
|
String SVG = "image/svg+xml";
|
||||||
|
String WEBP = "image/webp";
|
||||||
|
String PDF = "image/pdf";
|
||||||
|
String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
||||||
|
String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
|
String PPT = "application/vnd.ms-powerpoint";
|
||||||
|
String XLS = "application/vnd.ms-excel";
|
||||||
|
String DOC = "application/msword";
|
||||||
|
String ZIP = "application/zip";
|
||||||
|
String MP3 = "audio/mpeg";
|
||||||
|
String MP4 = "video/mp4";
|
||||||
|
|
||||||
|
|
||||||
|
static String u8Val(String mime) {
|
||||||
|
return StrUtil.format("{};charset={}", mime, "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.njzscloud.common.core.http.interceptor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.support.RequestInfo;
|
||||||
|
import com.njzscloud.common.core.http.support.ResponseInfo;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组合式拦截器, 响应拦截器默认解析 JSON 类型参数
|
||||||
|
*
|
||||||
|
* @see RequestInterceptor
|
||||||
|
* @see ResponseInterceptor
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CompositeInterceptor implements RequestInterceptor, ResponseInterceptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] process(HttpMethod method, String url, Object[] args) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object process(RequestInfo requestInfo, ResponseInfo responseInfo, Type responseType) {
|
||||||
|
System.out.println(Jackson.toJsonStr(requestInfo));
|
||||||
|
System.out.println(new String(requestInfo.body));
|
||||||
|
|
||||||
|
Object data = new String(responseInfo.body);
|
||||||
|
|
||||||
|
/* if (responseInfo.success) {
|
||||||
|
if (responseInfo.body != null) {
|
||||||
|
data = Jackson.toBean(responseInfo.body, responseType);
|
||||||
|
log.info("Jackson: {}", JSON.toJSONString(data));
|
||||||
|
data = JSON.parseObject(responseInfo.body, responseType);
|
||||||
|
log.info("Fastjson: {}", JSON.toJSONString(data));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("HTTP请求失败");
|
||||||
|
} */
|
||||||
|
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.njzscloud.common.core.http.interceptor;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求拦截器, 在请求之前触发, 可修改请求参数
|
||||||
|
*/
|
||||||
|
public interface RequestInterceptor {
|
||||||
|
Object[] process(HttpMethod method, String url, Object[] args);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.njzscloud.common.core.http.interceptor;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.support.RequestInfo;
|
||||||
|
import com.njzscloud.common.core.http.support.ResponseInfo;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应拦截器, 在请求之后触发,
|
||||||
|
* 无论请求是否成功或是发生异常都会调用,
|
||||||
|
* 可用于响应结果解析
|
||||||
|
*/
|
||||||
|
public interface ResponseInterceptor {
|
||||||
|
|
||||||
|
Object process(RequestInfo requestInfo, ResponseInfo responseInfo, Type responseType);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.BodyParam;
|
||||||
|
|
||||||
|
public interface BodyParamProcessor {
|
||||||
|
byte[] process(BodyParam bodyParam, String paramName, Class<?> paramClazz, Object paramValue);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.BodyParam;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class DefaultBodyParamProcessor implements BodyParamProcessor {
|
||||||
|
@Override
|
||||||
|
public byte[] process(BodyParam bodyParam, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
Method toBytesMethod = ReflectUtil.getMethod(paramClazz, "toBytes");
|
||||||
|
if (toBytesMethod == null) {
|
||||||
|
return paramValue.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
} else {
|
||||||
|
return ReflectUtil.invoke(paramValue, toBytesMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.FormBodyParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultFormBodyParamProcessor implements FormBodyParamProcessor {
|
||||||
|
@Override
|
||||||
|
public void process(FormBodyParam formBodyParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, List<String>> result) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.HeaderParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultHeaderParamProcessor implements HeaderParamProcessor {
|
||||||
|
@Override
|
||||||
|
public void process(HeaderParam headerParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, List<String>> result) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.JsonBodyParam;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
|
||||||
|
public class DefaultJsonBodyParamProcessor implements JsonBodyParamProcessor {
|
||||||
|
@Override
|
||||||
|
public byte[] process(JsonBodyParam jsonBodyParam, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
return Jackson.toJsonBytes(paramValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.MultiBodyParam;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple3;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultMultiBodyParamProcessor implements MultiBodyParamProcessor {
|
||||||
|
@Override
|
||||||
|
public void process(MultiBodyParam multiBodyParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, Tuple3<String, String, byte[]>> result) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.PathParam;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultPathParamProcessor implements PathParamProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(PathParam pathParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, String> result) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.QueryParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DefaultQueryParamProcessor implements QueryParamProcessor {
|
||||||
|
@Override
|
||||||
|
public void process(QueryParam queryParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, List<String>> result) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.XmlBodyParam;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
|
||||||
|
public class DefaultXmlBodyParamProcessor implements XmlBodyParamProcessor {
|
||||||
|
@Override
|
||||||
|
public byte[] process(XmlBodyParam xmlBodyParam, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
return Jackson.toXmlBytes(paramValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.FormBodyParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface FormBodyParamProcessor {
|
||||||
|
void process(FormBodyParam formBodyParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, List<String>> result);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.HeaderParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface HeaderParamProcessor {
|
||||||
|
void process(HeaderParam headerParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, List<String>> result);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.JsonBodyParam;
|
||||||
|
|
||||||
|
public interface JsonBodyParamProcessor {
|
||||||
|
byte[] process(JsonBodyParam jsonBodyParam, String paramName, Class<?> paramClazz, Object paramValue);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.MultiBodyParam;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple3;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface MultiBodyParamProcessor {
|
||||||
|
void process(MultiBodyParam multiBodyParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, Tuple3<String, String, byte[]>> result);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.PathParam;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface PathParamProcessor {
|
||||||
|
void process(PathParam pathParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, String> result);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.QueryParam;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface QueryParamProcessor {
|
||||||
|
void process(QueryParam queryParam, String paramName, Class<?> paramClazz, Object paramValue, Map<String, List<String>> result);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.njzscloud.common.core.http.processor;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.http.annotation.XmlBodyParam;
|
||||||
|
|
||||||
|
public interface XmlBodyParamProcessor {
|
||||||
|
byte[] process(XmlBodyParam xmlBodyParam, String paramName, Class<?> paramClazz, Object paramValue);
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.BodyParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.constant.Mime;
|
||||||
|
import com.njzscloud.common.core.http.processor.BodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
|
||||||
|
public class BodyParamResolver extends ParamResolver<BodyParam, Tuple2<String, byte[]>> {
|
||||||
|
byte[] result = null;
|
||||||
|
String contentType;
|
||||||
|
|
||||||
|
public BodyParamResolver() {
|
||||||
|
super(BodyParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tuple2<String, byte[]> resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result == null) return null;
|
||||||
|
return Tuple2.create(contentType == null ? Mime.BINARY : contentType, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(BodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
contentType = anno.contentType();
|
||||||
|
Class<? extends BodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
result = ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, BodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (hasParameterAnno) return false;
|
||||||
|
|
||||||
|
contentType = anno.contentType();
|
||||||
|
Class<? extends BodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
result = ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.FormBodyParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.constant.Mime;
|
||||||
|
import com.njzscloud.common.core.http.processor.FormBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class FormBodyParamResolver extends ParamResolver<FormBodyParam, Tuple2<String, byte[]>> {
|
||||||
|
Map<String, List<String>> result = new HashMap<>();
|
||||||
|
|
||||||
|
public FormBodyParamResolver() {
|
||||||
|
super(FormBodyParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tuple2<String, byte[]> resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result.isEmpty()) return null;
|
||||||
|
StringJoiner joiner = new StringJoiner("&");
|
||||||
|
result.forEach((k, v) -> v.forEach(it -> joiner.add(k + "=" + it)));
|
||||||
|
byte[] bytes = joiner.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
return Tuple2.create(Mime.FORM, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(FormBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String value = anno.value();
|
||||||
|
if (isKv(paramClazz)) resolveKv(paramValue);
|
||||||
|
else if (isArr(paramClazz)) resolveArr(StrUtil.isBlank(value) ? paramName : value, paramValue);
|
||||||
|
else if (isStr(paramClazz) || isNum(paramClazz) || isDt(paramClazz)) resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, FormBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (anno != null) {
|
||||||
|
Class<? extends FormBodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
if (processorClazz != FormBodyParamProcessor.class) {
|
||||||
|
ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue, result);
|
||||||
|
} else if (!hasParameterAnno && isKv(paramClazz)) {
|
||||||
|
resolveKv(paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anno != null) {
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) paramName = value;
|
||||||
|
}
|
||||||
|
if (isArr(paramClazz)) resolveArr(paramName, paramValue);
|
||||||
|
else resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resolveKv(Object paramValue) {
|
||||||
|
((Map<?, ?>) paramValue).forEach((k, v) -> {
|
||||||
|
if (v == null) return;
|
||||||
|
Class<?> clazz = v.getClass();
|
||||||
|
String paramName = k.toString();
|
||||||
|
if (isArr(clazz)) resolveArr(paramName, v);
|
||||||
|
else resolveFormable(null, paramName, clazz, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resolveFormable(FormBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String key = paramName;
|
||||||
|
String val;
|
||||||
|
|
||||||
|
String format = null;
|
||||||
|
RoundingMode roundingMode = null;
|
||||||
|
if (anno != null) {
|
||||||
|
format = anno.format();
|
||||||
|
roundingMode = anno.roundingMode();
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) key = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNum(paramClazz)) {
|
||||||
|
val = formatNum(format, roundingMode, paramValue);
|
||||||
|
} else if (isDt(paramClazz)) {
|
||||||
|
val = formatDt(format, paramValue);
|
||||||
|
} else {
|
||||||
|
val = paramValue.toString();
|
||||||
|
}
|
||||||
|
result.computeIfAbsent(key, it -> new ArrayList<>()).add(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.HeaderParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultHeaderParamProcessor;
|
||||||
|
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class HeaderParamResolver extends ParamResolver<HeaderParam, Map<String, String>> {
|
||||||
|
Map<String, List<String>> result = new TreeMap<>();
|
||||||
|
|
||||||
|
public HeaderParamResolver() {
|
||||||
|
super(HeaderParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
HashMap<String, String> map = new HashMap<>();
|
||||||
|
result.forEach((k, v) -> map.put(k, String.join(",", v)));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(HeaderParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String value = anno.value();
|
||||||
|
if (isKv(paramClazz)) resolveKv(paramValue);
|
||||||
|
else if (isArr(paramClazz)) resolveArr(StrUtil.isBlank(value) ? paramName : value, paramValue);
|
||||||
|
else if (isStr(paramClazz) || isNum(paramClazz) || isDt(paramClazz)) resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, HeaderParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (anno != null) {
|
||||||
|
Class<? extends DefaultHeaderParamProcessor> processorClazz = anno.processor();
|
||||||
|
if (processorClazz != DefaultHeaderParamProcessor.class) {
|
||||||
|
ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue, result);
|
||||||
|
} else if (!hasParameterAnno && isKv(paramClazz)) {
|
||||||
|
resolveKv(paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anno != null) {
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) paramName = value;
|
||||||
|
}
|
||||||
|
if (isArr(paramClazz)) resolveArr(paramName, paramValue);
|
||||||
|
else resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resolveKv(Object paramValue) {
|
||||||
|
((Map<?, ?>) paramValue).forEach((k, v) -> {
|
||||||
|
if (v == null) return;
|
||||||
|
Class<?> clazz = v.getClass();
|
||||||
|
String paramName = k.toString();
|
||||||
|
if (isArr(clazz)) resolveArr(paramName, v);
|
||||||
|
else resolveFormable(null, paramName, clazz, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resolveFormable(HeaderParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String key = paramName;
|
||||||
|
String val;
|
||||||
|
|
||||||
|
String format = null;
|
||||||
|
RoundingMode roundingMode = null;
|
||||||
|
if (anno != null) {
|
||||||
|
format = anno.format();
|
||||||
|
roundingMode = anno.roundingMode();
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) key = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNum(paramClazz)) {
|
||||||
|
val = formatNum(format, roundingMode, paramValue);
|
||||||
|
} else if (isDt(paramClazz)) {
|
||||||
|
val = formatDt(format, paramValue);
|
||||||
|
} else {
|
||||||
|
val = paramValue.toString();
|
||||||
|
}
|
||||||
|
result.computeIfAbsent(key, it -> new ArrayList<>()).add(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.JsonBodyParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.constant.Mime;
|
||||||
|
import com.njzscloud.common.core.http.processor.JsonBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
|
||||||
|
public class JsonBodyParamResolver extends ParamResolver<JsonBodyParam, Tuple2<String, byte[]>> {
|
||||||
|
byte[] result = null;
|
||||||
|
|
||||||
|
public JsonBodyParamResolver() {
|
||||||
|
super(JsonBodyParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tuple2<String, byte[]> resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result == null) return null;
|
||||||
|
return Tuple2.create(Mime.JSON, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(JsonBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
Class<? extends JsonBodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
result = ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, JsonBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (hasParameterAnno) return false;
|
||||||
|
|
||||||
|
Class<? extends JsonBodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
result = ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.core.util.URLUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.MultiBodyParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.constant.Mime;
|
||||||
|
import com.njzscloud.common.core.http.processor.MultiBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple3;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MultiBodyParamResolver extends ParamResolver<MultiBodyParam, Tuple2<String, byte[]>> {
|
||||||
|
private static final byte[] DASHDASH = "--".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final byte[] CRLF = StrUtil.CRLF.getBytes(StandardCharsets.UTF_8);
|
||||||
|
Map<String, Tuple3<String, String, byte[]>> result = new HashMap<>();
|
||||||
|
|
||||||
|
public MultiBodyParamResolver() {
|
||||||
|
super(MultiBodyParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tuple2<String, byte[]> resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result.isEmpty()) return null;
|
||||||
|
|
||||||
|
String boundary = RandomUtil.randomString(16);
|
||||||
|
String contentType = Mime.MULTIPART_FORM + "; boundary=" + boundary;
|
||||||
|
byte[] bytes = buildContent(boundary.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return Tuple2.create(contentType, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] buildContent(byte[] boundary) {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
result.forEach((k, v) -> {
|
||||||
|
String filename = URLUtil.encode(v.get_0());
|
||||||
|
String contentType = v.get_1();
|
||||||
|
byte[] bytes = v.get_2();
|
||||||
|
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, DASHDASH);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, boundary);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, CRLF);
|
||||||
|
|
||||||
|
byte[] contentDisposition;
|
||||||
|
if (StrUtil.isNotBlank(filename)) {
|
||||||
|
contentDisposition = StrUtil.format("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"", k, filename).getBytes(StandardCharsets.UTF_8);
|
||||||
|
} else {
|
||||||
|
contentDisposition = StrUtil.format("Content-Disposition: form-data; name=\"{}\"", k).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, contentDisposition);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, CRLF);
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(contentType)) {
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, ("Content-Type: " + contentType).getBytes(StandardCharsets.UTF_8));
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, CRLF);
|
||||||
|
}
|
||||||
|
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, CRLF);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, bytes);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, CRLF);
|
||||||
|
|
||||||
|
});
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, DASHDASH);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, boundary);
|
||||||
|
IoUtil.write(byteArrayOutputStream, false, DASHDASH);
|
||||||
|
|
||||||
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(MultiBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (isKv(paramClazz)) resolveKv(paramValue);
|
||||||
|
else if (isStr(paramClazz) || isNum(paramClazz) || isDt(paramClazz) || isBytes(paramClazz) || isIn(paramClazz) || isFile(paramClazz)) resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, MultiBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (anno != null) {
|
||||||
|
Class<? extends MultiBodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
if (processorClazz != MultiBodyParamProcessor.class) {
|
||||||
|
ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue, result);
|
||||||
|
} else if (!hasParameterAnno && isKv(paramClazz)) {
|
||||||
|
resolveKv(paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveFormable(MultiBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String key = paramName;
|
||||||
|
byte[] val;
|
||||||
|
String mime;
|
||||||
|
String filename = null;
|
||||||
|
|
||||||
|
String format = null;
|
||||||
|
RoundingMode roundingMode = null;
|
||||||
|
if (anno != null) {
|
||||||
|
format = anno.format();
|
||||||
|
roundingMode = anno.roundingMode();
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) key = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNum(paramClazz)) {
|
||||||
|
mime = Mime.TXT;
|
||||||
|
val = formatNum(format, roundingMode, paramValue).getBytes(StandardCharsets.UTF_8);
|
||||||
|
} else if (isDt(paramClazz)) {
|
||||||
|
mime = Mime.TXT;
|
||||||
|
val = formatDt(format, paramValue).getBytes(StandardCharsets.UTF_8);
|
||||||
|
} else if (isBytes(paramClazz)) {
|
||||||
|
mime = Mime.BINARY;
|
||||||
|
val = (byte[]) paramValue;
|
||||||
|
} else if (isIn(paramClazz) || isFile(paramClazz)) {
|
||||||
|
mime = Mime.BINARY;
|
||||||
|
Tuple2<String, byte[]> fileInfo = toFileInfo(paramValue);
|
||||||
|
filename = fileInfo.get_0();
|
||||||
|
val = fileInfo.get_1();
|
||||||
|
} else {
|
||||||
|
mime = Mime.TXT;
|
||||||
|
val = paramValue.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.put(key, Tuple3.create(filename, mime, val));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple3;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.TemporalAccessor;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class ParamResolver<T extends Annotation, R> {
|
||||||
|
protected final Class<T> annoType;
|
||||||
|
|
||||||
|
abstract public R resolve(HttpMethod httpMethod, String urlTpl);
|
||||||
|
|
||||||
|
public final void resolve(Parameter parameter, Object obj) {
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
Class<?> parameterClazz = obj.getClass();
|
||||||
|
String parameterName;
|
||||||
|
T parameterAnno = null;
|
||||||
|
if (parameter == null) {
|
||||||
|
parameterName = StrUtil.lowerFirst(obj.getClass().getName());
|
||||||
|
} else {
|
||||||
|
parameterName = parameter.getName();
|
||||||
|
parameterAnno = parameter.getAnnotation(this.annoType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameterAnno == null) {
|
||||||
|
parameterAnno = parameterClazz.getAnnotation(this.annoType);
|
||||||
|
}
|
||||||
|
|
||||||
|
int state = 0;
|
||||||
|
|
||||||
|
if (parameterAnno != null) {
|
||||||
|
state = this.resolveParameter(parameterAnno, parameterName, parameterClazz, obj) ?
|
||||||
|
1 :
|
||||||
|
2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == 1) return;
|
||||||
|
|
||||||
|
List<Method> getterMethods = ReflectUtil.getPublicMethods(parameterClazz,
|
||||||
|
it -> (it.getName().startsWith("get") || it.getName().startsWith("is")) &&
|
||||||
|
!Modifier.isStatic(it.getModifiers()) &&
|
||||||
|
!it.getName().equals("getClass")
|
||||||
|
);
|
||||||
|
|
||||||
|
for (Method getterMethod : getterMethods) {
|
||||||
|
Object fieldValue = ReflectUtil.invoke(obj, getterMethod);
|
||||||
|
if (fieldValue == null) continue;
|
||||||
|
String getterMethodName = getterMethod.getName();
|
||||||
|
Class<?> returnType = getterMethod.getReturnType();
|
||||||
|
String fieldName = StrUtil.lowerFirst(getterMethodName.startsWith("get") ?
|
||||||
|
getterMethodName.substring(3) :
|
||||||
|
getterMethodName.substring(2));
|
||||||
|
|
||||||
|
T fieldAnno = getterMethod.getAnnotation(this.annoType);
|
||||||
|
if (fieldAnno == null) {
|
||||||
|
Field field = ReflectUtil.getField(parameterClazz, fieldName);
|
||||||
|
fieldAnno = field.getAnnotation(this.annoType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldAnno == null && state == 0) continue;
|
||||||
|
|
||||||
|
this.resolveField(state == 2, fieldAnno, fieldName, returnType, fieldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void resolve(Object obj) {
|
||||||
|
resolve(null, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean resolveParameter(T anno, String paramName, Class<?> paramClazz, Object paramValue);
|
||||||
|
|
||||||
|
protected abstract boolean resolveField(boolean hasParameterAnno, T anno, String paramName, Class<?> paramClazz, Object paramValue);
|
||||||
|
|
||||||
|
|
||||||
|
public final boolean isNum(Class<?> clazz) {
|
||||||
|
return clazz == byte.class || clazz == short.class ||
|
||||||
|
clazz == int.class || clazz == long.class ||
|
||||||
|
clazz == float.class || clazz == double.class
|
||||||
|
|| Number.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isStr(Class<?> clazz) {
|
||||||
|
return clazz == String.class || clazz == char.class || clazz == Character.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isDt(Class<?> clazz) {
|
||||||
|
return clazz == LocalDate.class ||
|
||||||
|
clazz == LocalTime.class ||
|
||||||
|
clazz == LocalDateTime.class ||
|
||||||
|
Date.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isArr(Class<?> clazz) {
|
||||||
|
return Collection.class.isAssignableFrom(clazz) || clazz.isArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Collection<?> toArr(Object obj) {
|
||||||
|
if (obj == null) return null;
|
||||||
|
Class<?> clazz = obj.getClass();
|
||||||
|
if (clazz.isArray()) {
|
||||||
|
return Arrays.asList((Object[]) obj);
|
||||||
|
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||||
|
return (Collection<?>) obj;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("不是数组");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isKv(Class<?> clazz) {
|
||||||
|
return Map.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isIn(Class<?> clazz) {
|
||||||
|
return InputStream.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isBytes(Class<?> clazz) {
|
||||||
|
return clazz == byte[].class || clazz == Byte[].class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Tuple2<String, byte[]> toFileInfo(Object obj) {
|
||||||
|
if (obj == null) return null;
|
||||||
|
Class<?> clazz = obj.getClass();
|
||||||
|
if (isIn(clazz)) {
|
||||||
|
byte[] bytes = IoUtil.readBytes((InputStream) obj);
|
||||||
|
return Tuple3.create(null, bytes);
|
||||||
|
} else if (isFile(clazz)) {
|
||||||
|
byte[] bytes = FileUtil.readBytes((File) obj);
|
||||||
|
String name = ((File) obj).getName();
|
||||||
|
return Tuple3.create(name, bytes);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("不是文件");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isFile(Class<?> clazz) {
|
||||||
|
return File.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected final String formatDt(String format, Object value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
Class<?> clazz = value.getClass();
|
||||||
|
if (Date.class.isAssignableFrom(clazz)) {
|
||||||
|
if (StrUtil.isBlank(format)) format = DatePattern.NORM_DATETIME_PATTERN;
|
||||||
|
return DateUtil.format(((Date) value), format);
|
||||||
|
} else if (LocalDateTime.class.isAssignableFrom(clazz)) {
|
||||||
|
if (StrUtil.isBlank(format)) format = DatePattern.NORM_DATETIME_PATTERN;
|
||||||
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
|
||||||
|
return dateTimeFormatter.format((TemporalAccessor) value);
|
||||||
|
} else if (LocalDate.class.isAssignableFrom(clazz)) {
|
||||||
|
if (StrUtil.isBlank(format)) format = DatePattern.NORM_DATE_PATTERN;
|
||||||
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
|
||||||
|
return dateTimeFormatter.format((TemporalAccessor) value);
|
||||||
|
} else if (LocalTime.class.isAssignableFrom(clazz)) {
|
||||||
|
if (StrUtil.isBlank(format)) format = DatePattern.NORM_TIME_PATTERN;
|
||||||
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
|
||||||
|
return dateTimeFormatter.format((TemporalAccessor) value);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("不是时间");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final String formatDt(Object value) {
|
||||||
|
return formatDt(null, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final String formatNum(String format, RoundingMode roundingMode, Object value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (StrUtil.isBlank(format)) return formatNum(value);
|
||||||
|
|
||||||
|
Class<?> clazz = value.getClass();
|
||||||
|
if (isNum(clazz)) {
|
||||||
|
return NumberUtil.decimalFormat(format, value, roundingMode);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("不是数字");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final String formatNum(String format, Object value) {
|
||||||
|
return formatNum(format, null, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final String formatNum(Object value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
|
||||||
|
Class<?> clazz = value.getClass();
|
||||||
|
if (isNum(clazz)) {
|
||||||
|
if (clazz == BigDecimal.class) {
|
||||||
|
return ((BigDecimal) value).toPlainString();
|
||||||
|
} else {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("不是数字");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resolveKv(Object paramValue) {
|
||||||
|
((Map<?, ?>) paramValue).forEach((k, v) -> {
|
||||||
|
if (v == null) return;
|
||||||
|
resolveFormable(null, k.toString(), v.getClass(), v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveArr(String paramName, Object paramValue) {
|
||||||
|
toArr(paramValue)
|
||||||
|
.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(it -> resolveFormable(null, paramName, it.getClass(), it));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveFormable(T anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.PathParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.processor.PathParamProcessor;
|
||||||
|
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
public class PathParamResolver extends ParamResolver<PathParam, String> {
|
||||||
|
Map<String, String> result = new TreeMap<>();
|
||||||
|
|
||||||
|
public PathParamResolver() {
|
||||||
|
super(PathParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result.isEmpty()) return urlTpl;
|
||||||
|
return StrUtil.format(urlTpl, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(PathParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (isKv(paramClazz)) resolveKv(paramValue);
|
||||||
|
else if (isStr(paramClazz) || isNum(paramClazz) || isDt(paramClazz)) resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, PathParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (anno != null) {
|
||||||
|
Class<? extends PathParamProcessor> processorClazz = anno.processor();
|
||||||
|
if (processorClazz != PathParamProcessor.class) {
|
||||||
|
ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue, result);
|
||||||
|
} else if (!hasParameterAnno && isKv(paramClazz)) {
|
||||||
|
resolveKv(paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveFormable(PathParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String key = paramName;
|
||||||
|
String val;
|
||||||
|
|
||||||
|
String format = null;
|
||||||
|
RoundingMode roundingMode = null;
|
||||||
|
if (anno != null) {
|
||||||
|
format = anno.format();
|
||||||
|
roundingMode = anno.roundingMode();
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) key = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNum(paramClazz)) {
|
||||||
|
val = formatNum(format, roundingMode, paramValue);
|
||||||
|
} else if (isDt(paramClazz)) {
|
||||||
|
val = formatDt(format, paramValue);
|
||||||
|
} else {
|
||||||
|
val = paramValue.toString();
|
||||||
|
}
|
||||||
|
result.put(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.QueryParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.processor.DefaultQueryParamProcessor;
|
||||||
|
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class QueryParamResolver extends ParamResolver<QueryParam, String> {
|
||||||
|
Map<String, List<String>> result = new TreeMap<>();
|
||||||
|
|
||||||
|
public QueryParamResolver() {
|
||||||
|
super(QueryParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result.isEmpty()) return urlTpl;
|
||||||
|
StringJoiner joiner = new StringJoiner("&", urlTpl + (urlTpl.contains("?") ? "&" : "?"), "");
|
||||||
|
result.forEach((k, v) -> v.forEach(it -> joiner.add(k + "=" + it)));
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(QueryParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String value = anno.value();
|
||||||
|
if (isKv(paramClazz)) resolveKv(paramValue);
|
||||||
|
else if (isArr(paramClazz)) resolveArr(StrUtil.isBlank(value) ? paramName : value, paramValue);
|
||||||
|
else if (isStr(paramClazz) || isNum(paramClazz) || isDt(paramClazz)) resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
else return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, QueryParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (anno != null) {
|
||||||
|
Class<? extends DefaultQueryParamProcessor> processorClazz = anno.processor();
|
||||||
|
if (processorClazz != DefaultQueryParamProcessor.class) {
|
||||||
|
ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue, result);
|
||||||
|
} else if (!hasParameterAnno && isKv(paramClazz)) {
|
||||||
|
resolveKv(paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anno != null) {
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) paramName = value;
|
||||||
|
}
|
||||||
|
if (isArr(paramClazz)) resolveArr(paramName, paramValue);
|
||||||
|
else resolveFormable(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resolveKv(Object paramValue) {
|
||||||
|
((Map<?, ?>) paramValue).forEach((k, v) -> {
|
||||||
|
if (v == null) return;
|
||||||
|
Class<?> clazz = v.getClass();
|
||||||
|
String paramName = k.toString();
|
||||||
|
if (isArr(clazz)) resolveArr(paramName, v);
|
||||||
|
else resolveFormable(null, paramName, clazz, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void resolveFormable(QueryParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
String key = paramName;
|
||||||
|
String val;
|
||||||
|
|
||||||
|
String format = null;
|
||||||
|
RoundingMode roundingMode = null;
|
||||||
|
if (anno != null) {
|
||||||
|
format = anno.format();
|
||||||
|
roundingMode = anno.roundingMode();
|
||||||
|
String value = anno.value();
|
||||||
|
if (StrUtil.isNotBlank(value)) key = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNum(paramClazz)) {
|
||||||
|
val = formatNum(format, roundingMode, paramValue);
|
||||||
|
} else if (isDt(paramClazz)) {
|
||||||
|
val = formatDt(format, paramValue);
|
||||||
|
} else {
|
||||||
|
val = paramValue.toString();
|
||||||
|
}
|
||||||
|
result.computeIfAbsent(key, it -> new ArrayList<>()).add(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.njzscloud.common.core.http.resolver;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import com.njzscloud.common.core.http.annotation.XmlBodyParam;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.http.constant.Mime;
|
||||||
|
import com.njzscloud.common.core.http.processor.XmlBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
|
||||||
|
public class XmlBodyParamResolver extends ParamResolver<XmlBodyParam, Tuple2<String, byte[]>> {
|
||||||
|
byte[] result = null;
|
||||||
|
|
||||||
|
public XmlBodyParamResolver() {
|
||||||
|
super(XmlBodyParam.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tuple2<String, byte[]> resolve(HttpMethod httpMethod, String urlTpl) {
|
||||||
|
if (result == null) return null;
|
||||||
|
|
||||||
|
return Tuple2.create(Mime.XML, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveParameter(XmlBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
Class<? extends XmlBodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
result = ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected boolean resolveField(boolean hasParameterAnno, XmlBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
|
||||||
|
if (hasParameterAnno) return false;
|
||||||
|
|
||||||
|
Class<? extends XmlBodyParamProcessor> processorClazz = anno.processor();
|
||||||
|
result = ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.njzscloud.common.core.http.support;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ParameterInfo {
|
||||||
|
private String name;
|
||||||
|
private List<? extends Annotation> annoList;
|
||||||
|
private Object value;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.njzscloud.common.core.http.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.njzscloud.common.core.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class RequestInfo {
|
||||||
|
public final String desc;
|
||||||
|
public final HttpMethod method;
|
||||||
|
public final String url;
|
||||||
|
public final Map<String, String> headers;
|
||||||
|
public final String contentType;
|
||||||
|
public final byte[] body;
|
||||||
|
|
||||||
|
public static RequestInfo create(String desc, HttpMethod method, String url, Map<String, String> headers, Tuple2<String, byte[]> body) {
|
||||||
|
String contentType = null;
|
||||||
|
byte[] bytes = null;
|
||||||
|
if (body != null) {
|
||||||
|
contentType = body.get_0();
|
||||||
|
bytes = body.get_1();
|
||||||
|
}
|
||||||
|
return new RequestInfo(desc, method, url, MapUtil.unmodifiable(headers), contentType, bytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.njzscloud.common.core.http.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ResponseInfo {
|
||||||
|
public final boolean success;
|
||||||
|
public final int code;
|
||||||
|
public final String message;
|
||||||
|
public final Map<String, List<String>> header;
|
||||||
|
public final byte[] body;
|
||||||
|
public final Exception e;
|
||||||
|
|
||||||
|
public ResponseInfo(boolean success, int code, String message, Map<String, List<String>> header, byte[] body, Exception e) {
|
||||||
|
this.success = success;
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
this.header = header;
|
||||||
|
this.body = body;
|
||||||
|
this.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseInfo create(int code, String message, Map<String, List<String>> header, byte[] body) {
|
||||||
|
return new ResponseInfo(code >= 200 && code <= 299, code, message, header, body, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseInfo create(Exception e) {
|
||||||
|
return new ResponseInfo(false, 0, e.getMessage(), MapUtil.empty(), new byte[0], e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.njzscloud.common.core.http.support;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class ResponseResult<T> {
|
||||||
|
/**
|
||||||
|
* 是否成功, HTTP 状态码 200..299
|
||||||
|
*/
|
||||||
|
public final boolean success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 状态码
|
||||||
|
*/
|
||||||
|
public final int code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 状态信息
|
||||||
|
*/
|
||||||
|
public final String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 响应头
|
||||||
|
*/
|
||||||
|
public final Map<String, List<String>> headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 响应体
|
||||||
|
*/
|
||||||
|
public final T body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误信息, 在请求发生异常时有值
|
||||||
|
*/
|
||||||
|
public final Exception e;
|
||||||
|
|
||||||
|
private ResponseResult(boolean success, int code, String status, Map<String, List<String>> headers, T body, Exception e) {
|
||||||
|
this.success = success;
|
||||||
|
this.code = code;
|
||||||
|
this.status = status;
|
||||||
|
this.headers = headers;
|
||||||
|
this.body = body;
|
||||||
|
this.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResultBuilder<T> builder() {
|
||||||
|
return new ResponseResultBuilder<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ResponseResultBuilder<T> {
|
||||||
|
private int code;
|
||||||
|
private String status;
|
||||||
|
private Map<String, List<String>> headers;
|
||||||
|
private T body;
|
||||||
|
private Exception e;
|
||||||
|
|
||||||
|
private ResponseResultBuilder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResultBuilder<T> code(int code) {
|
||||||
|
this.code = code;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResultBuilder<T> status(String status) {
|
||||||
|
this.status = status;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResultBuilder<T> headers(Map<String, List<String>> headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResultBuilder<T> body(T body) {
|
||||||
|
this.body = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResultBuilder<T> e(Exception e) {
|
||||||
|
this.e = e;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResult<T> build() {
|
||||||
|
return new ResponseResult<>(
|
||||||
|
code >= 200 && code <= 299,
|
||||||
|
code,
|
||||||
|
status,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.njzscloud.common.core.ienum;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.njzscloud.common.core.fastjson.serializer.DictObjectDeserializer;
|
||||||
|
import com.njzscloud.common.core.fastjson.serializer.DictObjectSerializer;
|
||||||
|
import com.njzscloud.common.core.jackson.serializer.DictDeserializer;
|
||||||
|
import com.njzscloud.common.core.jackson.serializer.DictSerializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典枚举<br/>
|
||||||
|
* 仅两个子接口 DictInt、DictStr<br/>
|
||||||
|
*
|
||||||
|
* @see DictInt
|
||||||
|
* @see DictStr
|
||||||
|
* @see DictDeserializer
|
||||||
|
* @see DictSerializer
|
||||||
|
* @see DictObjectDeserializer
|
||||||
|
* @see DictObjectSerializer
|
||||||
|
*/
|
||||||
|
@JsonDeserialize(using = DictDeserializer.class)
|
||||||
|
@JsonSerialize(using = DictSerializer.class)
|
||||||
|
public interface Dict<T> extends IEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举单独序列化时的属性<br/>
|
||||||
|
* 存放枚举的 val 属性值
|
||||||
|
*/
|
||||||
|
String ENUM_VAL = "val";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举单独序列化时的属性<br/>
|
||||||
|
* 存放枚举的 txt 属性值
|
||||||
|
*/
|
||||||
|
String ENUM_TXT = "txt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 "值" 获取到对应的枚举对象
|
||||||
|
*
|
||||||
|
* @param val 值
|
||||||
|
* @param ds 枚举对象数组
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <D> 枚举类型
|
||||||
|
* @return Dict
|
||||||
|
*/
|
||||||
|
static <V, D extends Dict<V>> D parse(V val, D[] ds) {
|
||||||
|
for (D d : ds) {
|
||||||
|
if (d.getVal().equals(val)) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 值
|
||||||
|
*
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
T getVal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本表示
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String getTxt();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.njzscloud.common.core.ienum;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "值" 类型为 Integer<br/>
|
||||||
|
* 枚举应实现此接口
|
||||||
|
*
|
||||||
|
* @see DictStr
|
||||||
|
*/
|
||||||
|
public interface DictInt extends Dict<Integer> {
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.njzscloud.common.core.ienum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "值" 类型为 String<br/>
|
||||||
|
* 枚举应实现此接口
|
||||||
|
*
|
||||||
|
* @see DictInt
|
||||||
|
*/
|
||||||
|
public interface DictStr extends Dict<String> {
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.njzscloud.common.core.ienum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举接口
|
||||||
|
*/
|
||||||
|
public interface IEnum {
|
||||||
|
/**
|
||||||
|
* 枚举单独序列化时的属性<br/>
|
||||||
|
* 存放枚举的全限定类名
|
||||||
|
*/
|
||||||
|
String ENUM_TYPE = "type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举单独序列化时的属性<br/>
|
||||||
|
* 存放枚举的 name 属性值
|
||||||
|
*/
|
||||||
|
String ENUM_NAME = "name";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 枚举单独序列化时的属性<br/>
|
||||||
|
* 存放枚举的 ordinal 属性值
|
||||||
|
*/
|
||||||
|
String ENUM_ORDINAL = "ordinal";
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
package com.njzscloud.common.core.jackson;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.jackson.serializer.BigDecimalModule;
|
||||||
|
import com.njzscloud.common.core.jackson.serializer.LongModule;
|
||||||
|
import com.njzscloud.common.core.jackson.serializer.TimeModule;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jackson 工具<br/>
|
||||||
|
* 从 Spring 中获取 ObjectMapper
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class Jackson {
|
||||||
|
private static final ObjectMapper objectMapper;
|
||||||
|
private static final XmlMapper xmlMapper;
|
||||||
|
|
||||||
|
static {
|
||||||
|
ObjectMapper temp;
|
||||||
|
try {
|
||||||
|
temp = SpringUtil.getBean(ObjectMapper.class);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.warn("从 Spring 中获取 ObjectMapper 失败");
|
||||||
|
temp = createObjectMapper();
|
||||||
|
}
|
||||||
|
objectMapper = temp;
|
||||||
|
XmlMapper _xmlMapper;
|
||||||
|
try {
|
||||||
|
_xmlMapper = SpringUtil.getBean(XmlMapper.class);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.warn("从 Spring 中获取 XmlMapper 失败");
|
||||||
|
_xmlMapper = createXmlMapper();
|
||||||
|
}
|
||||||
|
xmlMapper = _xmlMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化为 JSON 字符串
|
||||||
|
*
|
||||||
|
* @param o 数据
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
public static String toJsonStr(Object o) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(o);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Jackson 序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化为 JSON 字节数组
|
||||||
|
*
|
||||||
|
* @param o 数据
|
||||||
|
* @return byte[]
|
||||||
|
*/
|
||||||
|
public static byte[] toJsonBytes(Object o) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsBytes(o);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Jackson 序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反序列化(泛型支持)<br/>
|
||||||
|
* 如: new TypeReference<List<String>>(){}, 类型对象建议缓存
|
||||||
|
*
|
||||||
|
* @param json json JSON 字符串
|
||||||
|
* @param type 类型
|
||||||
|
* @return T
|
||||||
|
* @see TypeReference
|
||||||
|
*/
|
||||||
|
public static <T> T toBean(String json, Type type) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Jackson 反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 反序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toBean(InputStream json, Type type) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Jackson 反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 反序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toBean(byte[] json, Type type) {
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Jackson 反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 反序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化为 JSON 字符串
|
||||||
|
*
|
||||||
|
* @param o 数据
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
public static String toXmlStr(Object o) {
|
||||||
|
try {
|
||||||
|
return xmlMapper.writeValueAsString(o);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Jackson 序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化为 JSON 字节数组
|
||||||
|
*
|
||||||
|
* @param o 数据
|
||||||
|
* @return byte[]
|
||||||
|
*/
|
||||||
|
public static byte[] toXmlBytes(Object o) {
|
||||||
|
try {
|
||||||
|
return xmlMapper.writeValueAsBytes(o);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Jackson 序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T xmlToBean(String json, Type type) {
|
||||||
|
try {
|
||||||
|
return xmlMapper.readValue(json, xmlMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Jackson 反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 反序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T xmlToBean(InputStream json, Type type) {
|
||||||
|
try {
|
||||||
|
return xmlMapper.readValue(json, xmlMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Jackson 反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 反序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T xmlToBean(byte[] json, Type type) {
|
||||||
|
try {
|
||||||
|
return xmlMapper.readValue(json, xmlMapper.getTypeFactory().constructType(type));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Jackson 反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "Jackson 反序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 ObjectMapper
|
||||||
|
*
|
||||||
|
* @return ObjectMapper
|
||||||
|
*/
|
||||||
|
private static ObjectMapper createObjectMapper() {
|
||||||
|
return new ObjectMapper()
|
||||||
|
.setLocale(Locale.CHINA)
|
||||||
|
.setTimeZone(TimeZone.getTimeZone("GMT+8"))
|
||||||
|
.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN))
|
||||||
|
.setSerializationInclusion(JsonInclude.Include.ALWAYS)
|
||||||
|
.configure(SerializationFeature.INDENT_OUTPUT, true) // 格式化输出
|
||||||
|
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||||
|
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) // 对Map中的KeyValue按照Key做排序后再输出。在有些验签的场景需要使用这个Feature
|
||||||
|
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
|
||||||
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
|
// .configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
|
||||||
|
.configure(JsonGenerator.Feature.IGNORE_UNKNOWN, true)
|
||||||
|
.registerModules(new TimeModule(), new LongModule(), new BigDecimalModule())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static XmlMapper createXmlMapper() {
|
||||||
|
return XmlMapper.xmlBuilder()
|
||||||
|
.defaultLocale(Locale.CHINA)
|
||||||
|
.defaultTimeZone(TimeZone.getTimeZone("GMT+8"))
|
||||||
|
.defaultDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN))
|
||||||
|
.serializationInclusion(JsonInclude.Include.ALWAYS)
|
||||||
|
.configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||||
|
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||||
|
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) // 对Map中的KeyValue按照Key做排序后再输出。在有些验签的场景需要使用这个Feature
|
||||||
|
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
|
||||||
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
|
.addModules(new TimeModule(), new LongModule(), new BigDecimalModule())
|
||||||
|
.build()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 objectMapper
|
||||||
|
*
|
||||||
|
* @return ObjectMapper
|
||||||
|
*/
|
||||||
|
public static ObjectMapper objectMapper() {
|
||||||
|
return objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlMapper xmlMapper() {
|
||||||
|
return xmlMapper;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigDecimal 序列化
|
||||||
|
*/
|
||||||
|
public class BigDecimalModule extends SimpleModule {
|
||||||
|
{
|
||||||
|
this.addSerializer(BigDecimal.class, new BigDecimalSerializer())
|
||||||
|
.addDeserializer(BigDecimal.class, new NumberDeserializers.BigDecimalDeserializer());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigDecimal 序列化为字符串
|
||||||
|
*/
|
||||||
|
public class BigDecimalSerializer extends JsonSerializer<BigDecimal> {
|
||||||
|
@Override
|
||||||
|
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
if (value != null) {
|
||||||
|
gen.writeString(value.toPlainString());
|
||||||
|
} else {
|
||||||
|
gen.writeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.*;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.node.IntNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.ienum.Dict;
|
||||||
|
import com.njzscloud.common.core.ienum.DictInt;
|
||||||
|
import com.njzscloud.common.core.ienum.DictStr;
|
||||||
|
import com.njzscloud.common.core.ienum.IEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dict 枚举的 Jackson 反序列化器<br/><br/>
|
||||||
|
* JSON 格式见对应的序列化器 {@link DictSerializer}
|
||||||
|
*
|
||||||
|
* @see Dict
|
||||||
|
* @see DictInt
|
||||||
|
* @see DictStr
|
||||||
|
* @see DictSerializer
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DictDeserializer extends JsonDeserializer<Dict> {
|
||||||
|
private static final ClassLoader CLASSLOADER = DictDeserializer.class.getClassLoader();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
|
||||||
|
JsonToken currentToken = p.getCurrentToken();
|
||||||
|
|
||||||
|
if (currentToken == JsonToken.START_OBJECT) {
|
||||||
|
TreeNode treeNode = p.getCodec().readTree(p);
|
||||||
|
TreeNode enumType_node = treeNode.get(IEnum.ENUM_TYPE);
|
||||||
|
String enumTypeField = ((TextNode) enumType_node).textValue();
|
||||||
|
TreeNode val_node = treeNode.get(Dict.ENUM_VAL);
|
||||||
|
|
||||||
|
Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz = CLASSLOADER.loadClass(enumTypeField);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw Exceptions.error(e, "类型加载失败:{}", enumTypeField);
|
||||||
|
}
|
||||||
|
if (val_node instanceof TextNode) {
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
String val = ((TextNode) val_node).textValue();
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
String val = ((TextNode) val_node).textValue();
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(Integer.parseInt(val), constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (val_node instanceof IntNode) {
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
int val = ((IntNode) val_node).intValue();
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(String.valueOf(val), constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
int val = ((IntNode) val_node).intValue();
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
JsonStreamContext context = p.getParsingContext();
|
||||||
|
String currentName = context.getCurrentName();
|
||||||
|
|
||||||
|
Object currentValue = p.getCurrentValue();
|
||||||
|
|
||||||
|
Class<?> valueClazz = currentValue.getClass();
|
||||||
|
try {
|
||||||
|
Field field = valueClazz.getDeclaredField(currentName);
|
||||||
|
Class<?> clazz = field.getType();
|
||||||
|
if (DictStr.class.isAssignableFrom(clazz)) {
|
||||||
|
String val = p.getValueAsString();
|
||||||
|
DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else if (DictInt.class.isAssignableFrom(clazz)) {
|
||||||
|
int val = p.getValueAsInt();
|
||||||
|
DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
|
||||||
|
return Dict.parse(val, constants);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("字典枚举反序列化失败", e);
|
||||||
|
throw Exceptions.error(e, "字典枚举反序列化失败,字段名:{},值:{}", currentName, currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonStreamContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.njzscloud.common.core.ienum.Dict;
|
||||||
|
import com.njzscloud.common.core.ienum.DictInt;
|
||||||
|
import com.njzscloud.common.core.ienum.DictStr;
|
||||||
|
import com.njzscloud.common.core.ienum.IEnum;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dict 枚举的 Jackson 序列化器<br/><br/>
|
||||||
|
* JSON 格式<br/>
|
||||||
|
* 1、枚举不是其他对象的属性<br/>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "type": "", // 枚举全限定类名, 反序列化时会用到
|
||||||
|
* "name": "", // name 属性
|
||||||
|
* "ordinal": 0, // ordinal 属性
|
||||||
|
* "val": 1, // val 属性(字符串/数字), 反序列化时会用到
|
||||||
|
* "txt": "1" // txt 属性
|
||||||
|
* }</pre>
|
||||||
|
* 2、枚举是其他对象的属性<br/>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* // ... 其他属性
|
||||||
|
* "原字段名称": 1, // val 属性(字符串/数字), 反序列化时会用到
|
||||||
|
* "原字段名称Txt": "1" // txt 属性
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see Dict
|
||||||
|
* @see DictInt
|
||||||
|
* @see DictStr
|
||||||
|
* @see DictDeserializer
|
||||||
|
*/
|
||||||
|
public class DictSerializer extends JsonSerializer<Dict> {
|
||||||
|
@Override
|
||||||
|
public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
gen.writeNull();
|
||||||
|
} else {
|
||||||
|
JsonStreamContext ctx = gen.getOutputContext();
|
||||||
|
if (ctx.inRoot()) {
|
||||||
|
gen.writeStartObject();
|
||||||
|
gen.writeStringField(IEnum.ENUM_TYPE, value.getClass().getName());
|
||||||
|
gen.writeStringField(IEnum.ENUM_NAME, ((Enum<?>) value).name());
|
||||||
|
gen.writeNumberField(IEnum.ENUM_ORDINAL, ((Enum<?>) value).ordinal());
|
||||||
|
gen.writeObjectField(Dict.ENUM_VAL, value.getVal());
|
||||||
|
gen.writeStringField(Dict.ENUM_TXT, value.getTxt());
|
||||||
|
gen.writeEndObject();
|
||||||
|
} else {
|
||||||
|
gen.writeObject(value.getVal());
|
||||||
|
String currentName = ctx.getCurrentName();
|
||||||
|
gen.writeStringField(currentName + "Txt", value.getTxt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long 序列化
|
||||||
|
*/
|
||||||
|
public class LongModule extends SimpleModule {
|
||||||
|
{
|
||||||
|
this.addSerializer(Long.class, new LongSerializer())
|
||||||
|
.addDeserializer(Long.class, new NumberDeserializers.LongDeserializer(Long.class, null));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long 序列化为字符串
|
||||||
|
*/
|
||||||
|
public class LongSerializer extends JsonSerializer<Long> {
|
||||||
|
@Override
|
||||||
|
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
if (value != null) {
|
||||||
|
gen.writeString(value.toString());
|
||||||
|
} else {
|
||||||
|
gen.writeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间类型序列化
|
||||||
|
*/
|
||||||
|
public class TimeModule extends SimpleModule {
|
||||||
|
{
|
||||||
|
this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)))
|
||||||
|
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)))
|
||||||
|
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)))
|
||||||
|
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)))
|
||||||
|
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)))
|
||||||
|
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,688 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class Q<E> extends AbstractQueue<E> implements BlockingQueue<E> {
|
||||||
|
private final ReentrantLock takeLock = new ReentrantLock();
|
||||||
|
private final Condition notEmpty = takeLock.newCondition();
|
||||||
|
private final ReentrantLock putLock = new ReentrantLock();
|
||||||
|
private final Condition notFull = putLock.newCondition();
|
||||||
|
|
||||||
|
private final int capacity;
|
||||||
|
private final int standbyCapacity;
|
||||||
|
private final AtomicInteger count = new AtomicInteger();
|
||||||
|
private final AtomicInteger standbyCount = new AtomicInteger();
|
||||||
|
|
||||||
|
private Node<E> head;
|
||||||
|
private Node<E> last;
|
||||||
|
private Node<E> border;
|
||||||
|
|
||||||
|
|
||||||
|
public Q() {
|
||||||
|
this(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Q(int capacity, int standbyCapacity) {
|
||||||
|
if (capacity <= 0) throw new IllegalArgumentException();
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.standbyCapacity = standbyCapacity;
|
||||||
|
border = last = head = new Node<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signalNotEmpty() {
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
notEmpty.signal();
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signalNotFull() {
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
notFull.signal();
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enqueue(Node<E> node) {
|
||||||
|
last = last.next = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private E dequeue() {
|
||||||
|
Node<E> h = head;
|
||||||
|
Node<E> first = h.next;
|
||||||
|
h.next = h; // help GC
|
||||||
|
head = first;
|
||||||
|
E x = first.item;
|
||||||
|
first.item = null;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fullyLock() {
|
||||||
|
putLock.lock();
|
||||||
|
takeLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fullyUnlock() {
|
||||||
|
takeLock.unlock();
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return count.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int remainingCapacity() {
|
||||||
|
return capacity - count.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(E e) throws InterruptedException {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
int c = -1;
|
||||||
|
Node<E> node = new Node<E>(e);
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
putLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
|
||||||
|
while (count.get() == capacity) {
|
||||||
|
notFull.await();
|
||||||
|
}
|
||||||
|
enqueue(node);
|
||||||
|
border = node;
|
||||||
|
c = count.getAndIncrement();
|
||||||
|
if (c + 1 < capacity) notFull.signal();
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == 0) signalNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean offer(E e, long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
|
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
long nanos = unit.toNanos(timeout);
|
||||||
|
int c = -1;
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
putLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (count.get() == capacity) {
|
||||||
|
if (nanos <= 0)
|
||||||
|
return false;
|
||||||
|
nanos = notFull.awaitNanos(nanos);
|
||||||
|
}
|
||||||
|
Node<E> node = new Node<>(e);
|
||||||
|
enqueue(node);
|
||||||
|
border = node;
|
||||||
|
c = count.getAndIncrement();
|
||||||
|
if (c + 1 < capacity) notFull.signal();
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == 0)
|
||||||
|
signalNotEmpty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean offer(E e) {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
if (count.get() == capacity) return false;
|
||||||
|
int c = -1;
|
||||||
|
Node<E> node = new Node<>(e);
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
if (count.get() < capacity) {
|
||||||
|
enqueue(node);
|
||||||
|
border = node;
|
||||||
|
c = count.getAndIncrement();
|
||||||
|
if (c + 1 < capacity) notFull.signal();
|
||||||
|
}
|
||||||
|
// log.info("放1:窗口数:{},备份数:{}", count.get(), standbyCount.get());
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == 0) signalNotEmpty();
|
||||||
|
return c >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean offerStandby(E e) {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
if (standbyCount.get() == standbyCapacity) return false;
|
||||||
|
int c = -1;
|
||||||
|
Node<E> node = new Node<>(e);
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
if (standbyCount.get() < standbyCapacity) {
|
||||||
|
enqueue(node);
|
||||||
|
c = count.get();
|
||||||
|
if (c == capacity) {
|
||||||
|
c--;
|
||||||
|
standbyCount.getAndIncrement();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
c = count.getAndIncrement();
|
||||||
|
}
|
||||||
|
if (c + 1 < capacity) notFull.signal();
|
||||||
|
}
|
||||||
|
// log.info("放2:窗口数:{},备份数:{}", count.get(), standbyCount.get());
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == 0) signalNotEmpty();
|
||||||
|
return c >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E take() throws InterruptedException {
|
||||||
|
E x;
|
||||||
|
int c = -1;
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (count.get() == 0) {
|
||||||
|
notEmpty.await();
|
||||||
|
}
|
||||||
|
if (border.next == null) {
|
||||||
|
c = count.getAndDecrement();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyCount.getAndDecrement();
|
||||||
|
c = count.get();
|
||||||
|
}
|
||||||
|
x = dequeue();
|
||||||
|
if (c > 1) notEmpty.signal();
|
||||||
|
// log.info("取2:窗口数:{},备份数:{}", count.get(), standbyCount.get());
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == capacity) signalNotFull();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
E x = null;
|
||||||
|
int c = -1;
|
||||||
|
long nanos = unit.toNanos(timeout);
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (count.get() == 0) {
|
||||||
|
if (nanos <= 0)
|
||||||
|
return null;
|
||||||
|
nanos = notEmpty.awaitNanos(nanos);
|
||||||
|
}
|
||||||
|
if (border.next == null) {
|
||||||
|
c = count.getAndDecrement();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyCount.getAndDecrement();
|
||||||
|
c = count.get();
|
||||||
|
}
|
||||||
|
x = dequeue();
|
||||||
|
if (c > 1) notEmpty.signal();
|
||||||
|
// log.info("取1:窗口数:{},备份数:{}", count.get(), standbyCount.get());
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == capacity)
|
||||||
|
signalNotFull();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E poll() {
|
||||||
|
final AtomicInteger count = this.count;
|
||||||
|
if (count.get() == 0)
|
||||||
|
return null;
|
||||||
|
E x = null;
|
||||||
|
int c = -1;
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
if (count.get() > 0) {
|
||||||
|
if (border.next == null) {
|
||||||
|
c = count.getAndDecrement();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyCount.getAndDecrement();
|
||||||
|
c = count.get();
|
||||||
|
}
|
||||||
|
x = dequeue();
|
||||||
|
if (c > 1) notEmpty.signal();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == capacity)
|
||||||
|
signalNotFull();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E peek() {
|
||||||
|
if (count.get() == 0) return null;
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
Node<E> first = head.next;
|
||||||
|
if (first == null) return null;
|
||||||
|
else return first.item;
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlink(Node<E> trail, Node<E> p, int borderFlag) {
|
||||||
|
p.item = null;
|
||||||
|
trail.next = p.next;
|
||||||
|
if (last == p) last = trail;
|
||||||
|
if (borderFlag == 0) {
|
||||||
|
if (last == p) {
|
||||||
|
border = trail;
|
||||||
|
if (count.getAndDecrement() == capacity) notFull.signal();
|
||||||
|
} else {
|
||||||
|
border = p.next;
|
||||||
|
}
|
||||||
|
} else if (borderFlag == -1) {
|
||||||
|
if (last == border) {
|
||||||
|
if (count.getAndDecrement() == capacity) notFull.signal();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
}
|
||||||
|
} else if (borderFlag == 1) {
|
||||||
|
standbyCount.getAndDecrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
// -1-->左边、0-->边界、1-->右边
|
||||||
|
int borderFlag = -1;
|
||||||
|
for (Node<E> trail = head, p = trail.next;
|
||||||
|
p != null;
|
||||||
|
trail = p, p = p.next) {
|
||||||
|
if (p == border) {
|
||||||
|
borderFlag = 0;
|
||||||
|
} else if (borderFlag == 0) {
|
||||||
|
borderFlag = 1;
|
||||||
|
}
|
||||||
|
if (o.equals(p.item)) {
|
||||||
|
unlink(trail, p, borderFlag);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
for (Node<E> p = head.next; p != null; p = p.next)
|
||||||
|
if (o.equals(p.item))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] toArray() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
int size = count.get() + standbyCount.get();
|
||||||
|
Object[] a = new Object[size];
|
||||||
|
int k = 0;
|
||||||
|
for (Node<E> p = head.next; p != null; p = p.next)
|
||||||
|
a[k++] = p.item;
|
||||||
|
return a;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T[] toArray(T[] a) {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
int size = count.get() + standbyCount.get();
|
||||||
|
if (a.length < size)
|
||||||
|
a = (T[]) java.lang.reflect.Array.newInstance
|
||||||
|
(a.getClass().getComponentType(), size);
|
||||||
|
|
||||||
|
int k = 0;
|
||||||
|
for (Node<E> p = head.next; p != null; p = p.next)
|
||||||
|
a[k++] = (T) p.item;
|
||||||
|
if (a.length > k)
|
||||||
|
a[k] = null;
|
||||||
|
return a;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
Node<E> p = head.next;
|
||||||
|
if (p == null)
|
||||||
|
return "[]";
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('[');
|
||||||
|
for (; ; ) {
|
||||||
|
E e = p.item;
|
||||||
|
sb.append(e == this ? "(this Collection)" : e);
|
||||||
|
p = p.next;
|
||||||
|
if (p == null)
|
||||||
|
return sb.append(']').toString();
|
||||||
|
sb.append(',').append(' ');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
for (Node<E> p, h = head; (p = h.next) != null; h = p) {
|
||||||
|
h.next = h;
|
||||||
|
p.item = null;
|
||||||
|
}
|
||||||
|
head = last;
|
||||||
|
// assert head.item == null && head.next == null;
|
||||||
|
standbyCount.getAndSet(0);
|
||||||
|
if (count.getAndSet(0) == capacity)
|
||||||
|
notFull.signal();
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int drainTo(Collection<? super E> c) {
|
||||||
|
return drainTo(c, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int drainTo(Collection<? super E> c, int maxElements) {
|
||||||
|
if (c == null)
|
||||||
|
throw new NullPointerException();
|
||||||
|
if (c == this)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (maxElements <= 0) return 0;
|
||||||
|
boolean signalNotFull = false;
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
int n = Math.min(maxElements, count.get() + standbyCount.get());
|
||||||
|
// count.get provides visibility to first n Nodes
|
||||||
|
Node<E> h = head;
|
||||||
|
int i = 0;
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
try {
|
||||||
|
while (i < n) {
|
||||||
|
Node<E> p = h.next;
|
||||||
|
c.add(p.item);
|
||||||
|
if (border == last) {
|
||||||
|
x++;
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
p.item = null;
|
||||||
|
h.next = h;
|
||||||
|
h = p;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
} finally {
|
||||||
|
head = h;
|
||||||
|
if (x > 0) {
|
||||||
|
signalNotFull = (count.getAndAdd(-x) == capacity);
|
||||||
|
}
|
||||||
|
if (y > 0) {
|
||||||
|
standbyCount.getAndAdd(-y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
if (signalNotFull)
|
||||||
|
signalNotFull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<E> iterator() {
|
||||||
|
return new Itr();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spliterator<E> spliterator() {
|
||||||
|
return new LBQSpliterator<E>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Node<E> {
|
||||||
|
E item;
|
||||||
|
|
||||||
|
Node<E> next;
|
||||||
|
|
||||||
|
Node(E x) {
|
||||||
|
item = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class LBQSpliterator<E> implements Spliterator<E> {
|
||||||
|
static final int MAX_BATCH = 1 << 25; // max batch array size;
|
||||||
|
final Q<E> queue;
|
||||||
|
Node<E> current; // current node; null until initialized
|
||||||
|
int batch; // batch size for splits
|
||||||
|
boolean exhausted; // true when no more nodes
|
||||||
|
long est; // size estimate
|
||||||
|
|
||||||
|
LBQSpliterator(Q<E> queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
this.est = queue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long estimateSize() {
|
||||||
|
return est;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spliterator<E> trySplit() {
|
||||||
|
Node<E> h;
|
||||||
|
final Q<E> q = this.queue;
|
||||||
|
int b = batch;
|
||||||
|
int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1;
|
||||||
|
if (!exhausted &&
|
||||||
|
((h = current) != null || (h = q.head.next) != null) &&
|
||||||
|
h.next != null) {
|
||||||
|
Object[] a = new Object[n];
|
||||||
|
int i = 0;
|
||||||
|
Node<E> p = current;
|
||||||
|
q.fullyLock();
|
||||||
|
try {
|
||||||
|
if (p != null || (p = q.head.next) != null) {
|
||||||
|
do {
|
||||||
|
if ((a[i] = p.item) != null)
|
||||||
|
++i;
|
||||||
|
} while ((p = p.next) != null && i < n);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
q.fullyUnlock();
|
||||||
|
}
|
||||||
|
if ((current = p) == null) {
|
||||||
|
est = 0L;
|
||||||
|
exhausted = true;
|
||||||
|
} else if ((est -= i) < 0L)
|
||||||
|
est = 0L;
|
||||||
|
if (i > 0) {
|
||||||
|
batch = i;
|
||||||
|
return Spliterators.spliterator
|
||||||
|
(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL |
|
||||||
|
Spliterator.CONCURRENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEachRemaining(Consumer<? super E> action) {
|
||||||
|
if (action == null) throw new NullPointerException();
|
||||||
|
final Q<E> q = this.queue;
|
||||||
|
if (!exhausted) {
|
||||||
|
exhausted = true;
|
||||||
|
Node<E> p = current;
|
||||||
|
do {
|
||||||
|
E e = null;
|
||||||
|
q.fullyLock();
|
||||||
|
try {
|
||||||
|
if (p == null)
|
||||||
|
p = q.head.next;
|
||||||
|
while (p != null) {
|
||||||
|
e = p.item;
|
||||||
|
p = p.next;
|
||||||
|
if (e != null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
q.fullyUnlock();
|
||||||
|
}
|
||||||
|
if (e != null)
|
||||||
|
action.accept(e);
|
||||||
|
} while (p != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryAdvance(Consumer<? super E> action) {
|
||||||
|
if (action == null) throw new NullPointerException();
|
||||||
|
final Q<E> q = this.queue;
|
||||||
|
if (!exhausted) {
|
||||||
|
E e = null;
|
||||||
|
q.fullyLock();
|
||||||
|
try {
|
||||||
|
if (current == null)
|
||||||
|
current = q.head.next;
|
||||||
|
while (current != null) {
|
||||||
|
e = current.item;
|
||||||
|
current = current.next;
|
||||||
|
if (e != null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
q.fullyUnlock();
|
||||||
|
}
|
||||||
|
if (current == null)
|
||||||
|
exhausted = true;
|
||||||
|
if (e != null) {
|
||||||
|
action.accept(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int characteristics() {
|
||||||
|
return Spliterator.ORDERED | Spliterator.NONNULL |
|
||||||
|
Spliterator.CONCURRENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Itr implements Iterator<E> {
|
||||||
|
|
||||||
|
private Node<E> current;
|
||||||
|
private Node<E> lastRet;
|
||||||
|
private E currentElement;
|
||||||
|
|
||||||
|
Itr() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
current = head.next;
|
||||||
|
if (current != null)
|
||||||
|
currentElement = current.item;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node<E> nextNode(Node<E> p) {
|
||||||
|
for (; ; ) {
|
||||||
|
Node<E> s = p.next;
|
||||||
|
if (s == p)
|
||||||
|
return head.next;
|
||||||
|
if (s == null || s.item != null)
|
||||||
|
return s;
|
||||||
|
p = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public E next() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
if (current == null)
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
E x = currentElement;
|
||||||
|
lastRet = current;
|
||||||
|
current = nextNode(current);
|
||||||
|
currentElement = (current == null) ? null : current.item;
|
||||||
|
return x;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
if (lastRet == null)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
Node<E> node = lastRet;
|
||||||
|
lastRet = null;
|
||||||
|
// -1-->左边、0-->边界、1-->右边
|
||||||
|
int borderFlag = -1;
|
||||||
|
for (Node<E> trail = head, p = trail.next;
|
||||||
|
p != null;
|
||||||
|
trail = p, p = p.next) {
|
||||||
|
if (p == border) {
|
||||||
|
borderFlag = 0;
|
||||||
|
} else if (borderFlag == 0) {
|
||||||
|
borderFlag = 1;
|
||||||
|
}
|
||||||
|
if (p == node) {
|
||||||
|
unlink(trail, p, borderFlag);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.RejectedExecutionHandler;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ThreadPool {
|
||||||
|
private static volatile ThreadPoolExecutor THREAD_POOL;
|
||||||
|
|
||||||
|
public static ThreadPoolExecutor defaultThreadPool() {
|
||||||
|
if (THREAD_POOL == null) {
|
||||||
|
synchronized (ThreadPool.class) {
|
||||||
|
if (THREAD_POOL == null) {
|
||||||
|
THREAD_POOL = createThreadPool(null,
|
||||||
|
10, 200,
|
||||||
|
5 * 60,
|
||||||
|
20, 2000,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return THREAD_POOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThreadPoolExecutor createThreadPool(String poolName, int corePoolSize, int maxPoolSize, long keepAliveSeconds, int windowCapacity, int standbyCapacity, RejectedExecutionHandler abortPolicy) {
|
||||||
|
RejectedExecutionHandler abortPolicy_ = abortPolicy == null ? new ThreadPoolExecutor.AbortPolicy() : abortPolicy;
|
||||||
|
Q<Runnable> q = new Q<>(windowCapacity, standbyCapacity);
|
||||||
|
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds, TimeUnit.SECONDS,
|
||||||
|
q,
|
||||||
|
new DefaultThreadFactory(poolName),
|
||||||
|
(r, p) -> {
|
||||||
|
if (!q.offerStandby(r)) {
|
||||||
|
log.debug("任务队列已满");
|
||||||
|
abortPolicy_.rejectedExecution(r, p);
|
||||||
|
} else {
|
||||||
|
log.debug("任务已加入备用队列");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
threadPoolExecutor.allowCoreThreadTimeOut(true);
|
||||||
|
return threadPoolExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ThreadPoolExecutor threadPool = defaultThreadPool();
|
||||||
|
try {
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
System.out.println("1--->" + Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(3000);
|
||||||
|
System.out.println("1<---");
|
||||||
|
});
|
||||||
|
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
System.out.println("2--->" + Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(3000);
|
||||||
|
System.out.println("2<---");
|
||||||
|
});
|
||||||
|
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
System.out.println("3--->" + Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(3000);
|
||||||
|
System.out.println("3<---");
|
||||||
|
});
|
||||||
|
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
System.out.println("4--->" + Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(3000);
|
||||||
|
System.out.println("4<---");
|
||||||
|
});
|
||||||
|
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
System.out.println("5--->" + Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(3000);
|
||||||
|
System.out.println("5<---");
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
threadPool.execute(()->{
|
||||||
|
System.out.println("6--->"+Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(60000);
|
||||||
|
System.out.println("6<---");
|
||||||
|
});
|
||||||
|
|
||||||
|
threadPool.execute(()->{
|
||||||
|
System.out.println("7--->"+Thread.currentThread().getName());
|
||||||
|
ThreadUtil.sleep(70000);
|
||||||
|
System.out.println("7<---");
|
||||||
|
});*/
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
ThreadUtil.sleep(300000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultThreadFactory implements ThreadFactory {
|
||||||
|
private static final AtomicInteger poolNumber = new AtomicInteger(1);
|
||||||
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
private final String namePrefix;
|
||||||
|
|
||||||
|
public DefaultThreadFactory(String poolName) {
|
||||||
|
if (StrUtil.isBlank(poolName)) namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
|
||||||
|
else namePrefix = poolName + "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultThreadFactory() {
|
||||||
|
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Thread newThread(@NotNull Runnable r) {
|
||||||
|
String name = namePrefix + threadNumber.getAndIncrement();
|
||||||
|
log.debug("创建新线程:{}", name);
|
||||||
|
Thread t = new Thread(r, name) {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
super.run();
|
||||||
|
} finally {
|
||||||
|
int i = threadNumber.decrementAndGet();
|
||||||
|
log.debug("线程结束:{},剩余:{}", this.getName(), i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (t.isDaemon()) t.setDaemon(false);
|
||||||
|
if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);
|
||||||
|
t.setUncaughtExceptionHandler((t1, e) -> log.error("线程异常:{}", t1.getName(), e));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,693 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class WindowBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E> {
|
||||||
|
|
||||||
|
private final int windowCapacity;
|
||||||
|
private final int totalCapacity;
|
||||||
|
private final AtomicInteger totalElementCount = new AtomicInteger();
|
||||||
|
private final AtomicInteger windowElementCount = new AtomicInteger();
|
||||||
|
private final AtomicInteger standbyElementCount = new AtomicInteger();
|
||||||
|
private final ReentrantLock takeLock = new ReentrantLock();
|
||||||
|
private final Condition notEmpty = takeLock.newCondition();
|
||||||
|
private final ReentrantLock putLock = new ReentrantLock();
|
||||||
|
private final Condition notFull = putLock.newCondition();
|
||||||
|
private Node<E> head;
|
||||||
|
private Node<E> border;
|
||||||
|
private Node<E> last;
|
||||||
|
|
||||||
|
public WindowBlockingQueue() {
|
||||||
|
this(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowBlockingQueue(int totalCapacity, int windowCapacity) {
|
||||||
|
if (windowCapacity <= 0 || totalCapacity < windowCapacity) throw new IllegalArgumentException();
|
||||||
|
this.totalCapacity = totalCapacity;
|
||||||
|
this.windowCapacity = windowCapacity;
|
||||||
|
head = border = last = new Node<>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowBlockingQueue(Collection<? extends E> c) {
|
||||||
|
this(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
int n = 0;
|
||||||
|
for (E e : c) {
|
||||||
|
if (e == null)
|
||||||
|
throw new NullPointerException();
|
||||||
|
if (n == windowCapacity)
|
||||||
|
throw new IllegalStateException("Queue full");
|
||||||
|
enqueue(new Node<>(e));
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
windowElementCount.set(n);
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signalNotEmpty() {
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
notEmpty.signal();
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void signalNotFull() {
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
notFull.signal();
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enqueue(Node<E> node) {
|
||||||
|
last = last.next = node;
|
||||||
|
totalElementCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private E dequeue() {
|
||||||
|
Node<E> h = head;
|
||||||
|
Node<E> first = h.next;
|
||||||
|
h.next = h;
|
||||||
|
head = first;
|
||||||
|
E x = first.item;
|
||||||
|
first.item = null;
|
||||||
|
totalElementCount.decrementAndGet();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fullyLock() {
|
||||||
|
putLock.lock();
|
||||||
|
takeLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void fullyUnlock() {
|
||||||
|
takeLock.unlock();
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return windowElementCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int remainingCapacity() {
|
||||||
|
return windowCapacity - windowElementCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(E e) throws InterruptedException {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
int c = -1;
|
||||||
|
Node<E> node = new Node<>(e);
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
final AtomicInteger count = this.windowElementCount;
|
||||||
|
putLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (count.get() == windowCapacity) {
|
||||||
|
notFull.await();
|
||||||
|
}
|
||||||
|
enqueue(node);
|
||||||
|
c = count.getAndIncrement();
|
||||||
|
if (c + 1 < windowCapacity)
|
||||||
|
notFull.signal();
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == 0)
|
||||||
|
signalNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
long nanos = unit.toNanos(timeout);
|
||||||
|
int c = -1;
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
final AtomicInteger count = this.windowElementCount;
|
||||||
|
putLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (count.get() == windowCapacity) {
|
||||||
|
if (nanos <= 0)
|
||||||
|
return false;
|
||||||
|
nanos = notFull.awaitNanos(nanos);
|
||||||
|
}
|
||||||
|
enqueue(new Node<E>(e));
|
||||||
|
c = count.getAndIncrement();
|
||||||
|
if (c + 1 < windowCapacity)
|
||||||
|
notFull.signal();
|
||||||
|
} finally {
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c == 0)
|
||||||
|
signalNotEmpty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean offer(E e) {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
boolean offered = false;
|
||||||
|
int c = windowElementCount.get();
|
||||||
|
if (c == windowCapacity) return offered;
|
||||||
|
Node<E> node = new Node<>(e);
|
||||||
|
final ReentrantLock putLock = this.putLock;
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
if (windowElementCount.get() < windowCapacity) {
|
||||||
|
enqueue(node);
|
||||||
|
offered = true;
|
||||||
|
c = windowElementCount.incrementAndGet();
|
||||||
|
border = node;
|
||||||
|
if (c < windowCapacity) notFull.signal();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// log.debug("1、添加元素:总数:{}、窗口区:{}、备份区:{}", totalElementCount.get(), windowElementCount.get(), standbyElementCount.get());
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c > 0) signalNotEmpty();
|
||||||
|
return offered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean offerStandby(E e) {
|
||||||
|
if (e == null) throw new NullPointerException();
|
||||||
|
boolean offered = false;
|
||||||
|
int c = totalElementCount.get();
|
||||||
|
if (c == totalCapacity) return offered;
|
||||||
|
Node<E> node = new Node<>(e);
|
||||||
|
putLock.lock();
|
||||||
|
try {
|
||||||
|
if (totalElementCount.get() < totalCapacity) {
|
||||||
|
enqueue(node);
|
||||||
|
offered = true;
|
||||||
|
c = windowElementCount.get();
|
||||||
|
if (c + 1 <= windowCapacity) {
|
||||||
|
border = node;
|
||||||
|
windowElementCount.incrementAndGet();
|
||||||
|
} else {
|
||||||
|
standbyElementCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
if (c + 1 < windowCapacity) notFull.signal();
|
||||||
|
}
|
||||||
|
c = windowElementCount.get();
|
||||||
|
} finally {
|
||||||
|
log.debug("3、添加元素:总数:{}、窗口区:{}、备份区:{}", totalElementCount.get(), windowElementCount.get(), standbyElementCount.get());
|
||||||
|
putLock.unlock();
|
||||||
|
}
|
||||||
|
if (c != 0) signalNotEmpty();
|
||||||
|
return offered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E take() throws InterruptedException {
|
||||||
|
E x;
|
||||||
|
int c = -1;
|
||||||
|
takeLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (windowElementCount.get() == 0) {
|
||||||
|
notEmpty.await();
|
||||||
|
}
|
||||||
|
x = dequeue();
|
||||||
|
if (border.next == null) {
|
||||||
|
c = windowElementCount.decrementAndGet();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyElementCount.decrementAndGet();
|
||||||
|
c = windowElementCount.get();
|
||||||
|
}
|
||||||
|
if (c > 0) notEmpty.signal();
|
||||||
|
} finally {
|
||||||
|
log.debug("1、提取元素:总数:{}、窗口:{}、备份区:{}", totalElementCount.get(), windowElementCount.get(), standbyElementCount.get());
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
if (c != windowCapacity) signalNotFull();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
E x = null;
|
||||||
|
int c = -1;
|
||||||
|
long nanos = unit.toNanos(timeout);
|
||||||
|
takeLock.lockInterruptibly();
|
||||||
|
try {
|
||||||
|
while (windowElementCount.get() == 0) {
|
||||||
|
if (nanos <= 0) return null;
|
||||||
|
nanos = notEmpty.awaitNanos(nanos);
|
||||||
|
}
|
||||||
|
x = dequeue();
|
||||||
|
if (border.next == null) {
|
||||||
|
c = windowElementCount.decrementAndGet();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyElementCount.decrementAndGet();
|
||||||
|
c = windowElementCount.get();
|
||||||
|
}
|
||||||
|
if (c > 0) notEmpty.signal();
|
||||||
|
} finally {
|
||||||
|
// log.debug("2、提取元素:总数:{}、窗口:{}、备份区:{}", totalElementCount.get(), windowElementCount.get(), standbyElementCount.get());
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
if (c != windowCapacity) signalNotFull();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E poll() {
|
||||||
|
if (windowElementCount.get() == 0) return null;
|
||||||
|
E x = null;
|
||||||
|
int c = -1;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
if (windowElementCount.get() > 0) {
|
||||||
|
x = dequeue();
|
||||||
|
if (border.next == null) {
|
||||||
|
c = windowElementCount.decrementAndGet();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyElementCount.decrementAndGet();
|
||||||
|
c = windowElementCount.get();
|
||||||
|
}
|
||||||
|
if (c > 0) notEmpty.signal();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
log.debug("3、提取元素:总数:{}、窗口:{}、备份区:{}", totalElementCount.get(), windowElementCount.get(), standbyElementCount.get());
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
if (c != windowCapacity) signalNotFull();
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public E peek() {
|
||||||
|
if (windowElementCount.get() == 0)
|
||||||
|
return null;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
Node<E> first = head.next;
|
||||||
|
if (first == null)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return first.item;
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlink(Node<E> p, Node<E> trail, boolean overBorder) {
|
||||||
|
totalElementCount.getAndDecrement();
|
||||||
|
if (p == border || !overBorder) {
|
||||||
|
if (border.next == null) {
|
||||||
|
windowElementCount.getAndDecrement();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyElementCount.decrementAndGet();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
standbyElementCount.decrementAndGet();
|
||||||
|
}
|
||||||
|
p.item = null;
|
||||||
|
trail.next = p.next;
|
||||||
|
if (last == p) last = trail;
|
||||||
|
|
||||||
|
if (windowElementCount.get() == windowCapacity) notFull.signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
boolean overBorder = false;
|
||||||
|
for (Node<E> trail = head, p = trail.next;
|
||||||
|
p != null;
|
||||||
|
trail = p, p = p.next) {
|
||||||
|
if (!overBorder) overBorder = trail == border;
|
||||||
|
if (o.equals(p.item)) {
|
||||||
|
unlink(p, trail, overBorder);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
log.debug("1、删除元素:总数:{}、窗口:{}、备份区:{}", totalElementCount.get(), windowElementCount.get(), standbyElementCount.get());
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
for (Node<E> p = head.next; p != null; p = p.next)
|
||||||
|
if (o.equals(p.item))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] toArray() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
int size = windowElementCount.get();
|
||||||
|
Object[] a = new Object[size];
|
||||||
|
int k = 0;
|
||||||
|
for (Node<E> p = head.next; p != null; p = p.next)
|
||||||
|
a[k++] = p.item;
|
||||||
|
return a;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T[] toArray(T[] a) {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
int size = windowElementCount.get();
|
||||||
|
if (a.length < size)
|
||||||
|
a = (T[]) java.lang.reflect.Array.newInstance
|
||||||
|
(a.getClass().getComponentType(), size);
|
||||||
|
|
||||||
|
int k = 0;
|
||||||
|
for (Node<E> p = head.next; p != null; p = p.next)
|
||||||
|
a[k++] = (T) p.item;
|
||||||
|
if (a.length > k)
|
||||||
|
a[k] = null;
|
||||||
|
return a;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
Node<E> p = head.next;
|
||||||
|
if (p == null)
|
||||||
|
return "[]";
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append('[');
|
||||||
|
for (; ; ) {
|
||||||
|
E e = p.item;
|
||||||
|
sb.append(e == this ? "(this Collection)" : e);
|
||||||
|
p = p.next;
|
||||||
|
if (p == null)
|
||||||
|
return sb.append(']').toString();
|
||||||
|
sb.append(',').append(' ');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
for (Node<E> p, h = head; (p = h.next) != null; h = p) {
|
||||||
|
h.next = h;
|
||||||
|
p.item = null;
|
||||||
|
}
|
||||||
|
head = last;
|
||||||
|
totalElementCount.getAndSet(0);
|
||||||
|
standbyElementCount.getAndSet(0);
|
||||||
|
if (windowElementCount.getAndSet(0) == windowCapacity) notFull.signal();
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int drainTo(Collection<? super E> c) {
|
||||||
|
return drainTo(c, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int drainTo(Collection<? super E> c, int maxElements) {
|
||||||
|
if (c == null)
|
||||||
|
throw new NullPointerException();
|
||||||
|
if (c == this)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (maxElements <= 0)
|
||||||
|
return 0;
|
||||||
|
boolean signalNotFull = false;
|
||||||
|
final ReentrantLock takeLock = this.takeLock;
|
||||||
|
takeLock.lock();
|
||||||
|
try {
|
||||||
|
int n = Math.min(maxElements, windowElementCount.get());
|
||||||
|
|
||||||
|
Node<E> h = head;
|
||||||
|
int i = 0;
|
||||||
|
try {
|
||||||
|
while (i < n) {
|
||||||
|
Node<E> p = h.next;
|
||||||
|
c.add(p.item);
|
||||||
|
p.item = null;
|
||||||
|
h.next = h;
|
||||||
|
h = p;
|
||||||
|
++i;
|
||||||
|
|
||||||
|
if (border.next == null) {
|
||||||
|
windowElementCount.decrementAndGet();
|
||||||
|
} else {
|
||||||
|
border = border.next;
|
||||||
|
standbyElementCount.decrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
} finally {
|
||||||
|
if (i > 0) {
|
||||||
|
head = h;
|
||||||
|
totalElementCount.getAndAdd(-i);
|
||||||
|
signalNotFull = (windowElementCount.get() == windowCapacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
takeLock.unlock();
|
||||||
|
if (signalNotFull)
|
||||||
|
signalNotFull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<E> iterator() {
|
||||||
|
return new Itr();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spliterator<E> spliterator() {
|
||||||
|
return new LBQSpliterator<E>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Node<E> {
|
||||||
|
E item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of:
|
||||||
|
* - the real successor Node
|
||||||
|
* - this Node, meaning the successor is head.next
|
||||||
|
* - null, meaning there is no successor (this is the last node)
|
||||||
|
*/
|
||||||
|
Node<E> next;
|
||||||
|
|
||||||
|
Node(E x) {
|
||||||
|
item = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class LBQSpliterator<E> implements Spliterator<E> {
|
||||||
|
static final int MAX_BATCH = 1 << 25;
|
||||||
|
final WindowBlockingQueue<E> queue;
|
||||||
|
Node<E> current;
|
||||||
|
int batch;
|
||||||
|
boolean exhausted;
|
||||||
|
long est;
|
||||||
|
|
||||||
|
LBQSpliterator(WindowBlockingQueue<E> queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
this.est = queue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long estimateSize() {
|
||||||
|
return est;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spliterator<E> trySplit() {
|
||||||
|
Node<E> h;
|
||||||
|
final WindowBlockingQueue<E> q = this.queue;
|
||||||
|
int b = batch;
|
||||||
|
int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1;
|
||||||
|
if (!exhausted &&
|
||||||
|
((h = current) != null || (h = q.head.next) != null) &&
|
||||||
|
h.next != null) {
|
||||||
|
Object[] a = new Object[n];
|
||||||
|
int i = 0;
|
||||||
|
Node<E> p = current;
|
||||||
|
q.fullyLock();
|
||||||
|
try {
|
||||||
|
if (p != null || (p = q.head.next) != null) {
|
||||||
|
do {
|
||||||
|
if ((a[i] = p.item) != null)
|
||||||
|
++i;
|
||||||
|
} while ((p = p.next) != null && i < n);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
q.fullyUnlock();
|
||||||
|
}
|
||||||
|
if ((current = p) == null) {
|
||||||
|
est = 0L;
|
||||||
|
exhausted = true;
|
||||||
|
} else if ((est -= i) < 0L)
|
||||||
|
est = 0L;
|
||||||
|
if (i > 0) {
|
||||||
|
batch = i;
|
||||||
|
return Spliterators.spliterator
|
||||||
|
(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL |
|
||||||
|
Spliterator.CONCURRENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEachRemaining(Consumer<? super E> action) {
|
||||||
|
if (action == null) throw new NullPointerException();
|
||||||
|
final WindowBlockingQueue<E> q = this.queue;
|
||||||
|
if (!exhausted) {
|
||||||
|
exhausted = true;
|
||||||
|
Node<E> p = current;
|
||||||
|
do {
|
||||||
|
E e = null;
|
||||||
|
q.fullyLock();
|
||||||
|
try {
|
||||||
|
if (p == null)
|
||||||
|
p = q.head.next;
|
||||||
|
while (p != null) {
|
||||||
|
e = p.item;
|
||||||
|
p = p.next;
|
||||||
|
if (e != null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
q.fullyUnlock();
|
||||||
|
}
|
||||||
|
if (e != null)
|
||||||
|
action.accept(e);
|
||||||
|
} while (p != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryAdvance(Consumer<? super E> action) {
|
||||||
|
if (action == null) throw new NullPointerException();
|
||||||
|
final WindowBlockingQueue<E> q = this.queue;
|
||||||
|
if (!exhausted) {
|
||||||
|
E e = null;
|
||||||
|
q.fullyLock();
|
||||||
|
try {
|
||||||
|
if (current == null)
|
||||||
|
current = q.head.next;
|
||||||
|
while (current != null) {
|
||||||
|
e = current.item;
|
||||||
|
current = current.next;
|
||||||
|
if (e != null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
q.fullyUnlock();
|
||||||
|
}
|
||||||
|
if (current == null)
|
||||||
|
exhausted = true;
|
||||||
|
if (e != null) {
|
||||||
|
action.accept(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int characteristics() {
|
||||||
|
return Spliterator.ORDERED | Spliterator.NONNULL |
|
||||||
|
Spliterator.CONCURRENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Itr implements Iterator<E> {
|
||||||
|
private Node<E> current;
|
||||||
|
private Node<E> lastRet;
|
||||||
|
private E currentElement;
|
||||||
|
|
||||||
|
Itr() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
current = head.next;
|
||||||
|
if (current != null)
|
||||||
|
currentElement = current.item;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next live successor of p, or null if no such.
|
||||||
|
* <p>
|
||||||
|
* Unlike other traversal methods, iterators need to handle both:
|
||||||
|
* - dequeued nodes (p.next == p)
|
||||||
|
* - (possibly multiple) interior removed nodes (p.item == null)
|
||||||
|
*/
|
||||||
|
private Node<E> nextNode(Node<E> p) {
|
||||||
|
for (; ; ) {
|
||||||
|
Node<E> s = p.next;
|
||||||
|
if (s == p)
|
||||||
|
return head.next;
|
||||||
|
if (s == null || s.item != null)
|
||||||
|
return s;
|
||||||
|
p = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public E next() {
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
if (current == null)
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
E x = currentElement;
|
||||||
|
lastRet = current;
|
||||||
|
current = nextNode(current);
|
||||||
|
currentElement = (current == null) ? null : current.item;
|
||||||
|
return x;
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
if (lastRet == null)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
fullyLock();
|
||||||
|
try {
|
||||||
|
Node<E> node = lastRet;
|
||||||
|
lastRet = null;
|
||||||
|
boolean overBorder = false;
|
||||||
|
for (Node<E> trail = head, p = trail.next;
|
||||||
|
p != null;
|
||||||
|
trail = p, p = p.next) {
|
||||||
|
if (!overBorder) overBorder = trail == border;
|
||||||
|
if (p == node) {
|
||||||
|
unlink(p, trail, overBorder);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fullyUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.njzscloud.common.core.tree;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.njzscloud.common.core.utils.GroupUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树型结构工具
|
||||||
|
*/
|
||||||
|
public class Tree {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建树形数据(倒序)
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param rootId 根节点 ID
|
||||||
|
* @param <T> 节点 ID 类型
|
||||||
|
* @return List<? extends TreeNode<T>>
|
||||||
|
*/
|
||||||
|
public static <T> List<? extends TreeNode<T>> listToTreeDesc(List<? extends TreeNode<T>> src, T rootId) {
|
||||||
|
return listToTree(src.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()), TreeNode::getId, TreeNode::getPid, TreeNode::setChildren, rootId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建树形数据(正序)
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param rootId 根节点 ID
|
||||||
|
* @param <T> 节点 ID 类型
|
||||||
|
* @return List<? extends TreeNode<T>>
|
||||||
|
*/
|
||||||
|
public static <T> List<? extends TreeNode<T>> listToTreeAsc(List<? extends TreeNode<T>> src, T rootId) {
|
||||||
|
return listToTree(src.stream().sorted().collect(Collectors.toList()), TreeNode::getId, TreeNode::getPid, TreeNode::setChildren, rootId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建树形数据(排序)
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param idFn id 提供函数
|
||||||
|
* @param pidFn pid 提供函数
|
||||||
|
* @param setChildrenFn 子节点设置函数
|
||||||
|
* @param rootId 根节点 ID
|
||||||
|
* @param reverse 是否倒序
|
||||||
|
* @param <M> 源集合元素类型
|
||||||
|
* @param <T> 节点 ID 类型
|
||||||
|
* @return List<M>
|
||||||
|
*/
|
||||||
|
public static <M extends Comparable<? super M>, T> List<M> listToTree(List<M> src, Function<M, T> idFn, Function<M, T> pidFn, BiConsumer<M, List<M>> setChildrenFn, T rootId, boolean reverse) {
|
||||||
|
if (CollUtil.isEmpty(src)) return Collections.emptyList();
|
||||||
|
if (reverse) {
|
||||||
|
src = src
|
||||||
|
.stream()
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<T, List<M>> pid_treeNode_map = GroupUtil.k_a(src, pidFn);
|
||||||
|
|
||||||
|
for (M m : src) {
|
||||||
|
setChildrenFn.accept(m, pid_treeNode_map.get(idFn.apply(m)));
|
||||||
|
}
|
||||||
|
return CollUtil.emptyIfNull(pid_treeNode_map.get(rootId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建树形数据(不排序)
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param idFn id 提供函数
|
||||||
|
* @param pidFn pid 提供函数
|
||||||
|
* @param setChildrenFn 子节点设置函数
|
||||||
|
* @param rootId 根节点 ID
|
||||||
|
* @param <M> 源集合元素类型
|
||||||
|
* @param <T> 节点 ID 类型
|
||||||
|
* @return List<M>
|
||||||
|
*/
|
||||||
|
public static <M, T> List<M> listToTree(List<M> src, Function<M, T> idFn, Function<M, T> pidFn, BiConsumer<M, List<M>> setChildrenFn, T rootId) {
|
||||||
|
if (CollUtil.isEmpty(src)) return Collections.emptyList();
|
||||||
|
|
||||||
|
Map<T, List<M>> pid_treeNode_map = GroupUtil.k_a(src, pidFn);
|
||||||
|
|
||||||
|
for (M m : src) {
|
||||||
|
setChildrenFn.accept(m, pid_treeNode_map.get(idFn.apply(m)));
|
||||||
|
}
|
||||||
|
return CollUtil.emptyIfNull(pid_treeNode_map.get(rootId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扁平化树
|
||||||
|
*
|
||||||
|
* @param src 源集合 树形结构
|
||||||
|
* @param getChildrenFn 子节点获取函数
|
||||||
|
* @param convert 转换函数, 源集合 元素 -> 结果集合 元素
|
||||||
|
* @param <S> 源集合 元素类型
|
||||||
|
* @param <D> 结果集合 元素类型
|
||||||
|
* @return List<D>
|
||||||
|
*/
|
||||||
|
public static <S, D> List<D> treeToList(List<S> src, Function<S, List<S>> getChildrenFn, Function<S, D> convert) {
|
||||||
|
List<D> dest;
|
||||||
|
if (CollUtil.isNotEmpty(src)) {
|
||||||
|
dest = new ArrayList<>();
|
||||||
|
treeToList(src, dest, getChildrenFn, convert);
|
||||||
|
} else {
|
||||||
|
dest = Collections.emptyList();
|
||||||
|
}
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扁平化树
|
||||||
|
*
|
||||||
|
* @param src 源集合 树形结构
|
||||||
|
* @param dest 结果集合
|
||||||
|
* @param getChildrenFn 子节点获取函数
|
||||||
|
* @param convert 转换函数, 源集合 元素 -> 结果集合 元素
|
||||||
|
* @param <S> 源集合 元素类型
|
||||||
|
* @param <D> 结果集合 元素类型
|
||||||
|
*/
|
||||||
|
public static <S, D> void treeToList(List<S> src, List<D> dest, Function<S, List<S>> getChildrenFn, Function<S, D> convert) {
|
||||||
|
if (CollUtil.isNotEmpty(src)) {
|
||||||
|
for (S s : src) {
|
||||||
|
D d = convert.apply(s);
|
||||||
|
dest.add(d);
|
||||||
|
List<S> children = getChildrenFn.apply(s);
|
||||||
|
if (CollUtil.isNotEmpty(children)) {
|
||||||
|
Tree.treeToList(children, dest, getChildrenFn, convert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.njzscloud.common.core.tree;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树结点
|
||||||
|
*
|
||||||
|
* @param <T> Id 的数据类型
|
||||||
|
*/
|
||||||
|
public interface TreeNode<T> extends Comparable<TreeNode<T>> {
|
||||||
|
/**
|
||||||
|
* 节点 ID
|
||||||
|
*
|
||||||
|
* @return T id
|
||||||
|
*/
|
||||||
|
T getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点 上级 ID
|
||||||
|
*
|
||||||
|
* @return T pid
|
||||||
|
*/
|
||||||
|
T getPid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int getSort();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子节点
|
||||||
|
*
|
||||||
|
* @return List<? extends TreeNode<T>>
|
||||||
|
*/
|
||||||
|
List<? extends TreeNode<T>> getChildren();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置子节点
|
||||||
|
*
|
||||||
|
* @param children 子节点
|
||||||
|
*/
|
||||||
|
void setChildren(List<? extends TreeNode<T>> children);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compareTo(TreeNode<T> o) {
|
||||||
|
return this.getSort() - o.getSort();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.njzscloud.common.core.tuple;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2 元组
|
||||||
|
*
|
||||||
|
* @param <_0>
|
||||||
|
* @param <_1>
|
||||||
|
*/
|
||||||
|
public class Tuple2<_0, _1> implements Iterable<Object> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元组大小
|
||||||
|
*/
|
||||||
|
public final int size = 2;
|
||||||
|
/**
|
||||||
|
* 第 0 个数据
|
||||||
|
*/
|
||||||
|
protected final _0 _0_;
|
||||||
|
/**
|
||||||
|
* 第 1 个数据
|
||||||
|
*/
|
||||||
|
protected final _1 _1_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
*/
|
||||||
|
protected Tuple2(_0 _0_, _1 _1_) {
|
||||||
|
this._0_ = _0_;
|
||||||
|
this._1_ = _1_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Tuple2
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @return Tuple2<__0, __1>
|
||||||
|
*/
|
||||||
|
public static <__0, __1> Tuple2<__0, __1> create(__0 _0_, __1 _1_) {
|
||||||
|
return new Tuple2<>(_0_, _1_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 0 个数据
|
||||||
|
*/
|
||||||
|
public _0 get_0() {
|
||||||
|
return _0_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 1 个数据
|
||||||
|
*/
|
||||||
|
public _1 get_1() {
|
||||||
|
return _1_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>按索引获取数据</p>
|
||||||
|
* <p>索引越界将返回 null</p>
|
||||||
|
*
|
||||||
|
* @param index 索引
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return (T) this._0_;
|
||||||
|
case 1:
|
||||||
|
return (T) this._1_;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取迭代器
|
||||||
|
*
|
||||||
|
* @return Iterator<Object>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterator<Object> iterator() {
|
||||||
|
return Collections.unmodifiableList(this.toList()).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 List
|
||||||
|
*
|
||||||
|
* @return List<Object>
|
||||||
|
*/
|
||||||
|
public List<Object> toList() {
|
||||||
|
return Arrays.asList(this.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 数组
|
||||||
|
*
|
||||||
|
* @return Object[]
|
||||||
|
*/
|
||||||
|
public Object[] toArray() {
|
||||||
|
return new Object[]{this._0_, this._1_};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Tuple2<?, ?> tuple2 = (Tuple2<?, ?>) o;
|
||||||
|
return Objects.equals(_0_, tuple2._0_) && Objects.equals(_1_, tuple2._1_);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(_0_, _1_);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.njzscloud.common.core.tuple;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3 元组
|
||||||
|
*
|
||||||
|
* @param <_0>
|
||||||
|
* @param <_1>
|
||||||
|
* @param <_2>
|
||||||
|
*/
|
||||||
|
public class Tuple3<_0, _1, _2> extends Tuple2<_0, _1> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元组大小
|
||||||
|
*/
|
||||||
|
public final int size = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第 2 个数据
|
||||||
|
*/
|
||||||
|
protected final _2 _2_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @param _2_ 第 2 个数据
|
||||||
|
*/
|
||||||
|
protected Tuple3(_0 _0_, _1 _1_, _2 _2_) {
|
||||||
|
super(_0_, _1_);
|
||||||
|
this._2_ = _2_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Tuple3
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @param _2_ 第 2 个数据
|
||||||
|
* @return Tuple3<__0, __1, __2>
|
||||||
|
*/
|
||||||
|
public static <__0, __1, __2> Tuple3<__0, __1, __2> create(__0 _0_, __1 _1_, __2 _2_) {
|
||||||
|
return new Tuple3<>(_0_, _1_, _2_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 2 个数据
|
||||||
|
*/
|
||||||
|
public _2 get_2() {
|
||||||
|
return _2_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>按索引获取数据</p>
|
||||||
|
* <p>索引越界将返回 null</p>
|
||||||
|
*
|
||||||
|
* @param index 索引
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return (T) this._0_;
|
||||||
|
case 1:
|
||||||
|
return (T) this._1_;
|
||||||
|
case 2:
|
||||||
|
return (T) this._2_;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 数组
|
||||||
|
*
|
||||||
|
* @return Object[]
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
return new Object[]{this._0_, this._1_, this._2_};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.njzscloud.common.core.tuple;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4 元组
|
||||||
|
*
|
||||||
|
* @param <_0>
|
||||||
|
* @param <_1>
|
||||||
|
* @param <_2>
|
||||||
|
* @param <_3>
|
||||||
|
*/
|
||||||
|
public class Tuple4<_0, _1, _2, _3> extends Tuple3<_0, _1, _2> {
|
||||||
|
/**
|
||||||
|
* 元组大小
|
||||||
|
*/
|
||||||
|
public final int size = 4;
|
||||||
|
/**
|
||||||
|
* 第 3 个数据
|
||||||
|
*/
|
||||||
|
protected final _3 _3_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @param _2_ 第 2 个数据
|
||||||
|
* @param _3_ 第 3 个数据
|
||||||
|
*/
|
||||||
|
protected Tuple4(_0 _0_, _1 _1_, _2 _2_, _3 _3_) {
|
||||||
|
super(_0_, _1_, _2_);
|
||||||
|
this._3_ = _3_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Tuple4
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @param _2_ 第 2 个数据
|
||||||
|
* @param _3_ 第 3 个数据
|
||||||
|
* @return Tuple3<__0, __1, __2, __3>
|
||||||
|
*/
|
||||||
|
public static <__0, __1, __2, __3> Tuple4<__0, __1, __2, __3> create(__0 _0_, __1 _1_, __2 _2_, __3 _3_) {
|
||||||
|
return new Tuple4<>(_0_, _1_, _2_, _3_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 3 个数据
|
||||||
|
*/
|
||||||
|
public _3 get_3() {
|
||||||
|
return _3_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>按索引获取数据</p>
|
||||||
|
* <p>索引越界将返回 null</p>
|
||||||
|
*
|
||||||
|
* @param index 索引
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
return (T) this._0_;
|
||||||
|
case 1:
|
||||||
|
return (T) this._1_;
|
||||||
|
case 2:
|
||||||
|
return (T) this._2_;
|
||||||
|
case 3:
|
||||||
|
return (T) this._3_;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 数组
|
||||||
|
*
|
||||||
|
* @return Object[]
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
return new Object[]{this._0_, this._1_, this._2_, this._3_};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see sun.nio.fs.Globs
|
||||||
|
*/
|
||||||
|
public class Globs {
|
||||||
|
private static final String regexMetaChars = ".^$+{[]|()";
|
||||||
|
private static final String globMetaChars = "\\*?[{";
|
||||||
|
private static final char EOL = 0; // TBD
|
||||||
|
|
||||||
|
private Globs() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRegexMeta(char c) {
|
||||||
|
return regexMetaChars.indexOf(c) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isGlobMeta(char c) {
|
||||||
|
return globMetaChars.indexOf(c) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char next(String glob, int i) {
|
||||||
|
if (i < glob.length()) {
|
||||||
|
return glob.charAt(i);
|
||||||
|
}
|
||||||
|
return EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a regex pattern from the given glob expression.
|
||||||
|
*
|
||||||
|
* @throws PatternSyntaxException
|
||||||
|
*/
|
||||||
|
private static String toRegexPattern(String globPattern, boolean isDos) {
|
||||||
|
boolean inGroup = false;
|
||||||
|
StringBuilder regex = new StringBuilder("^");
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < globPattern.length()) {
|
||||||
|
char c = globPattern.charAt(i++);
|
||||||
|
switch (c) {
|
||||||
|
case '\\':
|
||||||
|
// escape special characters
|
||||||
|
if (i == globPattern.length()) {
|
||||||
|
throw new PatternSyntaxException("No character to escape", globPattern, i - 1);
|
||||||
|
}
|
||||||
|
char next = globPattern.charAt(i++);
|
||||||
|
if (isGlobMeta(next) || isRegexMeta(next)) {
|
||||||
|
regex.append('\\');
|
||||||
|
}
|
||||||
|
regex.append(next);
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
if (isDos) {
|
||||||
|
regex.append("\\\\");
|
||||||
|
} else {
|
||||||
|
regex.append(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
// don't match name separator in class
|
||||||
|
if (isDos) {
|
||||||
|
regex.append("[[^\\\\]&&[");
|
||||||
|
} else {
|
||||||
|
regex.append("[[^/]&&[");
|
||||||
|
}
|
||||||
|
if (next(globPattern, i) == '^') {
|
||||||
|
// escape the regex negation char if it appears
|
||||||
|
regex.append("\\^");
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
// negation
|
||||||
|
if (next(globPattern, i) == '!') {
|
||||||
|
regex.append('^');
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// hyphen allowed at start
|
||||||
|
if (next(globPattern, i) == '-') {
|
||||||
|
regex.append('-');
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean hasRangeStart = false;
|
||||||
|
char last = 0;
|
||||||
|
while (i < globPattern.length()) {
|
||||||
|
c = globPattern.charAt(i++);
|
||||||
|
if (c == ']') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == '/' || (isDos && c == '\\')) {
|
||||||
|
throw new PatternSyntaxException("Explicit 'name separator' in class",
|
||||||
|
globPattern, i - 1);
|
||||||
|
}
|
||||||
|
// TBD: how to specify ']' in a class?
|
||||||
|
if (c == '\\' || c == '[' ||
|
||||||
|
c == '&' && next(globPattern, i) == '&') {
|
||||||
|
// escape '\', '[' or "&&" for regex class
|
||||||
|
regex.append('\\');
|
||||||
|
}
|
||||||
|
regex.append(c);
|
||||||
|
|
||||||
|
if (c == '-') {
|
||||||
|
if (!hasRangeStart) {
|
||||||
|
throw new PatternSyntaxException("Invalid range",
|
||||||
|
globPattern, i - 1);
|
||||||
|
}
|
||||||
|
if ((c = next(globPattern, i++)) == EOL || c == ']') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c < last) {
|
||||||
|
throw new PatternSyntaxException("Invalid range",
|
||||||
|
globPattern, i - 3);
|
||||||
|
}
|
||||||
|
regex.append(c);
|
||||||
|
hasRangeStart = false;
|
||||||
|
} else {
|
||||||
|
hasRangeStart = true;
|
||||||
|
last = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c != ']') {
|
||||||
|
throw new PatternSyntaxException("Missing ']", globPattern, i - 1);
|
||||||
|
}
|
||||||
|
regex.append("]]");
|
||||||
|
break;
|
||||||
|
case '{':
|
||||||
|
if (inGroup) {
|
||||||
|
throw new PatternSyntaxException("Cannot nest groups",
|
||||||
|
globPattern, i - 1);
|
||||||
|
}
|
||||||
|
regex.append("(?:(?:");
|
||||||
|
inGroup = true;
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
if (inGroup) {
|
||||||
|
regex.append("))");
|
||||||
|
inGroup = false;
|
||||||
|
} else {
|
||||||
|
regex.append('}');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
if (inGroup) {
|
||||||
|
regex.append(")|(?:");
|
||||||
|
} else {
|
||||||
|
regex.append(',');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
if (next(globPattern, i) == '*') {
|
||||||
|
// crosses directory boundaries
|
||||||
|
regex.append(".*");
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
// within directory boundary
|
||||||
|
if (isDos) {
|
||||||
|
regex.append("[^\\\\]*");
|
||||||
|
} else {
|
||||||
|
regex.append("[^/]*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
if (isDos) {
|
||||||
|
regex.append("[^\\\\]");
|
||||||
|
} else {
|
||||||
|
regex.append("[^/]");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (isRegexMeta(c)) {
|
||||||
|
regex.append('\\');
|
||||||
|
}
|
||||||
|
regex.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inGroup) {
|
||||||
|
throw new PatternSyntaxException("Missing '}", globPattern, i - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return regex.append('$').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toUnixRegexPattern(String globPattern) {
|
||||||
|
return toRegexPattern(globPattern, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toWindowsRegexPattern(String globPattern) {
|
||||||
|
return toRegexPattern(globPattern, true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,625 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象分组工具
|
||||||
|
*/
|
||||||
|
public class GroupUtil {
|
||||||
|
|
||||||
|
// region K-O
|
||||||
|
|
||||||
|
// region Collection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <SV> 值类型 / 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return Map<K, SV>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, SV> k_o(Collection<SV> src, Function<SV, K> kf) {
|
||||||
|
return k_o(src, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_o(src, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 值类型 / 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, SV>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, SV>> M k_o(Collection<SV> src, Function<SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_o(src, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
|
||||||
|
|
||||||
|
return k_o(src.stream(), kf, vf, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Collection 索引
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
* <p>kf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return Map<K, VT>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, SV> k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf) {
|
||||||
|
return k_o(src, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
* <p>kf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf K 提供函数
|
||||||
|
* @param vf V 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_o(src, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
|
||||||
|
* <p>kf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf K 提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, SV>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, SV>> M k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_o(src, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
* <p>kf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf K 提供函数
|
||||||
|
* @param vf V 提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
|
||||||
|
|
||||||
|
M map = mf.get();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (SV sv : src) {
|
||||||
|
K k = kf.apply(i, sv);
|
||||||
|
if (map.containsKey(k)) throw Exceptions.error("重复键:{}", sv);
|
||||||
|
V v = vf.apply(sv);
|
||||||
|
map.put(k, v);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
* <p>vf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, Function<SV, K> kf, BiFunction<Integer, SV, V> vf) {
|
||||||
|
return k_o(src, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
* <p>vf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, Function<SV, K> kf, BiFunction<Integer, SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
|
||||||
|
|
||||||
|
M map = mf.get();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (SV sv : src) {
|
||||||
|
K k = kf.apply(sv);
|
||||||
|
if (map.containsKey(k)) throw Exceptions.error("重复键:{}", sv);
|
||||||
|
V v = vf.apply(i, sv);
|
||||||
|
map.put(k, v);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
* <p>kf vf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, V> k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, BiFunction<Integer, SV, V> vf) {
|
||||||
|
return k_o(src, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
* <p>kf vf 中可得到 源集合元素 索引</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, V>> M k_o(Collection<SV> src, BiFunction<Integer, SV, K> kf, BiFunction<Integer, SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
|
||||||
|
|
||||||
|
M map = mf.get();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (SV sv : src) {
|
||||||
|
K k = kf.apply(i, sv);
|
||||||
|
if (map.containsKey(k)) throw Exceptions.error("重复键:{}", sv);
|
||||||
|
V v = vf.apply(i, sv);
|
||||||
|
map.put(k, v);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return Map<K, SV>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, SV> k_o(Stream<SV> stream, Function<SV, K> kf) {
|
||||||
|
return k_o(stream, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, V> k_o(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_o(stream, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, SV>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, SV>> M k_o(Stream<SV> stream, Function<SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_o(stream, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, V>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, V>> M k_o(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (stream == null) return MapUtil.empty(null);
|
||||||
|
BinaryOperator<V> op = (oldVal, val) -> {
|
||||||
|
throw Exceptions.error("值【{}】与【{}】对应的键重复", Jackson.toJsonStr(oldVal), Jackson.toJsonStr(val));
|
||||||
|
};
|
||||||
|
return stream.collect(Collectors.toMap(kf, vf, op, mf));
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region K-A
|
||||||
|
|
||||||
|
// region Collection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @return Map<K, List<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, List<SV>> k_a(Collection<SV> src, Function<SV, K> kf) {
|
||||||
|
return k_a(src, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, List<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, List<V>> k_a(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_a(src, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, List<SV>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, List<SV>>> M k_a(Collection<SV> src, Function<SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_a(src, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, List<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, List<V>>> M k_a(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
|
||||||
|
return src.stream().collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toList())));
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Stream
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @return Map<K, List<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, List<SV>> k_a(Stream<SV> stream, Function<SV, K> kf) {
|
||||||
|
return k_a(stream, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, List<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, List<V>> k_a(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_a(stream, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, List<SV>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, List<SV>>> M k_a(Stream<SV> stream, Function<SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_a(stream, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, List<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, List<V>>> M k_a(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (stream == null) return MapUtil.empty(null);
|
||||||
|
return stream.collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toList())));
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region K-S
|
||||||
|
// region Collection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return Map<K, Set<SV>>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, Set<SV>> k_s(Collection<SV> src, Function<SV, K> kf) {
|
||||||
|
return k_s(src, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, Set<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, Set<V>> k_s(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_s(src, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, Set<SV>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, Set<SV>>> M k_s(Collection<SV> src, Function<SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_s(src, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param src 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, Set<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, Set<V>>> M k_s(Collection<SV> src, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
|
||||||
|
return src.stream().collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toSet())));
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 源集合元素</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @return Map<K, Set<SV>>
|
||||||
|
*/
|
||||||
|
public static <SV, K> Map<K, Set<SV>> k_s(Stream<SV> stream, Function<SV, K> kf) {
|
||||||
|
return k_s(stream, kf, it -> it, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 HashMap, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @return Map<K, Set<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V> Map<K, Set<V>> k_s(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf) {
|
||||||
|
return k_s(stream, kf, vf, HashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, Set<SV>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, M extends Map<K, Set<SV>>> M k_s(Stream<SV> stream, Function<SV, K> kf, Supplier<M> mf) {
|
||||||
|
return k_s(stream, kf, it -> it, mf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>分组</p>
|
||||||
|
* <p>Map 为 mf, 键为 kf, 值为 vf</p>
|
||||||
|
*
|
||||||
|
* @param stream 源集合
|
||||||
|
* @param kf 键提供函数
|
||||||
|
* @param vf 值提供函数
|
||||||
|
* @param mf Map 提供函数
|
||||||
|
* @param <SV> 源集合元素类型
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
|
* @param <M> Map 类型
|
||||||
|
* @return Map<K, Set<V>>
|
||||||
|
*/
|
||||||
|
public static <SV, K, V, M extends Map<K, Set<V>>> M k_s(Stream<SV> stream, Function<SV, K> kf, Function<SV, V> vf, Supplier<M> mf) {
|
||||||
|
if (stream == null) return MapUtil.empty(null);
|
||||||
|
return stream.collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toSet())));
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>缓存/Redis 等 Key</p>
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class Key {
|
||||||
|
private final String keyTpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>创建 KEY</p>
|
||||||
|
* <p>KEY 字符串模板:可用:{名称} 做为占位符</p>
|
||||||
|
*
|
||||||
|
* @param keyTpl KEY 字符串模板
|
||||||
|
* @return KEY 对象
|
||||||
|
*/
|
||||||
|
public static Key create(String keyTpl) {
|
||||||
|
Assert.notEmpty(keyTpl, () -> Exceptions.error("KEY 字符串模板不能为空"));
|
||||||
|
keyTpl = keyTpl.replaceAll("\\{\\w+}", "{}");
|
||||||
|
return new Key(keyTpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>填充 KEY 模板</p>
|
||||||
|
*
|
||||||
|
* @param params 填充参数
|
||||||
|
* @return key 值
|
||||||
|
*/
|
||||||
|
public String fill(Object... params) {
|
||||||
|
int count = StrUtil.count(keyTpl, "{}");
|
||||||
|
Assert.isTrue(count == params.length,
|
||||||
|
() -> Exceptions.error("占位符数量:[{}]与参数数量:[{}]不一致", count, params.length));
|
||||||
|
return StrUtil.format(keyTpl, params);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.njzscloud.common.core.ex.ExceptionMsg;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应信息主体
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public final class R<T> {
|
||||||
|
/**
|
||||||
|
* <p>错误码 >= 0</p>
|
||||||
|
* <p>0 --> 没有错误</p>
|
||||||
|
* <p>其他 --> 有错误</p>
|
||||||
|
*/
|
||||||
|
private final int code;
|
||||||
|
/**
|
||||||
|
* 是否成功
|
||||||
|
*/
|
||||||
|
private final boolean success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简略信息
|
||||||
|
*/
|
||||||
|
private final String msg;
|
||||||
|
/**
|
||||||
|
* 响应数据
|
||||||
|
*/
|
||||||
|
private T data;
|
||||||
|
/**
|
||||||
|
* 详细信息
|
||||||
|
*/
|
||||||
|
private Object message;
|
||||||
|
|
||||||
|
private R(T data, ExceptionMsg msg, Object message) {
|
||||||
|
this(msg.code, data, msg.msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
private R(@JsonProperty("code") int code,
|
||||||
|
@JsonProperty("data") T data,
|
||||||
|
@JsonProperty("msg") String msg,
|
||||||
|
@JsonProperty("message") Object message) {
|
||||||
|
this.data = data;
|
||||||
|
this.code = code;
|
||||||
|
this.msg = msg;
|
||||||
|
this.message = message;
|
||||||
|
this.success = code == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功,响应码:0,响应信息:成功,详细信息:null,响应数据:null
|
||||||
|
*
|
||||||
|
* @param <D> 响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
*/
|
||||||
|
public static <D> R<D> success() {
|
||||||
|
return new R<>(0, null, "成功", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功,响应码:0,响应信息:成功,详细信息:null
|
||||||
|
*
|
||||||
|
* @param data 响应数据
|
||||||
|
* @param <D> 响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
*/
|
||||||
|
public static <D> R<D> success(D data) {
|
||||||
|
return new R<>(0, data, "成功", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功,响应码:0,详细信息:null
|
||||||
|
*
|
||||||
|
* @param data 响应数据
|
||||||
|
* @param msg 响应信息
|
||||||
|
* @param <D> 响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
*/
|
||||||
|
public static <D> R<D> success(D data, String msg) {
|
||||||
|
return new R<>(0, data, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败,响应码:11111,响应信息:系统异常!,详细信息:null,响应数据:null
|
||||||
|
*
|
||||||
|
* @param <D> 响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
*/
|
||||||
|
public static <D> R<D> failed() {
|
||||||
|
return new R<>(null, ExceptionMsg.SYS_EXP_MSG, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败,详细信息:null,响应数据:null
|
||||||
|
*
|
||||||
|
* @param msg 简略错误信息
|
||||||
|
* @param <D> 响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
* @see ExceptionMsg
|
||||||
|
*/
|
||||||
|
public static <D> R<D> failed(ExceptionMsg msg) {
|
||||||
|
return new R<>(null, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <D> R<D> failed(ExceptionMsg msg, Object message) {
|
||||||
|
return new R<>(null, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <D> R<D> failed(ExceptionMsg msg, String message, Object... param) {
|
||||||
|
if (StrUtil.isNotBlank(message) && param != null && param.length > 0) {
|
||||||
|
message = StrUtil.format(message, param);
|
||||||
|
}
|
||||||
|
return new R<>(null, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败,详细信息:null
|
||||||
|
*
|
||||||
|
* @param data 响应数据
|
||||||
|
* @param msg 简略错误信息
|
||||||
|
* @param <D> 响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
* @see ExceptionMsg
|
||||||
|
*/
|
||||||
|
public static <D> R<D> failed(D data, ExceptionMsg msg) {
|
||||||
|
return new R<>(null, msg, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <D> R<D> failed(D data, ExceptionMsg msg, Object message) {
|
||||||
|
return new R<>(null, msg, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置响应数据
|
||||||
|
*
|
||||||
|
* @param data 响应数据
|
||||||
|
* @return R<D>
|
||||||
|
*/
|
||||||
|
public R<T> setData(T data) {
|
||||||
|
this.data = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>添加响应数据</p>
|
||||||
|
* <p>需确保 data 为 Map 类型且 Key 为 String,data 为 null 时,会新建</p>
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param val 值
|
||||||
|
* @return R<T>
|
||||||
|
*/
|
||||||
|
public <V> R<Map<String, V>> put(String key, V val) {
|
||||||
|
if (this.data == null) {
|
||||||
|
R<Map<String, V>> r = new R<>(this.code, new HashMap<>(), this.msg, this.message);
|
||||||
|
r.data.put(key, val);
|
||||||
|
return r;
|
||||||
|
} else if (Map.class.isAssignableFrom(data.getClass())) {
|
||||||
|
((Map<String, V>) this.data).put(key, val);
|
||||||
|
return (R<Map<String, V>>) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exceptions.error("响应信息构建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>添加响应数据</p>
|
||||||
|
* <p>需确保 data 为 List 类型,data 为 null 时,会新建</p>
|
||||||
|
*
|
||||||
|
* @param val 值
|
||||||
|
* @return R<T>
|
||||||
|
*/
|
||||||
|
public <V> R<List<V>> add(V val) {
|
||||||
|
if (this.data == null) {
|
||||||
|
R<List<V>> r = new R<>(this.code, new ArrayList<>(), this.msg, this.message);
|
||||||
|
r.data.add(val);
|
||||||
|
return r;
|
||||||
|
} else if (List.class.isAssignableFrom(data.getClass())) {
|
||||||
|
((List<V>) this.data).add(val);
|
||||||
|
return (R<List<V>>) this;
|
||||||
|
}
|
||||||
|
throw Exceptions.error("响应信息构建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置详细信息
|
||||||
|
*
|
||||||
|
* @param message 详细信息
|
||||||
|
* @return R<T>
|
||||||
|
*/
|
||||||
|
public R<T> setMessage(Object message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置详细信息
|
||||||
|
*
|
||||||
|
* @param message 消息字符串模板,占位符:{}
|
||||||
|
* @param param 占位符参数
|
||||||
|
* @return R<T>
|
||||||
|
*/
|
||||||
|
public R<T> setMessage(String message, Object... param) {
|
||||||
|
if (StrUtil.isNotBlank(message) && param != null && param.length > 0) {
|
||||||
|
message = StrUtil.format(message, param);
|
||||||
|
}
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>转换</p>
|
||||||
|
* <p>将响应数据转换为其他对象</p>
|
||||||
|
* <p>code、msg、message 不变化</p>
|
||||||
|
*
|
||||||
|
* @param converter 转换器
|
||||||
|
* @param <D> 新响应数据的类型
|
||||||
|
* @return R<D>
|
||||||
|
*/
|
||||||
|
public <D> R<D> convert(Function<T, D> converter) {
|
||||||
|
D d = converter.apply(this.data);
|
||||||
|
return new R<>(this.code, d, this.msg, this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Jackson.toJsonStr(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?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>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-email</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- junit -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- mail -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- spring-boot -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- configuration processor -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,234 @@
|
||||||
|
package com.njzscloud.common.email;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.http.constant.Mime;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import javax.mail.util.ByteArrayDataSource;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件消息
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class MailMessage {
|
||||||
|
/**
|
||||||
|
* 收件人邮箱
|
||||||
|
*/
|
||||||
|
private final List<String> tos;
|
||||||
|
/**
|
||||||
|
* 邮件主题
|
||||||
|
*/
|
||||||
|
private final String subject;
|
||||||
|
/**
|
||||||
|
* 邮件内容
|
||||||
|
*/
|
||||||
|
private final String content;
|
||||||
|
/**
|
||||||
|
* 是否为 HTML
|
||||||
|
*/
|
||||||
|
private final boolean html;
|
||||||
|
/**
|
||||||
|
* 附件列表,0-->附件名称、1-->附件内容
|
||||||
|
*/
|
||||||
|
private final List<Tuple2<String, ByteArrayDataSource>> attachmentList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建邮件消息
|
||||||
|
*
|
||||||
|
* @param tos 收件人邮箱
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @param content 邮件内容
|
||||||
|
* @param html 是否为 HTML
|
||||||
|
* @param attachmentList 附件列表,0-->附件名称、1-->附件内容
|
||||||
|
*/
|
||||||
|
private MailMessage(List<String> tos, String subject, String content, boolean html, List<Tuple2<String, ByteArrayDataSource>> attachmentList) {
|
||||||
|
this.tos = tos;
|
||||||
|
this.subject = subject;
|
||||||
|
this.content = content;
|
||||||
|
this.html = html;
|
||||||
|
this.attachmentList = attachmentList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取构建器
|
||||||
|
*
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建器
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
/**
|
||||||
|
* 收件人邮箱
|
||||||
|
*/
|
||||||
|
private List<String> tos;
|
||||||
|
/**
|
||||||
|
* 邮件主题
|
||||||
|
*/
|
||||||
|
private String subject;
|
||||||
|
/**
|
||||||
|
* 邮件内容
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* 是否为 HTML
|
||||||
|
*/
|
||||||
|
private boolean html;
|
||||||
|
/**
|
||||||
|
* 附件列表,0-->附件名称、1-->附件内容
|
||||||
|
*/
|
||||||
|
private List<Tuple2<String, ByteArrayDataSource>> attachmentList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加收件人邮箱
|
||||||
|
*
|
||||||
|
* @param to 收件人邮箱
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder addTo(String to) {
|
||||||
|
if (this.tos == null) {
|
||||||
|
this.tos = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.tos.add(to);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置收件人邮箱
|
||||||
|
*
|
||||||
|
* @param tos 收件人邮箱
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder tos(List<String> tos) {
|
||||||
|
this.tos = tos;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置邮件主题
|
||||||
|
*
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder subject(String subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置邮件内容
|
||||||
|
*
|
||||||
|
* @param content 邮件内容(常规内容)
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder content(String content) {
|
||||||
|
this.content = content;
|
||||||
|
this.html = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置邮件内容
|
||||||
|
*
|
||||||
|
* @param content 邮件内容(HTML内容)
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder htmlContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
this.html = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置附件列表
|
||||||
|
*
|
||||||
|
* @param attachmentList 附件列表,0-->附件名称、1-->附件内容
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder attachmentList(List<Tuple2<String, ByteArrayDataSource>> attachmentList) {
|
||||||
|
this.attachmentList = attachmentList;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加附件
|
||||||
|
*
|
||||||
|
* @param filename 附件名称
|
||||||
|
* @param in 附件
|
||||||
|
* @param mime 内容类型
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder addAttachment(String filename, InputStream in, String mime) {
|
||||||
|
Assert.notBlank(filename, "附件名称不能为空");
|
||||||
|
Assert.notNull(in, "附件不能为空");
|
||||||
|
Assert.notBlank(mime, "内容类型不能为空");
|
||||||
|
if (this.attachmentList == null) {
|
||||||
|
this.attachmentList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
ByteArrayDataSource dataSource;
|
||||||
|
try (InputStream is = in) {
|
||||||
|
dataSource = new ByteArrayDataSource(is, mime);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw Exceptions.error(e, "附件读取失败");
|
||||||
|
}
|
||||||
|
this.attachmentList.add(Tuple2.create(filename, dataSource));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加附件
|
||||||
|
*
|
||||||
|
* @param filename 附件名称
|
||||||
|
* @param bytes 附件
|
||||||
|
* @param mime 内容类型
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder addAttachment(String filename, byte[] bytes, String mime) {
|
||||||
|
return addAttachment(filename, new ByteArrayInputStream(bytes), mime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加附件,内容类型默认:application/octet-stream
|
||||||
|
*
|
||||||
|
* @param filename 附件名称
|
||||||
|
* @param bytes 附件
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder addAttachment(String filename, byte[] bytes) {
|
||||||
|
return addAttachment(filename, new ByteArrayInputStream(bytes), Mime.BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加附件,内容类型默认:application/octet-stream
|
||||||
|
*
|
||||||
|
* @param filename 附件名称
|
||||||
|
* @param in 附件
|
||||||
|
* @return 构建器对象
|
||||||
|
*/
|
||||||
|
public Builder addAttachment(String filename, InputStream in) {
|
||||||
|
return addAttachment(filename, in, Mime.BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建邮件消息
|
||||||
|
*
|
||||||
|
* @return MailMessage
|
||||||
|
*/
|
||||||
|
public MailMessage build() {
|
||||||
|
Assert.isTrue(this.tos != null && !this.tos.isEmpty(), "收件人邮箱不能为空");
|
||||||
|
Assert.notBlank(this.subject, "邮件主题不能为空");
|
||||||
|
return new MailMessage(this.tos, this.subject, this.content, this.html, this.attachmentList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.njzscloud.common.email.util;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import com.njzscloud.common.email.MailMessage;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.mail.MailAuthenticationException;
|
||||||
|
import org.springframework.mail.MailException;
|
||||||
|
import org.springframework.mail.MailParseException;
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import javax.mail.util.ByteArrayDataSource;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 电子邮件工具
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class EMailUtil {
|
||||||
|
private static final String FROM;
|
||||||
|
private static final JavaMailSender MAIL_SENDER;
|
||||||
|
|
||||||
|
static {
|
||||||
|
MAIL_SENDER = SpringUtil.getBean(JavaMailSender.class);
|
||||||
|
FROM = SpringUtil.getProperty("spring.mail.username");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送简单文本邮件
|
||||||
|
*
|
||||||
|
* @param to 接收者邮件
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @param content 邮件内容
|
||||||
|
*/
|
||||||
|
public void sendSimpleMail(String to, String subject, String content) {
|
||||||
|
sendSimpleMail(Collections.singletonList(to), subject, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送简单文本邮件
|
||||||
|
*
|
||||||
|
* @param tos 接收者邮件
|
||||||
|
* @param subject 邮件主题
|
||||||
|
* @param content 邮件内容
|
||||||
|
*/
|
||||||
|
public void sendSimpleMail(List<String> tos, String subject, String content) {
|
||||||
|
SimpleMailMessage message = new SimpleMailMessage();
|
||||||
|
message.setFrom(FROM);
|
||||||
|
message.setTo(tos.toArray(new String[0]));
|
||||||
|
message.setSubject(subject);
|
||||||
|
message.setText(content);
|
||||||
|
try {
|
||||||
|
MAIL_SENDER.send(message);
|
||||||
|
} catch (MailException e) {
|
||||||
|
throw Exceptions.error(e,
|
||||||
|
e instanceof MailParseException ? "邮件消息解析失败" :
|
||||||
|
e instanceof MailAuthenticationException ? "邮件服务器认证失败" : "邮件发送失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送复杂邮件
|
||||||
|
*
|
||||||
|
* @param mailMessage 消息
|
||||||
|
*/
|
||||||
|
public void sendComplexMail(MailMessage mailMessage) {
|
||||||
|
MimeMessage message = MAIL_SENDER.createMimeMessage();
|
||||||
|
try {
|
||||||
|
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||||
|
helper.setFrom(FROM);
|
||||||
|
helper.setTo(mailMessage.getTos().toArray(new String[0]));
|
||||||
|
helper.setSubject(mailMessage.getSubject());
|
||||||
|
helper.setText(mailMessage.getContent(), mailMessage.isHtml());
|
||||||
|
List<Tuple2<String, ByteArrayDataSource>> attachmentList = mailMessage.getAttachmentList();
|
||||||
|
if (attachmentList != null && !attachmentList.isEmpty()) {
|
||||||
|
for (Tuple2<String, ByteArrayDataSource> dataSource : attachmentList) {
|
||||||
|
helper.addAttachment(dataSource.get_0(), dataSource.get_1());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
throw Exceptions.error(e, "邮件创建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MAIL_SENDER.send(message);
|
||||||
|
} catch (MailException e) {
|
||||||
|
throw Exceptions.error(e,
|
||||||
|
e instanceof MailParseException ? "邮件消息解析失败" :
|
||||||
|
e instanceof MailAuthenticationException ? "邮件服务器认证失败" : "邮件发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue