master
parent
34395a4fff
commit
dd0672d31a
|
|
@ -0,0 +1,13 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 1000
|
||||||
|
|
||||||
|
[*.{java,c,cpp,kt,xml}]
|
||||||
|
indent_size = 4
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**/logs
|
||||||
|
/**/*.iml
|
||||||
|
/**/.idea
|
||||||
|
/**/target
|
||||||
|
/**/.DS_Store
|
||||||
|
/**/.njzscloud-dispose
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.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>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</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>
|
||||||
|
|
||||||
|
</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,56 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.thread.SyncUtil;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class DualCache implements Cache {
|
||||||
|
private final Cache FIRST_CACHE;
|
||||||
|
private final Cache SECOND_CACHE;
|
||||||
|
|
||||||
|
public DualCache(Cache firstCache, Cache secondCache) {
|
||||||
|
FIRST_CACHE = firstCache;
|
||||||
|
SECOND_CACHE = secondCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key) {
|
||||||
|
return SyncUtil.syncR(key, () -> FIRST_CACHE.get(key, () -> SECOND_CACHE.get(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, Supplier<T> supplier) {
|
||||||
|
return SyncUtil.syncW(key, () -> FIRST_CACHE.get(key, () -> SECOND_CACHE.get(key, supplier)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
return SyncUtil.syncW(key, () -> FIRST_CACHE.get(key, timeout, () -> SECOND_CACHE.get(key, timeout, supplier)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
SyncUtil.syncW(key, () -> {
|
||||||
|
FIRST_CACHE.put(key, value);
|
||||||
|
SECOND_CACHE.put(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, Object value, long timeout) {
|
||||||
|
SyncUtil.syncW(key, () -> {
|
||||||
|
FIRST_CACHE.put(key, value);
|
||||||
|
SECOND_CACHE.put(key, value, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String key) {
|
||||||
|
SyncUtil.syncW(key, () -> {
|
||||||
|
FIRST_CACHE.remove(key);
|
||||||
|
SECOND_CACHE.remove(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,51 @@
|
||||||
|
package com.njzscloud.common.cache;
|
||||||
|
|
||||||
|
import com.njzscloud.common.redis.util.Redis;
|
||||||
|
import io.lettuce.core.SetArgs;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class SecondCache implements Cache {
|
||||||
|
|
||||||
|
private final long timeout;
|
||||||
|
|
||||||
|
public SecondCache(long timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T get(String key) {
|
||||||
|
return Redis.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, Supplier<T> supplier) {
|
||||||
|
Object o = Redis.get(key);
|
||||||
|
if (o != null) return (T) o;
|
||||||
|
o = supplier.get();
|
||||||
|
if (o != null) Redis.set(key, o);
|
||||||
|
return (T) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key, long timeout, Supplier<T> supplier) {
|
||||||
|
Object 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value, long timeout) {
|
||||||
|
Redis.set(key, value, new SetArgs().ex(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String key, Object value) {
|
||||||
|
if (timeout > 0) Redis.set(key, value, new SetArgs().ex(timeout));
|
||||||
|
else Redis.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(String key) {
|
||||||
|
Redis.del(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.njzscloud.common.cache.config;
|
||||||
|
|
||||||
|
import com.njzscloud.common.cache.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@ConditionalOnBooleanProperty(prefix = "cache", name = "enable")
|
||||||
|
@EnableConfigurationProperties(CacheProperties.class)
|
||||||
|
public class CacheAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Cache cache(CacheProperties properties) {
|
||||||
|
CacheProperties.FirstCacheProperties first = properties.getFirst();
|
||||||
|
CacheProperties.SecondCacheProperties second = properties.getSecond();
|
||||||
|
Cache noCache = new NoCache();
|
||||||
|
Cache firstCache = first.isEnabled() ? new FirstCache(first.getCapacity(), first.getTimeout()) : noCache;
|
||||||
|
Cache secondCache = second.isEnabled() ? new SecondCache(second.getTimeout()) : noCache;
|
||||||
|
return new DualCache(firstCache, secondCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
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 boolean enabled = false;
|
||||||
|
|
||||||
|
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 @@
|
||||||
|
com.njzscloud.common.cache.config.CacheAutoConfiguration
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</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>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-poi</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!--</editor-fold>-->
|
||||||
|
|
||||||
|
<!-- 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,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,105 @@
|
||||||
|
package com.njzscloud.common.core.jackson.serializer;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonStreamContext;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
|
import com.fasterxml.jackson.core.TreeNode;
|
||||||
|
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 {
|
||||||
|
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,113 @@
|
||||||
|
package com.njzscloud.common.core.log;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ansi 颜色
|
||||||
|
*/
|
||||||
|
public enum AnsiColor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认前景色
|
||||||
|
*/
|
||||||
|
DEFAULT("39"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黑
|
||||||
|
*/
|
||||||
|
BLACK("30"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 红
|
||||||
|
*/
|
||||||
|
RED("31"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绿
|
||||||
|
*/
|
||||||
|
GREEN("32"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黄
|
||||||
|
*/
|
||||||
|
YELLOW("33"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 蓝
|
||||||
|
*/
|
||||||
|
BLUE("34"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 品红
|
||||||
|
*/
|
||||||
|
MAGENTA("35"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 青
|
||||||
|
*/
|
||||||
|
CYAN("36"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 白
|
||||||
|
*/
|
||||||
|
WHITE("37"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮黑(灰)
|
||||||
|
*/
|
||||||
|
BRIGHT_BLACK("90"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮红
|
||||||
|
*/
|
||||||
|
BRIGHT_RED("91"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮绿
|
||||||
|
*/
|
||||||
|
BRIGHT_GREEN("92"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮黄
|
||||||
|
*/
|
||||||
|
BRIGHT_YELLOW("93"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮蓝
|
||||||
|
*/
|
||||||
|
BRIGHT_BLUE("94"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮品红
|
||||||
|
*/
|
||||||
|
BRIGHT_MAGENTA("95"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮青
|
||||||
|
*/
|
||||||
|
BRIGHT_CYAN("96"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮白
|
||||||
|
*/
|
||||||
|
BRIGHT_WHITE("97");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
AnsiColor(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnsiColor parse(String name) {
|
||||||
|
return Arrays.stream(AnsiColor.values())
|
||||||
|
.filter(member -> member.toString().equalsIgnoreCase(name))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.njzscloud.common.core.log;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
import ch.qos.logback.core.pattern.CompositeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志颜色转换
|
||||||
|
*/
|
||||||
|
public class ColorConverter extends CompositeConverter<ILoggingEvent> {
|
||||||
|
@Override
|
||||||
|
protected String transform(ILoggingEvent event, String in) {
|
||||||
|
AnsiColor color = AnsiColor.parse(getFirstOption());
|
||||||
|
if (color == null) {
|
||||||
|
switch (event.getLevel().toInt()) {
|
||||||
|
case Level.ERROR_INT:
|
||||||
|
color = AnsiColor.RED;
|
||||||
|
break;
|
||||||
|
case Level.WARN_INT:
|
||||||
|
color = AnsiColor.YELLOW;
|
||||||
|
break;
|
||||||
|
case Level.INFO_INT:
|
||||||
|
color = AnsiColor.BLUE;
|
||||||
|
break;
|
||||||
|
case Level.DEBUG_INT:
|
||||||
|
color = AnsiColor.GREEN;
|
||||||
|
break;
|
||||||
|
case Level.TRACE_INT:
|
||||||
|
color = AnsiColor.MAGENTA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = AnsiColor.BLACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "\033[" + color + "m" + in + "\033[0m";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class DefaultThreadFactory implements ThreadFactory {
|
||||||
|
private static final AtomicInteger poolNumber = new AtomicInteger(1);
|
||||||
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
private final String namePrefix;
|
||||||
|
private final boolean virtual;
|
||||||
|
|
||||||
|
public DefaultThreadFactory(boolean virtual, String poolName) {
|
||||||
|
if (StrUtil.isBlank(poolName)) namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
|
||||||
|
else namePrefix = poolName + "-";
|
||||||
|
this.virtual = virtual;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable r) {
|
||||||
|
String name = namePrefix + threadNumber.getAndIncrement();
|
||||||
|
Runnable task = () -> {
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
threadNumber.decrementAndGet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = (t1, e) -> log.error("线程异常:{}", t1.getName(), e);
|
||||||
|
if (virtual) {
|
||||||
|
return Thread.ofVirtual()
|
||||||
|
.name(name)
|
||||||
|
.uncaughtExceptionHandler(uncaughtExceptionHandler)
|
||||||
|
.unstarted(task);
|
||||||
|
} else {
|
||||||
|
return Thread.ofPlatform()
|
||||||
|
.name(name)
|
||||||
|
.daemon(true)
|
||||||
|
.uncaughtExceptionHandler(uncaughtExceptionHandler)
|
||||||
|
.priority(Thread.NORM_PRIORITY)
|
||||||
|
.unstarted(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,385 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.ex.SysThrowable;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
public class SyncUtil {
|
||||||
|
|
||||||
|
public static void sync(String blockName, long timeout, boolean fair, SyncBlock syncBlock) {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = LockWrapper.get(blockName, timeout, fair);
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
// 加锁成功
|
||||||
|
syncBlock.block();
|
||||||
|
} else {
|
||||||
|
throw Exceptions.error("锁获取失败:锁名称:{}", blockName);
|
||||||
|
}
|
||||||
|
} catch (SysThrowable e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "同步块执行失败:锁名称:{}", blockName);
|
||||||
|
} finally {
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
lockWrapper.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T sync(String blockName, long timeout, boolean fair, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = LockWrapper.get(blockName, timeout, fair);
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
// 加锁成功
|
||||||
|
return syncBlock.block();
|
||||||
|
} else {
|
||||||
|
throw Exceptions.error("锁获取失败:锁名称:{}", blockName);
|
||||||
|
}
|
||||||
|
} catch (SysThrowable e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "同步块执行失败:锁名称:{}", blockName);
|
||||||
|
} finally {
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
lockWrapper.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T sync(String blockName, boolean fair, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, 0, fair, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T sync(String blockName, long timeout, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, timeout, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T sync(String blockName, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, 0, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sync(String blockName, boolean fair, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, 0, fair, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sync(String blockName, long timeout, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, timeout, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sync(String blockName, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, 0, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncR(String blockName, long timeout, boolean fair, SyncBlock syncBlock) {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = LockWrapper.getRLock(blockName, timeout, fair);
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
// 加锁成功
|
||||||
|
syncBlock.block();
|
||||||
|
} else {
|
||||||
|
throw Exceptions.error("锁获取失败:锁名称:{}", blockName);
|
||||||
|
}
|
||||||
|
} catch (SysThrowable e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "同步块执行失败:锁名称:{}", blockName);
|
||||||
|
} finally {
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
lockWrapper.unrlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncR(String blockName, long timeout, boolean fair, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = LockWrapper.getRLock(blockName, timeout, fair);
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
// 加锁成功
|
||||||
|
return syncBlock.block();
|
||||||
|
} else {
|
||||||
|
throw Exceptions.error("锁获取失败:锁名称:{}", blockName);
|
||||||
|
}
|
||||||
|
} catch (SysThrowable e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "同步块执行失败:锁名称:{}", blockName);
|
||||||
|
} finally {
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
lockWrapper.unrlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncR(String blockName, boolean fair, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, 0, fair, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncR(String blockName, long timeout, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, timeout, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncR(String blockName, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, 0, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncR(String blockName, boolean fair, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, 0, fair, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncR(String blockName, long timeout, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, timeout, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncR(String blockName, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, 0, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncW(String blockName, long timeout, boolean fair, SyncBlock syncBlock) {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = LockWrapper.getWLock(blockName, timeout, fair);
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
// 加锁成功
|
||||||
|
syncBlock.block();
|
||||||
|
} else {
|
||||||
|
throw Exceptions.error("锁获取失败:锁名称:{}", blockName);
|
||||||
|
}
|
||||||
|
} catch (SysThrowable e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "同步块执行失败:锁名称:{}", blockName);
|
||||||
|
} finally {
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
lockWrapper.unwlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncW(String blockName, long timeout, boolean fair, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = LockWrapper.getWLock(blockName, timeout, fair);
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
// 加锁成功
|
||||||
|
return syncBlock.block();
|
||||||
|
} else {
|
||||||
|
throw Exceptions.error("锁获取失败:锁名称:{}", blockName);
|
||||||
|
}
|
||||||
|
} catch (SysThrowable e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "同步块执行失败:锁名称:{}", blockName);
|
||||||
|
} finally {
|
||||||
|
if (lockWrapper != null) {
|
||||||
|
lockWrapper.unwlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncW(String blockName, boolean fair, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, 0, fair, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncW(String blockName, long timeout, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, timeout, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T syncW(String blockName, SyncSupplierBlock<T> syncBlock) {
|
||||||
|
return sync(blockName, 0, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncW(String blockName, boolean fair, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, 0, fair, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncW(String blockName, long timeout, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, timeout, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void syncW(String blockName, SyncBlock syncBlock) {
|
||||||
|
sync(blockName, 0, false, syncBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SyncBlock {
|
||||||
|
void block();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SyncSupplierBlock<T> {
|
||||||
|
T block();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LockWrapper {
|
||||||
|
private static final Map<String, LockWrapper> LOCKS = new ConcurrentHashMap<>();
|
||||||
|
private final String blockName;
|
||||||
|
private final ReentrantLock lock;
|
||||||
|
private final ReentrantReadWriteLock rwlock;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
private LockWrapper(String blockName, boolean fair) {
|
||||||
|
this(blockName, fair, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LockWrapper(String blockName, boolean fair, boolean rw) {
|
||||||
|
if (rw) {
|
||||||
|
rwlock = new ReentrantReadWriteLock(fair);
|
||||||
|
lock = null;
|
||||||
|
} else {
|
||||||
|
lock = new ReentrantLock(fair);
|
||||||
|
rwlock = null;
|
||||||
|
}
|
||||||
|
this.count = 0;
|
||||||
|
this.blockName = blockName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LockWrapper getRLock(String blockName, long timeout, boolean fair) {
|
||||||
|
LockWrapper lockWrapper = LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
LockWrapper wrapper = Objects.requireNonNullElseGet(v, () -> new LockWrapper(blockName, fair, true));
|
||||||
|
// 增加引用数
|
||||||
|
wrapper.count++;
|
||||||
|
return wrapper;
|
||||||
|
});
|
||||||
|
if (timeout > 0) {
|
||||||
|
if (!lockWrapper.tryRLock(timeout)) {
|
||||||
|
// 加锁失败,回退引用数
|
||||||
|
LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
if (v == null) return null;
|
||||||
|
v.count--;
|
||||||
|
return v.count > 0 ? v : null;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return lockWrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lockWrapper.rlock();
|
||||||
|
return lockWrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LockWrapper getWLock(String blockName, long timeout, boolean fair) {
|
||||||
|
LockWrapper lockWrapper = LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
LockWrapper wrapper = Objects.requireNonNullElseGet(v, () -> new LockWrapper(blockName, fair, true));
|
||||||
|
// 增加引用数
|
||||||
|
wrapper.count++;
|
||||||
|
return wrapper;
|
||||||
|
});
|
||||||
|
if (timeout > 0) {
|
||||||
|
if (!lockWrapper.tryWLock(timeout)) {
|
||||||
|
// 加锁失败,回退引用数
|
||||||
|
LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
if (v == null) return null;
|
||||||
|
v.count--;
|
||||||
|
return v.count > 0 ? v : null;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return lockWrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lockWrapper.wlock();
|
||||||
|
return lockWrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LockWrapper get(String blockName, long timeout, boolean fair) {
|
||||||
|
LockWrapper lockWrapper = LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
LockWrapper wrapper = Objects.requireNonNullElseGet(v, () -> new LockWrapper(blockName, fair));
|
||||||
|
// 增加引用数
|
||||||
|
wrapper.count++;
|
||||||
|
return wrapper;
|
||||||
|
});
|
||||||
|
if (timeout > 0) {
|
||||||
|
if (!lockWrapper.tryLock(timeout)) {
|
||||||
|
// 加锁失败,回退引用数
|
||||||
|
LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
if (v == null) return null;
|
||||||
|
v.count--;
|
||||||
|
return v.count > 0 ? v : null;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return lockWrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lockWrapper.lock();
|
||||||
|
return lockWrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rlock() {
|
||||||
|
rwlock.readLock().lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryRLock(long timeout) {
|
||||||
|
try {
|
||||||
|
return rwlock.readLock().tryLock(timeout, TimeUnit.MICROSECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wlock() {
|
||||||
|
rwlock.writeLock().lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryWLock(long timeout) {
|
||||||
|
try {
|
||||||
|
return rwlock.writeLock().tryLock(timeout, TimeUnit.MICROSECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lock() {
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryLock(long timeout) {
|
||||||
|
try {
|
||||||
|
return lock.tryLock(timeout, TimeUnit.MICROSECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unrlock() {
|
||||||
|
// 正常解锁
|
||||||
|
rwlock.readLock().unlock();
|
||||||
|
LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
if (v == null) return null;
|
||||||
|
v.count--;
|
||||||
|
return v.count > 0 ? v : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unwlock() {
|
||||||
|
// 正常解锁
|
||||||
|
rwlock.writeLock().unlock();
|
||||||
|
LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
if (v == null) return null;
|
||||||
|
v.count--;
|
||||||
|
return v.count > 0 ? v : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unlock() {
|
||||||
|
lock.unlock();
|
||||||
|
LOCKS.compute(blockName, (k, v) -> {
|
||||||
|
if (v == null) return null;
|
||||||
|
v.count--;
|
||||||
|
return v.count > 0 ? v : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class ThreadContext {
|
||||||
|
private static final Map<Thread, Map<String, Object>> CONTEXT_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
*/
|
||||||
|
public static <T> T get(String key, T defaultValue) {
|
||||||
|
return (T) CONTEXT_CACHE
|
||||||
|
.computeIfAbsent(Thread.currentThread(), k -> new ConcurrentHashMap<>())
|
||||||
|
.computeIfAbsent(key, k -> defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
*/
|
||||||
|
public static <T> T get(String key) {
|
||||||
|
return (T) CONTEXT_CACHE
|
||||||
|
.computeIfAbsent(Thread.currentThread(), k -> new ConcurrentHashMap<>())
|
||||||
|
.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
public static <T> T set(String key, T value) {
|
||||||
|
return (T) CONTEXT_CACHE
|
||||||
|
.computeIfAbsent(Thread.currentThread(), k -> new ConcurrentHashMap<>())
|
||||||
|
.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
*/
|
||||||
|
public static void remove(String key) {
|
||||||
|
CONTEXT_CACHE
|
||||||
|
.computeIfAbsent(Thread.currentThread(), k -> new ConcurrentHashMap<>())
|
||||||
|
.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理当前线程对应的数据
|
||||||
|
*/
|
||||||
|
public static void clear() {
|
||||||
|
CONTEXT_CACHE.remove(Thread.currentThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.njzscloud.common.core.thread;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@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("Default");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return THREAD_POOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建线程池(平台线程)
|
||||||
|
*
|
||||||
|
* @param poolName 线程池名称
|
||||||
|
* @param poolSize 线程数量
|
||||||
|
* @param bufferSize 队列缓冲区大小
|
||||||
|
*/
|
||||||
|
public static ThreadPoolExecutor createThreadPool(String poolName, int poolSize, int bufferSize) {
|
||||||
|
return createPlatformThreadPool(poolName, 0, poolSize, 60, bufferSize, Integer.MAX_VALUE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建线程池(虚拟线程)
|
||||||
|
*
|
||||||
|
* @param poolName 线程池名称
|
||||||
|
*/
|
||||||
|
public static ThreadPoolExecutor createThreadPool(String poolName) {
|
||||||
|
return createVirtualThreadPool(poolName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建线程池(虚拟线程)
|
||||||
|
*
|
||||||
|
* @param poolName 线程池名称
|
||||||
|
* @param rejectedExecutionHandler 拒绝策略
|
||||||
|
*/
|
||||||
|
public static ThreadPoolExecutor createVirtualThreadPool(String poolName, RejectedExecutionHandler rejectedExecutionHandler) {
|
||||||
|
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
|
||||||
|
0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
|
||||||
|
new SynchronousQueue<>(),
|
||||||
|
new DefaultThreadFactory(true, poolName),
|
||||||
|
rejectedExecutionHandler == null ? new ThreadPoolExecutor.AbortPolicy() : rejectedExecutionHandler);
|
||||||
|
threadPoolExecutor.allowCoreThreadTimeOut(true);
|
||||||
|
return threadPoolExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param poolName 线程池名称
|
||||||
|
* @param corePoolSize 核心线程数
|
||||||
|
* @param maxPoolSize 最大线程数
|
||||||
|
* @param keepAliveSeconds 空闲时间(秒)
|
||||||
|
* @param windowCapacity 一级队列大小
|
||||||
|
* @param standbyCapacity 二级队列大小
|
||||||
|
* @param rejectedExecutionHandler 拒绝策略
|
||||||
|
*/
|
||||||
|
public static ThreadPoolExecutor createPlatformThreadPool(String poolName,
|
||||||
|
int corePoolSize, int maxPoolSize, long keepAliveSeconds,
|
||||||
|
int windowCapacity, int standbyCapacity,
|
||||||
|
RejectedExecutionHandler rejectedExecutionHandler
|
||||||
|
) {
|
||||||
|
Assert.isTrue(windowCapacity > 0 && standbyCapacity > 0 && windowCapacity <= standbyCapacity, () -> Exceptions.error("队列大小设置错误"));
|
||||||
|
Q<Runnable> q = new Q<>(windowCapacity, standbyCapacity);
|
||||||
|
RejectedExecutionHandler abortPolicy = rejectedExecutionHandler == null ? new ThreadPoolExecutor.AbortPolicy() : rejectedExecutionHandler;
|
||||||
|
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
|
||||||
|
corePoolSize, maxPoolSize, keepAliveSeconds <= 0 ? 60 : keepAliveSeconds, TimeUnit.SECONDS,
|
||||||
|
q,
|
||||||
|
new DefaultThreadFactory(false, poolName),
|
||||||
|
(r, p) -> {
|
||||||
|
if (!q.offerStandby(r)) {
|
||||||
|
log.debug("任务队列已满");
|
||||||
|
abortPolicy.rejectedExecution(r, p);
|
||||||
|
} else {
|
||||||
|
log.debug("任务已加入备用队列");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
threadPoolExecutor.allowCoreThreadTimeOut(corePoolSize == 0);
|
||||||
|
return threadPoolExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompletableFuture<Void> runAsync(Runnable runnable) {
|
||||||
|
return CompletableFuture.runAsync(runnable, defaultThreadPool());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
|
||||||
|
return CompletableFuture.supplyAsync(supplier, defaultThreadPool());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,118 @@
|
||||||
|
package com.njzscloud.common.core.tuple;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2 元组
|
||||||
|
*
|
||||||
|
* @param <_0>
|
||||||
|
* @param <_1>
|
||||||
|
*/
|
||||||
|
public class Tuple2<_0, _1> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元组大小
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public final int size = 2;
|
||||||
|
/**
|
||||||
|
* 第 0 个数据
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
protected final _0 _0_;
|
||||||
|
/**
|
||||||
|
* 第 1 个数据
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
protected final _1 _1_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
*/
|
||||||
|
public 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> of(__0 _0_, __1 _1_) {
|
||||||
|
return new Tuple2<>(_0_, _1_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 0 个数据
|
||||||
|
*/
|
||||||
|
@JsonProperty("0")
|
||||||
|
public _0 get_0() {
|
||||||
|
return _0_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 1 个数据
|
||||||
|
*/
|
||||||
|
@JsonProperty("1")
|
||||||
|
public _1 get_1() {
|
||||||
|
return _1_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>按索引获取数据</p>
|
||||||
|
* <p>索引越界将返回 null</p>
|
||||||
|
*
|
||||||
|
* @param index 索引
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(int index) {
|
||||||
|
return switch (index) {
|
||||||
|
case 0 -> (T) this._0_;
|
||||||
|
case 1 -> (T) this._1_;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 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,86 @@
|
||||||
|
package com.njzscloud.common.core.tuple;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3 元组
|
||||||
|
*
|
||||||
|
* @param <_0>
|
||||||
|
* @param <_1>
|
||||||
|
* @param <_2>
|
||||||
|
*/
|
||||||
|
public class Tuple3<_0, _1, _2> extends Tuple2<_0, _1> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元组大小
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public final int size = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第 2 个数据
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
protected final _2 _2_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @param _2_ 第 2 个数据
|
||||||
|
*/
|
||||||
|
public 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> of(__0 _0_, __1 _1_, __2 _2_) {
|
||||||
|
return new Tuple3<>(_0_, _1_, _2_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 2 个数据
|
||||||
|
*/
|
||||||
|
@JsonProperty("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) {
|
||||||
|
return switch (index) {
|
||||||
|
case 0 -> (T) this._0_;
|
||||||
|
case 1 -> (T) this._1_;
|
||||||
|
case 2 -> (T) this._2_;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 数组
|
||||||
|
*
|
||||||
|
* @return Object[]
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
return new Object[]{this._0_, this._1_, this._2_};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.njzscloud.common.core.tuple;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4 元组
|
||||||
|
*
|
||||||
|
* @param <_0>
|
||||||
|
* @param <_1>
|
||||||
|
* @param <_2>
|
||||||
|
* @param <_3>
|
||||||
|
*/
|
||||||
|
public class Tuple4<_0, _1, _2, _3> extends Tuple3<_0, _1, _2> {
|
||||||
|
/**
|
||||||
|
* 元组大小
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public final int size = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第 3 个数据
|
||||||
|
*/
|
||||||
|
protected final _3 _3_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造方法
|
||||||
|
*
|
||||||
|
* @param _0_ 第 0 个数据
|
||||||
|
* @param _1_ 第 1 个数据
|
||||||
|
* @param _2_ 第 2 个数据
|
||||||
|
* @param _3_ 第 3 个数据
|
||||||
|
*/
|
||||||
|
public 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> of(__0 _0_, __1 _1_, __2 _2_, __3 _3_) {
|
||||||
|
return new Tuple4<>(_0_, _1_, _2_, _3_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第 3 个数据
|
||||||
|
*/
|
||||||
|
@JsonProperty("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) {
|
||||||
|
return switch (index) {
|
||||||
|
case 0 -> (T) this._0_;
|
||||||
|
case 1 -> (T) this._1_;
|
||||||
|
case 2 -> (T) this._2_;
|
||||||
|
case 3 -> (T) this._3_;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成 数组
|
||||||
|
*
|
||||||
|
* @return Object[]
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
return new Object[]{this._0_, this._1_, this._2_, this._3_};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
public class BCD {
|
||||||
|
public static String bcdToStr(byte[] bcd) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bcd) {
|
||||||
|
sb.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
// 移除前面的0
|
||||||
|
return sb.toString().replaceFirst("^0+", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class CallerUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取方法调用者的详细信息(类名、方法名、行号)
|
||||||
|
*
|
||||||
|
* @return 调用者信息封装类,若获取失败返回 null
|
||||||
|
*/
|
||||||
|
public static Caller getCaller(int depth) {
|
||||||
|
/*
|
||||||
|
getStackTrace
|
||||||
|
getCaller
|
||||||
|
getCaller 的调用者
|
||||||
|
getCaller 的调用者的调用者
|
||||||
|
*/
|
||||||
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
|
|
||||||
|
int i = 2 + depth;
|
||||||
|
if (stackTrace.length < i + 1) {
|
||||||
|
// 调用栈过短(理论上不可能,除非在 JVM 启动初期调用)
|
||||||
|
return Caller.defaultCaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveCaller(stackTrace, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Caller resolveCaller(StackTraceElement[] stackTrace, int depth) {
|
||||||
|
StackTraceElement callerElement = stackTrace[depth];
|
||||||
|
// 解析栈元素的信息
|
||||||
|
String className = callerElement.getClassName(); // 调用者类名(全限定名)
|
||||||
|
String methodName = callerElement.getMethodName();// 调用者方法名
|
||||||
|
int lineNumber = callerElement.getLineNumber(); // 调用者代码行号(-1 表示未知)
|
||||||
|
String fileName = callerElement.getFileName(); // 调用者所在文件名(可能为 null)
|
||||||
|
return new Caller(className, methodName, lineNumber, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tuple2<String, Caller> printStackTrace(Throwable throwable) {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
try (PrintStream printStream = new PrintStream(outputStream)) {
|
||||||
|
// 3. 调用 printStackTrace,输出到 PrintStream(实际写入 ByteArrayOutputStream)
|
||||||
|
throwable.printStackTrace(printStream);
|
||||||
|
// 4. 将字节流转为字符串(默认 UTF-8 编码,兼容所有字符)
|
||||||
|
String string = outputStream.toString(StandardCharsets.UTF_8);
|
||||||
|
return Tuple2.of(string, resolveCaller(throwable.getStackTrace(), 0));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 极端情况下(如编码异常),返回简化信息
|
||||||
|
return Tuple2.of(throwable.getMessage(), resolveCaller(throwable.getStackTrace(), 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用者信息封装类(用于结构化返回结果)
|
||||||
|
*
|
||||||
|
* @param className Getter 方法 全限定类名(如:com.example.Test)
|
||||||
|
* @param methodName 方法名(如:main)
|
||||||
|
* @param lineNumber 调用行号
|
||||||
|
* @param fileName 文件名(如:Test.java)
|
||||||
|
*/
|
||||||
|
public record Caller(String className, String methodName, int lineNumber, String fileName) {
|
||||||
|
public static Caller defaultCaller = new Caller();
|
||||||
|
|
||||||
|
public Caller() {
|
||||||
|
this("", "", -1, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StrUtil.format("调用者信息, 类名:{}、方法名:{}、行号:{}、文件名:{}",
|
||||||
|
className, methodName, lineNumber, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 坐标转换工具类:WGS-84转GCJ-02(火星坐标系)
|
||||||
|
* WGS-84:GPS设备获取的原始坐标
|
||||||
|
* GCJ-02:高德地图、谷歌中国地图等使用的坐标系
|
||||||
|
*/
|
||||||
|
public class CoordinateConverter {
|
||||||
|
// 长半径
|
||||||
|
private static final double PI = 3.1415926535897932384626;
|
||||||
|
private static final double A = 6378245.0;
|
||||||
|
// 扁率
|
||||||
|
private static final double EE = 0.00669342162296594323;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断坐标是否在中国境内(不在中国境内则不做偏移)
|
||||||
|
*
|
||||||
|
* @param lat 纬度
|
||||||
|
* @param lon 经度
|
||||||
|
* @return 是否在中国境内
|
||||||
|
*/
|
||||||
|
public static boolean outOfChina(double lat, double lon) {
|
||||||
|
if (lon < 72.004 || lon > 137.8347) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return lat < 0.8293 || lat > 55.8271;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 纬度转换
|
||||||
|
*
|
||||||
|
* @param x 经度
|
||||||
|
* @param y 纬度
|
||||||
|
* @return 转换后的纬度偏移量
|
||||||
|
*/
|
||||||
|
private static double transformLat(double x, double y) {
|
||||||
|
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
|
||||||
|
ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经度转换
|
||||||
|
*
|
||||||
|
* @param x 经度
|
||||||
|
* @param y 纬度
|
||||||
|
* @return 转换后的经度偏移量
|
||||||
|
*/
|
||||||
|
private static double transformLon(double x, double y) {
|
||||||
|
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
||||||
|
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
|
||||||
|
ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
|
||||||
|
ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将WGS-84坐标转换为GCJ-02坐标
|
||||||
|
*
|
||||||
|
* @param wgsLat WGS-84纬度
|
||||||
|
* @param wgsLon WGS-84经度
|
||||||
|
* @return 转换后的坐标数组 [纬度, 经度]
|
||||||
|
*/
|
||||||
|
public static Tuple2<Double, Double> wgs84ToGcj02(double wgsLat, double wgsLon) {
|
||||||
|
// 如果不在中国境内,直接返回原坐标
|
||||||
|
if (outOfChina(wgsLat, wgsLon)) {
|
||||||
|
return Tuple2.of(wgsLat, wgsLon);
|
||||||
|
}
|
||||||
|
|
||||||
|
double dLat = transformLat(wgsLon - 105.0, wgsLat - 35.0);
|
||||||
|
double dLon = transformLon(wgsLon - 105.0, wgsLat - 35.0);
|
||||||
|
double radLat = wgsLat / 180.0 * PI;
|
||||||
|
double magic = Math.sin(radLat);
|
||||||
|
magic = 1 - EE * magic * magic;
|
||||||
|
double sqrtMagic = Math.sqrt(magic);
|
||||||
|
dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI);
|
||||||
|
dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI);
|
||||||
|
|
||||||
|
// 保留6位小数,约10厘米精度
|
||||||
|
double gcjLat = round(wgsLat + dLat, 6);
|
||||||
|
double gcjLon = round(wgsLon + dLon, 6);
|
||||||
|
|
||||||
|
return Tuple2.of(gcjLat, gcjLon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 四舍五入保留指定小数位数
|
||||||
|
*
|
||||||
|
* @param value 数值
|
||||||
|
* @param scale 小数位数
|
||||||
|
* @return 处理后的数值
|
||||||
|
*/
|
||||||
|
private static double round(double value, int scale) {
|
||||||
|
return new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP).doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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,164 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ImgUtil {
|
||||||
|
|
||||||
|
// 默认字体(可根据需求调整)
|
||||||
|
private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 24);
|
||||||
|
// 默认水印颜色(半透明黑色)
|
||||||
|
private static final Color DEFAULT_COLOR = Color.WHITE;
|
||||||
|
// 行间距(字体大小的1.2倍)
|
||||||
|
private static final float LINE_SPACING_RATIO = 1.2f;
|
||||||
|
// 左右边距(避免文本贴边)
|
||||||
|
private static final int PADDING_HORIZONTAL = 10;
|
||||||
|
// 上下边距
|
||||||
|
private static final int PADDING_VERTICAL = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在左上角添加文字水印
|
||||||
|
*
|
||||||
|
* @param sourceImageIn 图片
|
||||||
|
* @param type jpg 或 png
|
||||||
|
* @param txt 文字
|
||||||
|
*/
|
||||||
|
public static byte[] addTxtWatermark(InputStream sourceImageIn, String type, String txt) {
|
||||||
|
try {
|
||||||
|
BufferedImage sourceImage = ImageIO.read(sourceImageIn);
|
||||||
|
if (sourceImage == null) {
|
||||||
|
throw Exceptions.error("图片读取失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage targetImage = new BufferedImage(
|
||||||
|
sourceImage.getWidth(),
|
||||||
|
sourceImage.getHeight(),
|
||||||
|
type.equalsIgnoreCase("png") ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB
|
||||||
|
);
|
||||||
|
Graphics2D g2d = targetImage.createGraphics();
|
||||||
|
|
||||||
|
g2d.drawImage(sourceImage, 0, 0, null);
|
||||||
|
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
|
||||||
|
g2d.setFont(DEFAULT_FONT);
|
||||||
|
g2d.setColor(DEFAULT_COLOR);
|
||||||
|
|
||||||
|
int maxWatermarkWidth = (sourceImage.getWidth() / 2) - 2 * PADDING_HORIZONTAL;
|
||||||
|
if (maxWatermarkWidth <= 0) {
|
||||||
|
throw Exceptions.error("图片宽度过小,无法添加水印");
|
||||||
|
}
|
||||||
|
int lineHeight = g2d.getFontMetrics().getHeight();
|
||||||
|
|
||||||
|
String[] txts = txt.split("\n");
|
||||||
|
int currentY = PADDING_VERTICAL + g2d.getFontMetrics().getAscent();
|
||||||
|
int maxHeight = targetImage.getHeight();
|
||||||
|
|
||||||
|
for (String s : txts) {
|
||||||
|
List<String> lines = wrapText(s, DEFAULT_FONT, maxWatermarkWidth);
|
||||||
|
for (String line : lines) {
|
||||||
|
g2d.drawString(line, PADDING_HORIZONTAL, currentY);
|
||||||
|
currentY += (int) (lineHeight * LINE_SPACING_RATIO);
|
||||||
|
if (currentY >= maxHeight) {
|
||||||
|
throw Exceptions.error("图片高度不足,无法添加水印");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
boolean write = ImageIO.write(targetImage, type, outputStream);
|
||||||
|
if (write) {
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
} else {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Exceptions.error(e, "水印添加失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本自动换行处理
|
||||||
|
*
|
||||||
|
* @param text 待换行文本
|
||||||
|
* @param font 字体
|
||||||
|
* @param maxWidth 最大宽度(像素)
|
||||||
|
* @return 换行后的文本行列表
|
||||||
|
*/
|
||||||
|
private static List<String> wrapText(String text, Font font, int maxWidth) {
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
if (text == null || text.isBlank()) {
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graphics2D g2d = (Graphics2D) new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics();
|
||||||
|
g2d.setFont(font);
|
||||||
|
FontMetrics metrics = g2d.getFontMetrics();
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
String[] words = text.split(" ");
|
||||||
|
StringBuilder currentLine = new StringBuilder();
|
||||||
|
|
||||||
|
for (String word : words) {
|
||||||
|
String tempLine = currentLine.isEmpty() ? word : currentLine + " " + word;
|
||||||
|
Rectangle2D bounds = metrics.getStringBounds(tempLine, null);
|
||||||
|
|
||||||
|
if (bounds.getWidth() <= maxWidth) {
|
||||||
|
// 当前行加上单词后未超过最大宽度,继续添加
|
||||||
|
currentLine = new StringBuilder(tempLine);
|
||||||
|
} else {
|
||||||
|
// 超过最大宽度,保存当前行,开始新行
|
||||||
|
if (!currentLine.isEmpty()) {
|
||||||
|
lines.add(currentLine.toString());
|
||||||
|
currentLine = new StringBuilder(word);
|
||||||
|
} else {
|
||||||
|
// 单个单词超过最大宽度,强制截断(特殊情况处理)
|
||||||
|
lines.add(truncateWord(word, metrics, maxWidth));
|
||||||
|
currentLine = new StringBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加最后一行
|
||||||
|
if (!currentLine.isEmpty()) {
|
||||||
|
lines.add(currentLine.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个单词超长时的强制截断(按字符分割)
|
||||||
|
*
|
||||||
|
* @param word 超长单词
|
||||||
|
* @param metrics 字体度量
|
||||||
|
* @param maxWidth 最大宽度
|
||||||
|
* @return 截断后的单词片段
|
||||||
|
*/
|
||||||
|
private static String truncateWord(String word, FontMetrics metrics, int maxWidth) {
|
||||||
|
StringBuilder truncated = new StringBuilder();
|
||||||
|
for (char c : word.toCharArray()) {
|
||||||
|
String temp = truncated + String.valueOf(c);
|
||||||
|
if (metrics.getStringBounds(temp, null).getWidth() <= maxWidth) {
|
||||||
|
truncated.append(c);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return truncated.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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,39 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME
|
||||||
|
*/
|
||||||
|
public class Mime {
|
||||||
|
public static final String FORM = "application/x-www-form-urlencoded";
|
||||||
|
public static final String MULTIPART_FORM = "multipart/form-data";
|
||||||
|
public static final String BINARY = "application/octet-stream";
|
||||||
|
public static final String JSON = "application/json";
|
||||||
|
public static final String X_NDJSON = "application/x-ndjson";
|
||||||
|
public static final String XML = "application/xml";
|
||||||
|
public static final String TXT = "text/plain";
|
||||||
|
public static final String HTML = "text/html";
|
||||||
|
public static final String CSS = "text/css";
|
||||||
|
public static final String GIF = "image/gif";
|
||||||
|
public static final String JPG = "image/jpeg";
|
||||||
|
public static final String PNG = "image/png";
|
||||||
|
public static final String SVG = "image/svg+xml";
|
||||||
|
public static final String WEBP = "image/webp";
|
||||||
|
public static final String PDF = "image/pdf";
|
||||||
|
public static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
||||||
|
public static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
public static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
|
public static final String PPT = "application/vnd.ms-powerpoint";
|
||||||
|
public static final String XLS = "application/vnd.ms-excel";
|
||||||
|
public static final String DOC = "application/msword";
|
||||||
|
public static final String ZIP = "application/zip";
|
||||||
|
public static final String MP3 = "audio/mpeg";
|
||||||
|
public static final String MP4 = "video/mp4";
|
||||||
|
|
||||||
|
|
||||||
|
public static String u8Val(String mime) {
|
||||||
|
return StrUtil.format("{};charset={}", mime, "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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,113 @@
|
||||||
|
package com.njzscloud.common.core.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class StrUtil {
|
||||||
|
// 匹配非特殊字符的正则表达式,对应 TS 中的 [^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+
|
||||||
|
private static final Pattern SPLIT_CHAR_PATTERN = Pattern.compile("[^\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7f]+");
|
||||||
|
|
||||||
|
// 匹配大写字母的正则表达式,用于分割单词
|
||||||
|
private static final Pattern UPPER_CASE_PATTERN = Pattern.compile("([A-Z])");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否为空
|
||||||
|
*/
|
||||||
|
private static boolean isBlank(String str) {
|
||||||
|
return str == null || str.trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首字母大写
|
||||||
|
*/
|
||||||
|
private static String capitalize(String word) {
|
||||||
|
if (isBlank(word)) {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
return Character.toUpperCase(word.charAt(0)) +
|
||||||
|
(word.length() > 1 ? word.substring(1).toLowerCase() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以大写字母为分割符,转换成其他分隔符
|
||||||
|
*/
|
||||||
|
public static String processWords(String str, WordJoiner joiner) {
|
||||||
|
if (isBlank(str)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理字符串:在大写字母前添加分隔符,然后转为小写
|
||||||
|
String processed = UPPER_CASE_PATTERN.matcher(str).replaceAll(" $1").toLowerCase();
|
||||||
|
|
||||||
|
// 提取匹配的单词
|
||||||
|
Matcher matcher = SPLIT_CHAR_PATTERN.matcher(processed);
|
||||||
|
List<String> words = new ArrayList<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
words.add(matcher.group().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 joiner 连接单词
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (int i = 0; i < words.size(); i++) {
|
||||||
|
result.append(joiner.join(words.get(i), i));
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小驼峰命名 (camelCase)
|
||||||
|
*/
|
||||||
|
public static String camelCase(String str) {
|
||||||
|
return processWords(str, (word, index) -> {
|
||||||
|
if (index == 0) {
|
||||||
|
return word;
|
||||||
|
} else {
|
||||||
|
return capitalize(word);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大驼峰命名 (PascalCase)
|
||||||
|
*/
|
||||||
|
public static String pascalCase(String str) {
|
||||||
|
String camel = camelCase(str);
|
||||||
|
return capitalize(camel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下划线命名 (snake_case)
|
||||||
|
*/
|
||||||
|
public static String snakeCase(String str) {
|
||||||
|
return processWords(str, (word, index) -> {
|
||||||
|
if (index == 0) {
|
||||||
|
return word;
|
||||||
|
} else {
|
||||||
|
return "_" + word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中横杠命名 (kebab-case)
|
||||||
|
*/
|
||||||
|
public static String kebabCase(String str) {
|
||||||
|
return processWords(str, (word, index) -> {
|
||||||
|
if (index == 0) {
|
||||||
|
return word;
|
||||||
|
} else {
|
||||||
|
return "-" + word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数式接口,用于处理单词连接
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface WordJoiner {
|
||||||
|
String join(String word, int index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-email</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</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>
|
||||||
|
|
||||||
|
<!-- 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.tuple.Tuple2;
|
||||||
|
import com.njzscloud.common.core.utils.Mime;
|
||||||
|
import jakarta.mail.util.ByteArrayDataSource;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
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.of(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 jakarta.mail.MessagingException;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
import jakarta.mail.util.ByteArrayDataSource;
|
||||||
|
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 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 ? "邮件服务器认证失败" : "邮件发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-gen</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>njzscloud-common-gen</name>
|
||||||
|
<url>http://maven.apache.org</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-mvc</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-mp</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ibeetl</groupId>
|
||||||
|
<artifactId>beetl</artifactId>
|
||||||
|
<version>3.19.2.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.njzscloud.common.gen;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple3;
|
||||||
|
import com.njzscloud.common.core.utils.R;
|
||||||
|
import com.njzscloud.common.gen.support.Generator;
|
||||||
|
import com.njzscloud.common.mp.support.PageParam;
|
||||||
|
import com.njzscloud.common.mp.support.PageResult;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码模板
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/tpl")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TplController {
|
||||||
|
|
||||||
|
private final TplService tplService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
@PostMapping("/add")
|
||||||
|
public R<?> add(@RequestBody TplEntity tplEntity) {
|
||||||
|
tplService.add(tplEntity);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
*/
|
||||||
|
@PostMapping("/modify")
|
||||||
|
public R<?> modify(@RequestBody TplEntity tplEntity) {
|
||||||
|
tplService.modify(tplEntity);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
@PostMapping("/del")
|
||||||
|
public R<?> del(@RequestBody List<Long> ids) {
|
||||||
|
tplService.del(ids);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/generate")
|
||||||
|
public R<?> generate(@RequestParam("tplName") String tplName, @RequestParam("tableName") String tableName, @RequestBody(required = false) Map<String, Object> data) {
|
||||||
|
Tuple3<String, String, String> res = Generator.generate(tplName, tableName, data);
|
||||||
|
return R.success(MapUtil.builder()
|
||||||
|
.put("dir", res.get_0())
|
||||||
|
.put("filename", res.get_1())
|
||||||
|
.put("content", res.get_2())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
public R<TplEntity> detail(@RequestParam Long id) {
|
||||||
|
return R.success(tplService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
@GetMapping("/paging")
|
||||||
|
public R<PageResult<TplEntity>> paging(PageParam pageParam, TplEntity tplEntity) {
|
||||||
|
return R.success(tplService.paging(pageParam, tplEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.njzscloud.common.gen;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.njzscloud.common.gen.contant.TplCategory;
|
||||||
|
import com.njzscloud.common.gen.support.Tpl;
|
||||||
|
import com.njzscloud.common.mp.support.handler.j.JsonTypeHandler;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码模板
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@TableName(value = "sys_tpl", autoResultMap = true)
|
||||||
|
public class TplEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板名称
|
||||||
|
*/
|
||||||
|
private String tplName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板类型; 字典编码:tpl_category
|
||||||
|
*/
|
||||||
|
private TplCategory tplCategory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模板内容
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = JsonTypeHandler.class)
|
||||||
|
private Tpl tpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型数据
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = JsonTypeHandler.class)
|
||||||
|
private Map<String, Object> modelData;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.njzscloud.common.gen;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码模板
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface TplMapper extends BaseMapper<TplEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.njzscloud.common.gen;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.gen.contant.TplCategory;
|
||||||
|
import com.njzscloud.common.gen.support.TemplateEngine;
|
||||||
|
import com.njzscloud.common.gen.support.Tpl;
|
||||||
|
import com.njzscloud.common.mp.support.PageParam;
|
||||||
|
import com.njzscloud.common.mp.support.PageResult;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码模板
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class TplService extends ServiceImpl<TplMapper, TplEntity> implements IService<TplEntity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*
|
||||||
|
* @param tplEntity 数据
|
||||||
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void add(TplEntity tplEntity) {
|
||||||
|
String tplName = tplEntity.getTplName();
|
||||||
|
Assert.notBlank(tplName, () -> Exceptions.clierr("模板名称不能为空"));
|
||||||
|
TplCategory tplCategory = tplEntity.getTplCategory();
|
||||||
|
Assert.notNull(tplCategory, () -> Exceptions.clierr("模板类型不能为空"));
|
||||||
|
boolean exists = this.exists(Wrappers.lambdaQuery(TplEntity.class).eq(TplEntity::getTplName, tplName));
|
||||||
|
Assert.isFalse(exists, () -> Exceptions.clierr("模板:{} 已存在", tplName));
|
||||||
|
Tpl tpl = tplEntity.getTpl();
|
||||||
|
if (tpl == null) {
|
||||||
|
tplEntity.setTpl(new Tpl());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> modelData = tplEntity.getModelData();
|
||||||
|
if (modelData == null) {
|
||||||
|
tplEntity.setModelData(MapUtil.empty());
|
||||||
|
}
|
||||||
|
this.save(tplEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
*
|
||||||
|
* @param tplEntity 数据
|
||||||
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void modify(TplEntity tplEntity) {
|
||||||
|
String tplName = tplEntity.getTplName();
|
||||||
|
Assert.notBlank(tplName, () -> Exceptions.clierr("模板名称不能为空"));
|
||||||
|
TplCategory tplCategory = tplEntity.getTplCategory();
|
||||||
|
Assert.notNull(tplCategory, () -> Exceptions.clierr("模板类型不能为空"));
|
||||||
|
Long id = tplEntity.getId();
|
||||||
|
boolean exists = this.exists(Wrappers.lambdaQuery(TplEntity.class)
|
||||||
|
.eq(TplEntity::getTplName, tplName)
|
||||||
|
.ne(TplEntity::getId, id)
|
||||||
|
);
|
||||||
|
Assert.isFalse(exists, () -> Exceptions.clierr("模板:{} 已存在", tplName));
|
||||||
|
TplEntity oldData = this.getById(id);
|
||||||
|
Assert.notNull(oldData, () -> Exceptions.clierr("未找到要修改数据"));
|
||||||
|
TemplateEngine.rmEngine(oldData.getTplName());
|
||||||
|
this.updateById(tplEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*
|
||||||
|
* @param ids Ids
|
||||||
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void del(List<Long> ids) {
|
||||||
|
this.removeBatchByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*
|
||||||
|
* @param id Id
|
||||||
|
* @return SysTplEntity 结果
|
||||||
|
*/
|
||||||
|
public TplEntity detail(Long id) {
|
||||||
|
return this.getById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*
|
||||||
|
* @param tplEntity 筛选条件
|
||||||
|
* @param pageParam 分页参数
|
||||||
|
* @return PageResult<SysTplEntity> 分页结果
|
||||||
|
*/
|
||||||
|
public PageResult<TplEntity> paging(PageParam pageParam, TplEntity tplEntity) {
|
||||||
|
return PageResult.of(this.page(pageParam.toPage(), Wrappers.query(tplEntity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.njzscloud.common.gen.config;
|
||||||
|
|
||||||
|
import com.njzscloud.common.gen.TplController;
|
||||||
|
import com.njzscloud.common.gen.TplService;
|
||||||
|
import com.njzscloud.common.gen.support.DbMetaData;
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@MapperScan("com.njzscloud.common.gen")
|
||||||
|
public class GenAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TplService sysTplService() {
|
||||||
|
return new TplService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TplController sysTplController(TplService tplService) {
|
||||||
|
return new TplController(tplService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DbMetaData dbMetaData(DataSource dataSource) {
|
||||||
|
return new DbMetaData(dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.njzscloud.common.gen.contant;
|
||||||
|
|
||||||
|
import com.njzscloud.common.core.ienum.DictStr;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典代码:tpl_category
|
||||||
|
* 字典名称:模板类型
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum TplCategory implements DictStr {
|
||||||
|
Controller("Controller", "Controller"),
|
||||||
|
Service("Service", "Service"),
|
||||||
|
Mapper("Mapper", "Mapper"),
|
||||||
|
MapperXml("MapperXml", "MapperXml"),
|
||||||
|
Entity("Entity", "Entity"),
|
||||||
|
;
|
||||||
|
private final String val;
|
||||||
|
private final String txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.njzscloud.common.gen.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import org.beetl.core.Context;
|
||||||
|
import org.beetl.core.Function;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class Btl {
|
||||||
|
public static final Function toCamelCase = (Object[] paras, Context ctx) -> StrUtil.toCamelCase(paras[0].toString());
|
||||||
|
public static final Function upperFirst = (Object[] paras, Context ctx) -> StrUtil.upperFirst(paras[0].toString());
|
||||||
|
public static final Function isBlank = (Object[] paras, Context ctx) -> StrUtil.isBlank(paras[0].toString());
|
||||||
|
public static final Function subAfter = (Object[] paras, Context ctx) -> StrUtil.subAfter(paras[0].toString(), paras[1].toString(), false);
|
||||||
|
public static final Function sqlDataTypeMap = new Function() {
|
||||||
|
private final Map<String, Map<String, String>> map = MapUtil.<String, Map<String, String>>builder()
|
||||||
|
.put("DEFAULT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "").build())
|
||||||
|
.put("VARCHAR", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "String").build())
|
||||||
|
.put("CHAR", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "String").build())
|
||||||
|
.put("LONGTEXT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "String").build())
|
||||||
|
.put("TEXT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "String").build())
|
||||||
|
.put("BIT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "Boolean").build())
|
||||||
|
.put("TINYINT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "Integer").build())
|
||||||
|
.put("SMALLINT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "Integer").build())
|
||||||
|
.put("MEDIUMINT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "Integer").build())
|
||||||
|
.put("INT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "Integer").build())
|
||||||
|
.put("BIGINT", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "Long").build())
|
||||||
|
.put("DOUBLE", MapUtil.<String, String>builder().put("importStatement", "").put("dataType", "DOUBLE").build())
|
||||||
|
.put("DECIMAL", MapUtil.<String, String>builder().put("importStatement", "import java.math.BigDecimal;").put("dataType", "BigDecimal").build())
|
||||||
|
.put("DATE", MapUtil.<String, String>builder().put("importStatement", "import java.time.LocalDate;").put("dataType", "LocalDate").build())
|
||||||
|
.put("TIME", MapUtil.<String, String>builder().put("importStatement", "import java.time.LocalTime;").put("dataType", "LocalTime").build())
|
||||||
|
.put("DATETIME", MapUtil.<String, String>builder().put("importStatement", "import java.time.LocalDateTime;").put("dataType", "LocalDateTime").build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object call(Object[] paras, Context ctx) {
|
||||||
|
return map.getOrDefault(paras[0].toString().toUpperCase(), map.get("DEFAULT"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.njzscloud.common.gen.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DbMetaData {
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getMetaData(String tableName) {
|
||||||
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
|
return getTables(connection, tableName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取数据库元数据失败", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> getTableColumns(Connection conn, String tableName) throws Exception {
|
||||||
|
List<Map<String, Object>> columns = new ArrayList<>();
|
||||||
|
|
||||||
|
DatabaseMetaData metaData = conn.getMetaData();
|
||||||
|
try (ResultSet columnsData = metaData.getColumns(null, null, tableName, "%")) {
|
||||||
|
while (columnsData.next()) {
|
||||||
|
columns.add(MapUtil.<String, Object>builder()
|
||||||
|
.put("name", columnsData.getString("COLUMN_NAME"))
|
||||||
|
.put("dataType", columnsData.getString("TYPE_NAME"))
|
||||||
|
.put("size", columnsData.getInt("COLUMN_SIZE"))
|
||||||
|
.put("nullable", columnsData.getInt("NULLABLE") == DatabaseMetaData.columnNullable)
|
||||||
|
.put("defaultValue", columnsData.getString("COLUMN_DEF"))
|
||||||
|
.put("comment", columnsData.getString("REMARKS"))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (ResultSet primaryKeyData = metaData.getPrimaryKeys(null, null, tableName)) {
|
||||||
|
while (primaryKeyData.next()) {
|
||||||
|
String primaryKeyColumn = primaryKeyData.getString("COLUMN_NAME");
|
||||||
|
columns.forEach(column -> column.put("primaryKey", column.get("name").equals(primaryKeyColumn)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> getTables(Connection conn, String tableName) throws Exception {
|
||||||
|
List<Map<String, Object>> tableInfos = new ArrayList<>();
|
||||||
|
DatabaseMetaData metaData = conn.getMetaData();
|
||||||
|
try (ResultSet tablesData = metaData.getTables(null, null, StrUtil.isNotBlank(tableName) ? tableName : "%", new String[]{"TABLE"})) {
|
||||||
|
while (tablesData.next()) {
|
||||||
|
String tableName_ = tablesData.getString("TABLE_NAME");
|
||||||
|
List<Map<String, Object>> columns = getTableColumns(conn, tableName_);
|
||||||
|
tableInfos.add(MapUtil.<String, Object>builder()
|
||||||
|
.put("name", tableName_)
|
||||||
|
.put("comment", tablesData.getString("REMARKS"))
|
||||||
|
.put("columns", columns)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableInfos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.njzscloud.common.gen.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.jackson.Jackson;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple3;
|
||||||
|
import com.njzscloud.common.gen.TplEntity;
|
||||||
|
import com.njzscloud.common.gen.TplService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Generator {
|
||||||
|
|
||||||
|
private static Tuple3<String, String, String> generate0(String tplName, String tableName, Map<String, Object> data) {
|
||||||
|
DbMetaData dbMetaData = SpringUtil.getBean(DbMetaData.class);
|
||||||
|
TplService tplService = SpringUtil.getBean(TplService.class);
|
||||||
|
Assert.notBlank(tableName, () -> Exceptions.clierr("未指定表名称"));
|
||||||
|
data.put("tableName", tableName);
|
||||||
|
List<Map<String, Object>> table = dbMetaData.getMetaData(tableName);
|
||||||
|
TplEntity tplEntity = tplService.getOne(Wrappers.<TplEntity>lambdaQuery()
|
||||||
|
.eq(TplEntity::getTplName, tplName));
|
||||||
|
|
||||||
|
Tpl tpl = tplEntity.getTpl();
|
||||||
|
Map<String, Object> modelData = tplEntity.getModelData();
|
||||||
|
HashMap<String, Object> map = new HashMap<>(modelData);
|
||||||
|
map.put("table", CollUtil.isNotEmpty(table) ? table.getFirst() : MapUtil.empty());
|
||||||
|
map.putAll(data);
|
||||||
|
log.info("数据:{}", Jackson.toJsonStr(map));
|
||||||
|
TemplateEngine templateEngine = TemplateEngine.getEngine(tplName, tpl);
|
||||||
|
String content = templateEngine.renderContent(map);
|
||||||
|
String dir = templateEngine.renderDir(map);
|
||||||
|
String filename = templateEngine.renderFilename(map);
|
||||||
|
return Tuple3.of(dir, StrUtil.isBlank(filename) ? tplName + ".txt" : filename, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param tplName 模板名称
|
||||||
|
* @param tableName 表名称
|
||||||
|
* @param data 数据
|
||||||
|
* @return 0-->文件夹、1-->文件名、2-->内容
|
||||||
|
*/
|
||||||
|
public static Tuple3<String, String, String> generate(String tplName, String tableName, Map<String, Object> data) {
|
||||||
|
return generate0(tplName, tableName, data == null ? new HashMap<>() : data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.njzscloud.common.gen.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.beetl.core.Configuration;
|
||||||
|
import org.beetl.core.GroupTemplate;
|
||||||
|
import org.beetl.core.Template;
|
||||||
|
import org.beetl.core.exception.BeetlException;
|
||||||
|
import org.beetl.core.exception.ErrorInfo;
|
||||||
|
import org.beetl.core.resource.MapResourceLoader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TemplateEngine {
|
||||||
|
private static final GroupTemplate GT;
|
||||||
|
private static final MapResourceLoader LOADER = new MapResourceLoader();
|
||||||
|
private static final Map<String, TemplateEngine> ENGINE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
GroupTemplate getTemplate_;
|
||||||
|
try {
|
||||||
|
Configuration cfg = Configuration.defaultConfiguration();
|
||||||
|
getTemplate_ = new GroupTemplate(LOADER, cfg);
|
||||||
|
getTemplate_.registerFunction("toCamelCase", Btl.toCamelCase);
|
||||||
|
getTemplate_.registerFunction("upperFirst", Btl.upperFirst);
|
||||||
|
getTemplate_.registerFunction("isBlank", Btl.isBlank);
|
||||||
|
getTemplate_.registerFunction("subAfter", Btl.subAfter);
|
||||||
|
getTemplate_.registerFunction("sqlDataTypeMap", Btl.sqlDataTypeMap);
|
||||||
|
getTemplate_.setErrorHandler(TemplateEngine::processException);
|
||||||
|
} catch (IOException e) {
|
||||||
|
getTemplate_ = null;
|
||||||
|
log.error("模板引擎初始化失败", e);
|
||||||
|
}
|
||||||
|
GT = getTemplate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String tplName;
|
||||||
|
|
||||||
|
private TemplateEngine(String tplName, Tpl tpl) {
|
||||||
|
this.tplName = tplName;
|
||||||
|
LOADER.put(tplName + "-content", tpl.getContent());
|
||||||
|
LOADER.put(tplName + "-dir", tpl.getDir());
|
||||||
|
LOADER.put(tplName + "-filename", tpl.getFilename());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void processException(BeetlException beeException, GroupTemplate groupTemplate, Writer writer) {
|
||||||
|
ErrorInfo errorInfo = beeException.toErrorInfo();
|
||||||
|
throw Exceptions.error(MapUtil.builder()
|
||||||
|
.put("type", errorInfo.type)
|
||||||
|
.put("line", errorInfo.errorTokenLine)
|
||||||
|
.put("token", errorInfo.errorTokenText)
|
||||||
|
.put("msg", errorInfo.msg)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TemplateEngine getEngine(String tplName, Tpl tpl) {
|
||||||
|
return ENGINE.computeIfAbsent(tplName, key -> new TemplateEngine(tplName, tpl));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void rmEngine(String tplName) {
|
||||||
|
ENGINE.remove(tplName);
|
||||||
|
LOADER.remove(tplName + "-content");
|
||||||
|
LOADER.remove(tplName + "-dir");
|
||||||
|
LOADER.remove(tplName + "-filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String render(String tplName, Map<String, Object> data) {
|
||||||
|
Template template = GT.getTemplate(tplName);
|
||||||
|
template.binding(data);
|
||||||
|
return template.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String renderContent(Map<String, Object> data) {
|
||||||
|
return render(tplName + "-content", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String renderDir(Map<String, Object> data) {
|
||||||
|
return render(tplName + "-dir", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String renderFilename(Map<String, Object> data) {
|
||||||
|
return render(tplName + "-filename", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.njzscloud.common.gen.support;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class Tpl {
|
||||||
|
private String content;
|
||||||
|
private String dir;
|
||||||
|
private String filename;
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content == null ? "" : content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDir() {
|
||||||
|
return StrUtil.isBlank(dir) ? "" : dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return StrUtil.isBlank(filename) ? "" : filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
com.njzscloud.common.gen.config.GenAutoConfiguration
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<%
|
||||||
|
var entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));
|
||||||
|
var controllerClass = upperFirst(entityName) + "Controller";
|
||||||
|
var entityClass = upperFirst(entityName) + "Entity";
|
||||||
|
var entityInstance = entityName + "Entity";
|
||||||
|
var serviceClass = upperFirst(entityName) + "Service";
|
||||||
|
var serviceInstance = entityName + "Service";
|
||||||
|
var mapperClass = upperFirst(entityName) + "Mapper";
|
||||||
|
var baseUrl = table.name;
|
||||||
|
%>
|
||||||
|
package ${basePackage}.controller;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import com.njzscloud.common.core.utils.R;
|
||||||
|
import com.njzscloud.common.mp.support.PageParam;
|
||||||
|
import com.njzscloud.common.mp.support.PageResult;
|
||||||
|
import ${basePackage}.entity.${entityClass};
|
||||||
|
import ${basePackage}.service.${serviceClass};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ${table.comment}
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/${baseUrl}")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ${controllerClass} {
|
||||||
|
private final ${serviceClass} ${serviceInstance};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
@PostMapping("/add")
|
||||||
|
public R<?> add(@RequestBody ${entityClass} ${entityInstance}) {
|
||||||
|
${serviceInstance}.add(${entityInstance});
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
*/
|
||||||
|
@PostMapping("/modify")
|
||||||
|
public R<?> modify(@RequestBody ${entityClass} ${entityInstance}) {
|
||||||
|
${serviceInstance}.modify(${entityInstance});
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
@PostMapping("/del")
|
||||||
|
public R<?> del(@RequestBody List<Long> ids) {
|
||||||
|
${serviceInstance}.del(ids);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
public R<${entityClass}> detail(@RequestParam Long id) {
|
||||||
|
return R.success(${serviceInstance}.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
@GetMapping("/paging")
|
||||||
|
public R<PageResult<${entityClass}>> paging(PageParam pageParam, ${entityClass} ${entityInstance}) {
|
||||||
|
return R.success(${serviceInstance}.paging(pageParam, ${entityInstance}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<%
|
||||||
|
var entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));
|
||||||
|
var controllerClass = upperFirst(entityName) + "Controller";
|
||||||
|
var entityClass = upperFirst(entityName) + "Entity";
|
||||||
|
var entityInstance = entityName + "Entity";
|
||||||
|
var serviceClass = upperFirst(entityName) + "Service";
|
||||||
|
var serviceInstance = entityName + "Service";
|
||||||
|
var mapperClass = upperFirst(entityName) + "Mapper";
|
||||||
|
var baseUrl = table.name;
|
||||||
|
%>
|
||||||
|
package ${basePackage}.entity;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.ToString;
|
||||||
|
<%for(column in table.columns) {
|
||||||
|
var map = sqlDataTypeMap(column.dataType);
|
||||||
|
%>
|
||||||
|
<%if(!isBlank(map.importStatement)){%>
|
||||||
|
${map.importStatement}
|
||||||
|
<%}%>
|
||||||
|
<%}%>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ${table.comment}
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@TableName("${table.name}")
|
||||||
|
public class ${entityClass} {
|
||||||
|
|
||||||
|
<%
|
||||||
|
for(column in table.columns){
|
||||||
|
var map = sqlDataTypeMap(column.dataType);
|
||||||
|
%>
|
||||||
|
/**
|
||||||
|
* ${column.comment}
|
||||||
|
*/
|
||||||
|
<%if(column.primaryKey){%>
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
<%}%>
|
||||||
|
<%if(column.name == "creator_id" || column.name == "create_time"){%>
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
<%}else if(column.name == "modifier_id" || column.name == "modify_time"){%>
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
<%}else if(column.name == "deleted"){%>
|
||||||
|
@TableLogic
|
||||||
|
<%}%>
|
||||||
|
private ${map.dataType} ${toCamelCase(column.name)};
|
||||||
|
|
||||||
|
<%}%>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<%
|
||||||
|
var entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));
|
||||||
|
var controllerClass = upperFirst(entityName) + "Controller";
|
||||||
|
var entityClass = upperFirst(entityName) + "Entity";
|
||||||
|
var entityInstance = entityName + "Entity";
|
||||||
|
var serviceClass = upperFirst(entityName) + "Service";
|
||||||
|
var serviceInstance = entityName + "Service";
|
||||||
|
var mapperClass = upperFirst(entityName) + "Mapper";
|
||||||
|
var baseUrl = table.name;
|
||||||
|
%>
|
||||||
|
package ${basePackage}.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import ${basePackage}.entity.${entityClass};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ${table.comment}
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ${mapperClass} extends BaseMapper<${entityClass}> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<%
|
||||||
|
var entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));
|
||||||
|
var controllerClass = upperFirst(entityName) + "Controller";
|
||||||
|
var entityClass = upperFirst(entityName) + "Entity";
|
||||||
|
var entityInstance = entityName + "Entity";
|
||||||
|
var serviceClass = upperFirst(entityName) + "Service";
|
||||||
|
var serviceInstance = entityName + "Service";
|
||||||
|
var mapperClass = upperFirst(entityName) + "Mapper";
|
||||||
|
var baseUrl = table.name;
|
||||||
|
%>
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="${basePackage}.entity.${entityClass}">
|
||||||
|
</mapper>
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<%
|
||||||
|
var entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));
|
||||||
|
var controllerClass = upperFirst(entityName) + "Controller";
|
||||||
|
var entityClass = upperFirst(entityName) + "Entity";
|
||||||
|
var entityInstance = entityName + "Entity";
|
||||||
|
var serviceClass = upperFirst(entityName) + "Service";
|
||||||
|
var serviceInstance = entityName + "Service";
|
||||||
|
var mapperClass = upperFirst(entityName) + "Mapper";
|
||||||
|
var baseUrl = table.name;
|
||||||
|
%>
|
||||||
|
package ${basePackage}.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.njzscloud.common.mp.support.PageParam;
|
||||||
|
import com.njzscloud.common.mp.support.PageResult;
|
||||||
|
import ${basePackage}.entity.${entityClass};
|
||||||
|
import ${basePackage}.mapper.${mapperClass};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ${table.comment}
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ${serviceClass} extends ServiceImpl<${mapperClass}, ${entityClass}> implements IService<${entityClass}> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
public void add(${entityClass} ${entityInstance}) {
|
||||||
|
this.save(${entityInstance});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
*/
|
||||||
|
public void modify(${entityClass} ${entityInstance}) {
|
||||||
|
this.updateById(${entityInstance});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void del(List<Long> ids) {
|
||||||
|
this.removeBatchByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
public ${entityClass} detail(Long id) {
|
||||||
|
return this.getById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
public PageResult<${entityClass}> paging(PageParam pageParam, ${entityClass} ${entityInstance}) {
|
||||||
|
return PageResult.of(this.page(pageParam.toPage(), Wrappers.<${entityClass}>query(${entityInstance})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"tplName": "Service",
|
||||||
|
"tplCategory": "Service",
|
||||||
|
"tpl": {
|
||||||
|
"dir": "",
|
||||||
|
"filename": "",
|
||||||
|
"content": "<%\nvar entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));\nvar controllerClass = upperFirst(entityName) + \"Controller\";\nvar entityClass = upperFirst(entityName) + \"Entity\";\nvar entityInstance = entityName + \"Entity\";\nvar serviceClass = upperFirst(entityName) + \"Service\";\nvar serviceInstance = entityName + \"Service\";\nvar mapperClass = upperFirst(entityName) + \"Mapper\";\nvar baseUrl = entityName + \"Service\";\n%>\npackage ${basePackage}.service;\n\nimport lombok.extern.slf4j.Slf4j;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.njzscloud.common.mp.support.PageParam;\nimport com.njzscloud.common.mp.support.PageResult;\nimport java.util.List;\nimport org.springframework.transaction.annotation.Transactional;\nimport ${basePackage}.entity.${entityClass};\nimport ${basePackage}.mapper.${mapperClass};\n\n/**\n * ${table.comment}\n */\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class ${serviceClass} extends ServiceImpl<${mapperClass}, ${entityClass}> implements IService<${entityClass}> {\n\n /**\n * 新增\n */\n public void add(${entityClass} ${entityInstance}) {\n this.save(${entityInstance});\n }\n\n /**\n * 修改\n */\n public void modify(${entityClass} ${entityInstance}) {\n this.updateById(${entityInstance});\n }\n\n /**\n * 删除\n */\n @Transactional(rollbackFor = Exception.class)\n public void del(List<Long> ids) {\n this.removeBatchByIds(ids);\n }\n\n /**\n * 详情\n */\n public ${entityClass} detail(Long id) {\n return this.getById(id);\n }\n\n /**\n * 分页查询\n */\n public PageResult<${entityClass}> paging(PageParam pageParam, ${entityClass} ${entityInstance}) {\n return PageResult.of(this.page(pageParam.toPage(), Wrappers.<${entityClass}>query(${entityInstance})));\n }\n}\n"
|
||||||
|
},
|
||||||
|
"modelData": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"id": "1947531036271337474",
|
||||||
|
"tpl": {
|
||||||
|
"dir": "",
|
||||||
|
"filename": "",
|
||||||
|
"content": "<%\nvar entityName = toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix));\nvar controllerClass = upperFirst(entityName) + \"Controller\";\nvar entityClass = upperFirst(entityName) + \"Entity\";\nvar entityInstance = entityName + \"Entity\";\nvar serviceClass = upperFirst(entityName) + \"Service\";\nvar serviceInstance = entityName + \"Service\";\nvar baseUrl = entityName + \"Service\";\n%>\npackage ${basePackage}.entity;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\nimport com.baomidou.mybatisplus.annotation.*;\nimport lombok.ToString;\n<%for(column in table.columns) {\n var map = sqlDataTypeMap(column.dataType);\n%>\n<%if(!isBlank(map.importStatement)){%>\n${map.importStatement}\n<%}%>\n<%}%>\n\n/**\n * ${table.comment}\n */\n@Getter\n@Setter\n@ToString\n@Accessors(chain = true)\n@TableName(\"${table.name}\")\npublic class ${entityClass} {\n\n<%\nfor(column in table.columns){\n var map = sqlDataTypeMap(column.dataType);\n%>\n /**\n * ${column.comment}\n */\n <%if(column.primaryKey){%>\n @TableId(type = IdType.ASSIGN_ID)\n <%}%>\n <%if(column.name == \"creator_id\" || column.name == \"create_time\"){%>\n @TableId(type = IdType.ASSIGN_ID)\n <%}else if(column.name == \"modifier_id\" || column.name == \"modify_time\"){%>\n @TableField(fill = FieldFill.INSERT_UPDATE)\n <%}else if(column.name == \"deleted\"){%>\n @TableLogic\n <%}%>\n private ${map.dataType} ${toCamelCase(column.name)};\n\n<%}%>\n}\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_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-http</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- okhttp -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
package com.njzscloud.common.http;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import com.njzscloud.common.http.config.HttpClientProperties;
|
||||||
|
import com.njzscloud.common.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.http.support.ResponseInfo;
|
||||||
|
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,220 @@
|
||||||
|
package com.njzscloud.common.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 cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.njzscloud.common.core.ex.Exceptions;
|
||||||
|
import com.njzscloud.common.core.tuple.Tuple2;
|
||||||
|
import com.njzscloud.common.http.annotation.GetEndpoint;
|
||||||
|
import com.njzscloud.common.http.annotation.PostEndpoint;
|
||||||
|
import com.njzscloud.common.http.annotation.RemoteServer;
|
||||||
|
import com.njzscloud.common.http.constant.HttpMethod;
|
||||||
|
import com.njzscloud.common.http.interceptor.RequestInterceptor;
|
||||||
|
import com.njzscloud.common.http.interceptor.ResponseInterceptor;
|
||||||
|
import com.njzscloud.common.http.resolver.*;
|
||||||
|
import com.njzscloud.common.http.support.RequestInfo;
|
||||||
|
import com.njzscloud.common.http.support.ResponseInfo;
|
||||||
|
import com.njzscloud.common.http.support.ResponseResult;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务装饰器, 用于 "接口注解式" HTTP 请求配置, 使用 JDK 动态代理进行接口装饰
|
||||||
|
* <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, () -> Proxy.newProxyInstance(
|
||||||
|
clazz.getClassLoader(),
|
||||||
|
new Class[]{clazz},
|
||||||
|
new MethodInterceptorImpl(clazz, HTTP_CLIENT)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
private static class MethodInterceptorImpl implements InvocationHandler {
|
||||||
|
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 String name;
|
||||||
|
private final RequestInterceptor requestInterceptor;
|
||||||
|
private final ResponseInterceptor responseInterceptor;
|
||||||
|
|
||||||
|
public MethodInterceptorImpl(Class<?> clazz, HttpClient httpClient) {
|
||||||
|
RemoteServer anno = clazz.getAnnotation(RemoteServer.class);
|
||||||
|
String name = anno.name();
|
||||||
|
if (StrUtil.isBlank(name)) {
|
||||||
|
this.name = clazz.getName();
|
||||||
|
} else {
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
}
|
||||||
|
String value = anno.value();
|
||||||
|
if (value.startsWith("${") && value.endsWith("}")) {
|
||||||
|
value = SpringUtil.getProperty(value.substring(2, value.length() - 1));
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(value)) {
|
||||||
|
throw Exceptions.error("地址不合法");
|
||||||
|
}
|
||||||
|
Matcher matcher = ADDR_PATTERN.matcher(value);
|
||||||
|
Assert.isTrue(matcher.matches(), () -> Exceptions.error("地址不合法"));
|
||||||
|
baseUrl = value;
|
||||||
|
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 invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||||
|
HttpMethod httpMethod;
|
||||||
|
String urlTpl;
|
||||||
|
String desc;
|
||||||
|
String methodName = method.getName();
|
||||||
|
switch (methodName) {
|
||||||
|
case "hashCode" -> {
|
||||||
|
return System.identityHashCode(proxy);
|
||||||
|
}
|
||||||
|
case "equals" -> {
|
||||||
|
return proxy == args[0];
|
||||||
|
}
|
||||||
|
case "toString" -> {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetEndpoint getEndpoint = method.getAnnotation(GetEndpoint.class);
|
||||||
|
if (getEndpoint == null) {
|
||||||
|
PostEndpoint postEndpoint = method.getAnnotation(PostEndpoint.class);
|
||||||
|
if (postEndpoint == null) {
|
||||||
|
return method.invoke(this, args);
|
||||||
|
} 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.of("", 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 typeWrap) {
|
||||||
|
|
||||||
|
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,23 @@
|
||||||
|
package com.njzscloud.common.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.http.processor.BodyParamProcessor;
|
||||||
|
import com.njzscloud.common.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.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.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.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.http.annotation;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njzscloud.common.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.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.http.processor.DefaultJsonBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.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.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.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.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.http.processor.DefaultPathParamProcessor;
|
||||||
|
import com.njzscloud.common.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.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.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.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,46 @@
|
||||||
|
package com.njzscloud.common.http.annotation;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njzscloud.common.http.interceptor.CompositeInterceptor;
|
||||||
|
import com.njzscloud.common.http.interceptor.RequestInterceptor;
|
||||||
|
import com.njzscloud.common.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() default "";
|
||||||
|
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求拦截器, 在请求之前触发
|
||||||
|
*
|
||||||
|
* @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.http.annotation;
|
||||||
|
|
||||||
|
import com.njzscloud.common.http.processor.DefaultXmlBodyParamProcessor;
|
||||||
|
import com.njzscloud.common.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,25 @@
|
||||||
|
package com.njzscloud.common.http.config;
|
||||||
|
|
||||||
|
import com.njzscloud.common.http.HttpClient;
|
||||||
|
import com.njzscloud.common.http.HttpClientDecorator;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http客户端自动配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(HttpClientProperties.class)
|
||||||
|
public class HttpClientAutoConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HttpClient httpClient(HttpClientProperties httpClientProperties) {
|
||||||
|
return new HttpClient(httpClientProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public HttpClientDecorator httpClientDecorator(HttpClient httpClient) {
|
||||||
|
return new HttpClientDecorator(httpClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.njzscloud.common.http.config;
|
||||||
|
|
||||||
|
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("http-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,8 @@
|
||||||
|
package com.njzscloud.common.http.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 方法
|
||||||
|
*/
|
||||||
|
public enum HttpMethod {
|
||||||
|
GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, CONNECT, PATCH
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue