Merge branch 'master' of https://git.njzscloud.com/lzq/njzscloud-dispose
# Conflicts: # z-doc/warehouse_management_schema.sqlmaster
commit
4acaccf533
|
|
@ -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,124 @@
|
|||
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[] declaredFields = valueClazz.getDeclaredFields();
|
||||
Field field = null;
|
||||
for (Field field_ : declaredFields) {
|
||||
if (field_.getName().equals(currentName)) {
|
||||
field = field_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (field == null) {
|
||||
declaredFields = valueClazz.getSuperclass().getDeclaredFields();
|
||||
for (Field field_ : declaredFields) {
|
||||
if (field_.getName().equals(currentName)) {
|
||||
field = field_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (field == null) {
|
||||
throw new NoSuchFieldException("字段不存在:" + 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,121 @@
|
|||
package com.njzscloud.common.gen;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.njzscloud.common.core.utils.Mime;
|
||||
import com.njzscloud.common.core.utils.R;
|
||||
import com.njzscloud.common.mp.support.PageParam;
|
||||
import com.njzscloud.common.mp.support.PageResult;
|
||||
import com.njzscloud.common.mvc.util.ServletUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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("/download")
|
||||
public void download(@RequestParam("lang") String lang,
|
||||
@RequestParam("tableName") String tableName,
|
||||
@RequestBody(required = false) Map<String, Object> data,
|
||||
HttpServletResponse response) {
|
||||
|
||||
ByteArrayOutputStream out = tplService.download(lang, tableName, data);
|
||||
ServletUtil.download(response, out.toByteArray(), Mime.ZIP, DateTime.now() + ".zip");
|
||||
}
|
||||
|
||||
@PostMapping("/preview")
|
||||
public R<?> preview(@RequestParam("lang") String lang,
|
||||
@RequestParam("tableName") String tableName,
|
||||
@RequestBody(required = false) Map<String, Object> data) {
|
||||
|
||||
Map<String, Map<String, String>> map = tplService.preview(lang, tableName, data);
|
||||
|
||||
return R.success(map);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取表信息
|
||||
*/
|
||||
@GetMapping("/table/paging")
|
||||
public R<PageResult<Map<String, Object>>> tablePaging(PageParam pageParam,
|
||||
@RequestParam(value = "tableSchema", required = false) String tableSchema,
|
||||
@RequestParam(value = "tableName", required = false) String tableName
|
||||
) {
|
||||
return R.success(tplService.tablePaging(pageParam, tableSchema, tableName));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有模板
|
||||
*/
|
||||
@GetMapping("/list_all")
|
||||
public R<?> listAll(@RequestParam("lang") String lang) {
|
||||
return R.success(tplService
|
||||
.list(Wrappers.lambdaQuery(TplEntity.class)
|
||||
.eq(TplEntity::getLang, lang)
|
||||
.orderByAsc(TplEntity::getTplName)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
public R<TplEntity> detail(@RequestParam("id") 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,51 @@
|
|||
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.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;
|
||||
|
||||
private String lang;
|
||||
|
||||
/**
|
||||
* 模板内容
|
||||
*/
|
||||
@TableField(typeHandler = JsonTypeHandler.class)
|
||||
private Tpl tpl;
|
||||
|
||||
/**
|
||||
* 模型数据
|
||||
*/
|
||||
@TableField(typeHandler = JsonTypeHandler.class)
|
||||
private Map<String, Object> modelData;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.njzscloud.common.gen;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 代码模板
|
||||
*/
|
||||
@Mapper
|
||||
public interface TplMapper extends BaseMapper<TplEntity> {
|
||||
|
||||
@Select("SELECT TABLE_SCHEMA tableSchema, TABLE_NAME tableName, TABLE_COMMENT tableComment\n" +
|
||||
"FROM information_schema.TABLES\n" +
|
||||
"${ew.customSqlSegment}")
|
||||
IPage<Map<String, Object>> tablePaging(Page<Object> page, @Param("ew") QueryWrapper<Object> ew);
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package com.njzscloud.common.gen;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
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.core.tuple.Tuple2;
|
||||
import com.njzscloud.common.core.tuple.Tuple3;
|
||||
import com.njzscloud.common.gen.support.DbMetaData;
|
||||
import com.njzscloud.common.gen.support.Generator;
|
||||
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.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 代码模板
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
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("模板名称不能为空"));
|
||||
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("模板名称不能为空"));
|
||||
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)));
|
||||
}
|
||||
|
||||
private final DbMetaData dbMetaData;
|
||||
|
||||
public PageResult<Map<String, Object>> tablePaging(PageParam pageParam, String tableSchema, String tableName) {
|
||||
pageParam.setOrders("TABLE_NAME");
|
||||
if (StrUtil.isBlank(tableSchema)) {
|
||||
tableSchema = dbMetaData.getDatabaseName();
|
||||
}
|
||||
Assert.notBlank(tableSchema, () -> Exceptions.exception("数据库名称获取失败"));
|
||||
return PageResult.of(baseMapper.tablePaging(pageParam.toPage(), Wrappers.query()
|
||||
.eq("TABLE_SCHEMA", tableSchema)
|
||||
.like(StrUtil.isNotBlank(tableName), "TABLE_NAME", tableName)
|
||||
));
|
||||
}
|
||||
|
||||
public Map<String, Map<String, String>> preview(String lang, String tableName, Map<String, Object> data) {
|
||||
Map<String, Map<String, String>> map = MapUtil.newHashMap();
|
||||
List<String> tplNames = this.list(Wrappers.lambdaQuery(TplEntity.class)
|
||||
.eq(TplEntity::getLang, lang))
|
||||
.stream()
|
||||
.map(TplEntity::getTplName)
|
||||
.toList();
|
||||
|
||||
for (String tplName : tplNames) {
|
||||
|
||||
Tuple2<String, String> res = generate(tplName, tableName, data);
|
||||
|
||||
map.put(tplName, MapUtil.<String, String>builder()
|
||||
.put("path", res.get_0())
|
||||
.put("content", res.get_1())
|
||||
.build());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream download(String lang, String tableName, Map<String, Object> data) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
List<String> tplNames = this.list(Wrappers.lambdaQuery(TplEntity.class)
|
||||
.eq(TplEntity::getLang, lang))
|
||||
.stream()
|
||||
.map(TplEntity::getTplName)
|
||||
.toList();
|
||||
if (tplNames.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
int size = tplNames.size();
|
||||
String[] paths = new String[size];
|
||||
InputStream[] ins = new InputStream[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
String tplName = tplNames.get(i);
|
||||
Tuple2<String, String> res = generate(tplName, tableName, data);
|
||||
paths[i] = res.get_0();
|
||||
ins[i] = new ByteArrayInputStream(res.get_1().getBytes());
|
||||
}
|
||||
ZipUtil.zip(out, paths, ins);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private Tuple2<String, String> generate(String tplName, String tableName, Map<String, Object> data) {
|
||||
Tuple3<String, String, String> res = Generator.generate(tplName, tableName, data);
|
||||
String res0 = res.get_0();
|
||||
String res1 = res.get_1();
|
||||
String res2 = res.get_2();
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
if (StrUtil.isNotBlank(res0)) {
|
||||
pathBuilder.append(res0.trim());
|
||||
if (!res0.endsWith("/")) pathBuilder.append("/");
|
||||
}
|
||||
if (StrUtil.isNotBlank(res1)) pathBuilder.append(res1.trim());
|
||||
else pathBuilder.append(IdUtil.fastSimpleUUID()).append(".txt");
|
||||
return Tuple2.of(pathBuilder.toString(), res2);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(DbMetaData dbMetaData) {
|
||||
return new TplService(dbMetaData);
|
||||
}
|
||||
|
||||
@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,67 @@
|
|||
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 toDashCase = (Object[] paras, Context ctx) -> StrUtil.toSymbolCase(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 javaType = 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"));
|
||||
}
|
||||
};
|
||||
|
||||
public static final Function tsType = new Function() {
|
||||
private final Map<String, Map<String, String>> map = MapUtil.<String, Map<String, String>>builder()
|
||||
.put("DEFAULT", MapUtil.<String, String>builder().put("dataType", "").build())
|
||||
.put("VARCHAR", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("CHAR", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("LONGTEXT", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("TEXT", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("BIT", MapUtil.<String, String>builder().put("dataType", "boolean").build())
|
||||
.put("TINYINT", MapUtil.<String, String>builder().put("dataType", "number").build())
|
||||
.put("SMALLINT", MapUtil.<String, String>builder().put("dataType", "number").build())
|
||||
.put("MEDIUMINT", MapUtil.<String, String>builder().put("dataType", "number").build())
|
||||
.put("INT", MapUtil.<String, String>builder().put("dataType", "number").build())
|
||||
.put("BIGINT", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("DOUBLE", MapUtil.<String, String>builder().put("dataType", "number").build())
|
||||
.put("DECIMAL", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("DATE", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("TIME", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.put("DATETIME", MapUtil.<String, String>builder().put("dataType", "string").build())
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public Object call(Object[] paras, Context ctx) {
|
||||
return map.getOrDefault(paras[0].toString().toUpperCase(), map.get("DEFAULT"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
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 String getDatabaseName() {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
return conn.getCatalog();
|
||||
} catch (Exception e) {
|
||||
log.error("获取数据库名称失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getTableMetaData(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,56 @@
|
|||
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 final DbMetaData dbMetaData = SpringUtil.getBean(DbMetaData.class);
|
||||
private static final TplService tplService = SpringUtil.getBean(TplService.class);
|
||||
|
||||
private static Tuple3<String, String, String> generate0(String tplName, String tableName, Map<String, Object> data) {
|
||||
|
||||
Assert.notBlank(tableName, () -> Exceptions.clierr("未指定表名称"));
|
||||
data.put("tableName", tableName);
|
||||
List<Map<String, Object>> table = dbMetaData.getTableMetaData(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,91 @@
|
|||
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("toDashCase", Btl.toDashCase);
|
||||
getTemplate_.registerFunction("upperFirst", Btl.upperFirst);
|
||||
getTemplate_.registerFunction("isBlank", Btl.isBlank);
|
||||
getTemplate_.registerFunction("subAfter", Btl.subAfter);
|
||||
getTemplate_.registerFunction("javaType", Btl.javaType);
|
||||
getTemplate_.registerFunction("tsType", Btl.tsType);
|
||||
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,75 @@
|
|||
<%
|
||||
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 = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
%>
|
||||
package ${basePackage}.${moduleName}.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 java.util.List;
|
||||
import ${basePackage}.${moduleName}.pojo.entity.${entityClass};
|
||||
import ${basePackage}.${moduleName}.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 = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
%>
|
||||
package ${basePackage}.${moduleName}.pojo.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 = javaType(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 = javaType(column.dataType);
|
||||
%>
|
||||
/**
|
||||
* ${column.comment}
|
||||
*/
|
||||
<%if(column.primaryKey){%>
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
<%}%>
|
||||
<%if(column.name == "creator_id" || column.name == "create_time"){%>
|
||||
@TableId(fill = FieldFill.INSERT)
|
||||
<%}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 = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
%>
|
||||
package ${basePackage}.${moduleName}.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import ${basePackage}.${moduleName}.pojo.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 = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
%>
|
||||
<?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}.${moduleName}.pojo.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 = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
%>
|
||||
package ${basePackage}.${moduleName}.service;
|
||||
|
||||
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 java.util.List;
|
||||
import ${basePackage}.${moduleName}.pojo.entity.${entityClass};
|
||||
import ${basePackage}.${moduleName}.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,116 @@
|
|||
{
|
||||
"ts": [
|
||||
{
|
||||
"modelData": {
|
||||
"prefix": "",
|
||||
"basePackage": "",
|
||||
"moduleName": "",
|
||||
"subModuleName": ""
|
||||
},
|
||||
"tplName": "page.vue",
|
||||
"tpl": {
|
||||
"dir": "${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>",
|
||||
"filename": "${pageVueName}",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {
|
||||
"moduleName": "",
|
||||
"subModuleName": ""
|
||||
},
|
||||
"tplName": "form.vue",
|
||||
"tpl": {
|
||||
"dir": "${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>",
|
||||
"filename": "${formVueName}",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {
|
||||
"moduleName": "",
|
||||
"subModuleName": ""
|
||||
},
|
||||
"tplName": "page.ts",
|
||||
"tpl": {
|
||||
"dir": "${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>",
|
||||
"filename": "${pageTsName}",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {
|
||||
"moduleName": "",
|
||||
"subModuleName": ""
|
||||
},
|
||||
"tplName": "api.ts",
|
||||
"tpl": {
|
||||
"dir": "${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>",
|
||||
"filename": "${apiTsName}",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {
|
||||
"moduleName": "",
|
||||
"subModuleName": ""
|
||||
},
|
||||
"tplName": "d.ts",
|
||||
"tpl": {
|
||||
"dir": "${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>",
|
||||
"filename": "${dTsName}",
|
||||
"content": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"java": [
|
||||
{
|
||||
"modelData": {
|
||||
"basePackage": "",
|
||||
"moduleName": ""
|
||||
},
|
||||
"tplName": "entity.java",
|
||||
"tpl": {
|
||||
"dir": "pojo/entity",
|
||||
"filename": "${upperFirst(toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix)))}Entity.java",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {},
|
||||
"tplName": "mapper.java",
|
||||
"tpl": {
|
||||
"dir": "mapper",
|
||||
"filename": "${upperFirst(toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix)))}Mapper.java",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {},
|
||||
"tplName": "mapper.xml",
|
||||
"tpl": {
|
||||
"dir": "",
|
||||
"filename": "${upperFirst(toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix)))}Mapper.xml",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {},
|
||||
"tplName": "service.java",
|
||||
"tpl": {
|
||||
"dir": "service",
|
||||
"filename": "${upperFirst(toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix)))}Service.java",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"modelData": {},
|
||||
"tplName": "controller.java",
|
||||
"tpl": {
|
||||
"dir": "controller",
|
||||
"filename": "${upperFirst(toCamelCase(isBlank(prefix) ? table.name : subAfter(table.name, prefix)))}Controller.java",
|
||||
"content": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<%
|
||||
var ulc = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
var lcc = toCamelCase(ulc);
|
||||
var ucc = upperFirst(lcc);
|
||||
var dc = toDashCase(lcc);
|
||||
|
||||
var pageVueName = ucc + ".vue";
|
||||
var formVueName = ucc + "Form.vue";
|
||||
var pageTsName = dc + ".ts";
|
||||
var apiTsName = dc + "-api.ts";
|
||||
var dTsName = dc + ".d.ts";
|
||||
%>
|
||||
import {
|
||||
get,
|
||||
post
|
||||
} from '@/common/utils/http-util.ts'
|
||||
|
||||
export default {
|
||||
paging(data: ${ucc}Types.Search${ucc}Param) {
|
||||
return get<G.PageResult<${ucc}Types.Search${ucc}Result>>('/${ulc}/paging', data)
|
||||
},
|
||||
detail(id: string) {
|
||||
return get<${ucc}Types.Search${ucc}Result>('/${ulc}/detail', {id})
|
||||
},
|
||||
add(data: ${ucc}Types.Add${ucc}Param) {
|
||||
return post('/${ulc}/add', data)
|
||||
},
|
||||
modify(data: ${ucc}Types.Modify${ucc}Param) {
|
||||
return post('/${ulc}/modify', data)
|
||||
},
|
||||
del(ids: string[]) {
|
||||
return post('/${ulc}/del', ids)
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<%
|
||||
var ulc = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
var lcc = toCamelCase(ulc);
|
||||
var ucc = upperFirst(lcc);
|
||||
var dc = toDashCase(lcc);
|
||||
|
||||
var pageVueName = ucc + ".vue";
|
||||
var formVueName = ucc + "Form.vue";
|
||||
var pageTsName = dc + ".ts";
|
||||
var apiTsName = dc + "-api.ts";
|
||||
var dTsName = dc + ".d.ts";
|
||||
%>
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
namespace ${ucc}Types {
|
||||
interface Search${ucc}Param extends G.PageParam {
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
// ${column.comment}
|
||||
${toCamelCase(column.name)}?: ${map.dataType}
|
||||
<%}%>
|
||||
}
|
||||
|
||||
interface Search${ucc}Result {
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
// ${column.comment}
|
||||
${toCamelCase(column.name)}?: ${map.dataType}
|
||||
<%}%>
|
||||
}
|
||||
|
||||
interface Add${ucc}Param {
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
// ${column.comment}
|
||||
${toCamelCase(column.name)}?: ${map.dataType}
|
||||
<%}%>
|
||||
}
|
||||
|
||||
interface Modify${ucc}Param {
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
// ${column.comment}
|
||||
${toCamelCase(column.name)}?: ${map.dataType}
|
||||
<%}%>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<%
|
||||
var ulc = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
var lcc = toCamelCase(ulc);
|
||||
var ucc = upperFirst(lcc);
|
||||
var dc = toDashCase(lcc);
|
||||
|
||||
var pageVueName = ucc + ".vue";
|
||||
var formVueName = ucc + "Form.vue";
|
||||
var pageTsName = dc + ".ts";
|
||||
var apiTsName = dc + "-api.ts";
|
||||
var dTsName = dc + ".d.ts";
|
||||
%>
|
||||
<template>
|
||||
<ElDialog v-model="showDialog"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
width="fit-content"
|
||||
ref="${lcc}Form"
|
||||
@close="dialogCloseHandler">
|
||||
<ElForm :model="formData"
|
||||
:rules="rules"
|
||||
class="form-panel"
|
||||
label-width="auto">
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
<ElFormItem label="${column.comment}" prop="${toCamelCase(column.name)}">
|
||||
<ElInput
|
||||
v-model="formData.${toCamelCase(column.name)}"
|
||||
:disabled="status === 'view'"
|
||||
placeholder="${column.comment}"/>
|
||||
</ElFormItem>
|
||||
<%}%>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
|
||||
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler">提交</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ${ucc}Api from '@/pages/${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>/${apiTsName}'
|
||||
import Strings from '@/common/utils/strings.ts'
|
||||
import FormUtil from '@/common/utils/formUtil.ts'
|
||||
import {
|
||||
ElMessage,
|
||||
type FormInstance,
|
||||
type FormRules,
|
||||
} from 'element-plus'
|
||||
|
||||
const emits = defineEmits(['editSucc'])
|
||||
const showDialog = ref(false)
|
||||
const submiting = ref(false)
|
||||
const status = ref<'add' | 'view' | 'modify'>('add')
|
||||
|
||||
const ${lcc}FormIns = useTemplateRef<FormInstance>('${lcc}Form')
|
||||
|
||||
const formData = ref<${ucc}Types.Search${ucc}Result>({})
|
||||
const rules = reactive<FormRules<${ucc}Types.Search${ucc}Result>>({
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
%>
|
||||
${toCamelCase(column.name)}: [{ required: true, message: '请填写${column.comment}', trigger: 'blur' }],
|
||||
<%}%>
|
||||
})
|
||||
|
||||
function dialogCloseHandler() {
|
||||
formData.vaule = {}
|
||||
}
|
||||
|
||||
function submitHandler() {
|
||||
if (status.value === 'view') return
|
||||
submiting.value = true
|
||||
if (formData.vaule.id != null) {
|
||||
FormUtil.submit(${lcc}FormIns, () => ${ucc}Api.modify(formData.vaule))
|
||||
.then(() => {
|
||||
ElMessage.success('修改成功')
|
||||
emits('editSucc')
|
||||
showDialog.value = false
|
||||
})
|
||||
.finally(() => {
|
||||
submiting.value = false
|
||||
})
|
||||
} else {
|
||||
FormUtil.submit(${lcc}FormIns, () => ${ucc}Api.add(formData.vaule))
|
||||
.then(() => {
|
||||
ElMessage.success('添加成功')
|
||||
emits('editSucc')
|
||||
showDialog.value = false
|
||||
})
|
||||
.finally(() => {
|
||||
submiting.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open(data: ${ucc}Types.Search${ucc}Result = {}) {
|
||||
showDialog.value = true
|
||||
if (!Strings.isBlank(data.id)) {
|
||||
status.value = 'modify'
|
||||
${ucc}Api.detail(data.id!)
|
||||
.then(res => {
|
||||
formData.vaule = res.data
|
||||
})
|
||||
} else {
|
||||
status.value = 'add'
|
||||
formData.vaule = data
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.form-panel {
|
||||
padding 20px
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<%
|
||||
var ulc = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
var lcc = toCamelCase(ulc);
|
||||
var ucc = upperFirst(lcc);
|
||||
var dc = toDashCase(lcc);
|
||||
|
||||
var pageVueName = ucc + ".vue";
|
||||
var formVueName = ucc + "Form.vue";
|
||||
var pageTsName = dc + ".ts";
|
||||
var apiTsName = dc + "-api.ts";
|
||||
var dTsName = dc + ".d.ts";
|
||||
%>
|
||||
export default {
|
||||
component: () => import('@/pages/${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>/${pageVueName}'),
|
||||
} as RouterTypes.RouteConfig
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
<%
|
||||
var ulc = isBlank(prefix) ? table.name : subAfter(table.name, prefix);
|
||||
var lcc = toCamelCase(ulc);
|
||||
var ucc = upperFirst(lcc);
|
||||
var dc = toDashCase(lcc);
|
||||
|
||||
var pageVueName = ucc + ".vue";
|
||||
var formVueName = ucc + "Form.vue";
|
||||
var pageTsName = dc + ".ts";
|
||||
var apiTsName = dc + "-api.ts";
|
||||
var dTsName = dc + ".d.ts";
|
||||
%>
|
||||
<template>
|
||||
<Page>
|
||||
<ElForm v-show="showSearchForm" inline @submit.prevent="paging">
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
<ElFormItem label="${column.comment}">
|
||||
<ElInput
|
||||
v-model="searchForm.${toCamelCase(column.name)}"
|
||||
placeholder="${column.comment}"/>
|
||||
</ElFormItem>
|
||||
<%}%>
|
||||
<ElFormItem>
|
||||
<ElButton :icon="elIcons.Search" :loading="searching" native-type="submit" type="primary">搜索</ElButton>
|
||||
<ElButton :icon="elIcons.Refresh" :loading="searching" @click="reset">重置</ElButton>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<div class="tool-bar">
|
||||
<ElButton :icon="elIcons.Plus" type="primary" @click="addHandler">新建</ElButton>
|
||||
<ElButton :icon="elIcons.Filter" type="default" @click="showSearchForm = !showSearchForm"/>
|
||||
</div>
|
||||
|
||||
<ElTable v-loading="searching" :data="tableData"
|
||||
cell-class-name="table-cell"
|
||||
class="table-list"
|
||||
empty-text="暂无数据"
|
||||
header-row-class-name="table-header"
|
||||
row-key="id">
|
||||
<%
|
||||
for(column in table.columns) {
|
||||
var map = tsType(column.dataType);
|
||||
%>
|
||||
<ElTableColumn label="${column.comment}" prop="${toCamelCase(column.name)}"/>
|
||||
<%}%>
|
||||
<ElTableColumn label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<div class="action-btn">
|
||||
<ElPopconfirm
|
||||
confirm-button-text="是"
|
||||
cancel-button-text="否"
|
||||
confirm-button-type="danger"
|
||||
cancel-button-type="primary"
|
||||
placement="top"
|
||||
title="是否删除当前数据?"
|
||||
width="180"
|
||||
@confirm="delHandler(scope)">
|
||||
<template #reference>
|
||||
<ElButton text type="danger" :loading="deling">删除</ElButton>
|
||||
</template>
|
||||
</ElPopconfirm>
|
||||
<ElButton text type="primary" @click="modifyHandler(scope)">修改</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
<ElPagination
|
||||
class="pagination"
|
||||
layout="prev, pager, next"
|
||||
:page-size="pagination.size"
|
||||
:total="pagination.total"
|
||||
@change="pageChangeHandler"/>
|
||||
<${ucc}Form ref="${lcc}Form" @edit-succ="paging"/>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ${ucc}Api from '@/pages/${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>/${apiTsName}'
|
||||
import ${ucc}Form from '@/pages/${moduleName}<%if(subModuleName != null){%>/${subModuleName}<%}%>/${formVueName}'
|
||||
import Page from '@/components/page/Page.vue'
|
||||
import { elIcons } from '@/common/element/element.ts'
|
||||
|
||||
const tableData = ref<${ucc}Types.Search${ucc}Result[]>([])
|
||||
const searchForm = reactive<${ucc}Types.Search${ucc}Param>({
|
||||
current: 1,
|
||||
size: 20,
|
||||
})
|
||||
const searching = ref(false)
|
||||
const deling = ref(false)
|
||||
const showSearchForm = ref(true)
|
||||
const ${lcc}FormIns = useTemplateRef<InstanceType<typeof ${ucc}Form>>('${lcc}Form')
|
||||
const pagination = reactive<G.Pagination>({
|
||||
total: 0,
|
||||
current: 1,
|
||||
size: 1,
|
||||
})
|
||||
function pageChangeHandler(currentPage: number, pageSize: number) {
|
||||
searchForm.current = currentPage
|
||||
searchForm.size = pageSize
|
||||
paging()
|
||||
}
|
||||
function showDialog(data?: ${ucc}Types.Search${ucc}Result) {
|
||||
${lcc}FormIns.value?.open(data)
|
||||
}
|
||||
|
||||
function delHandler({row}: { row: ${ucc}Types.Search${ucc}Result }) {
|
||||
deling.value = true
|
||||
${ucc}Api.del([ row.id! ])
|
||||
.then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
paging()
|
||||
})
|
||||
.finally(() => {
|
||||
deling.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function modifyHandler({row}: { row: ${ucc}Types.Search${ucc}Result }) {
|
||||
showDialog(row)
|
||||
}
|
||||
|
||||
function addHandler() {
|
||||
showDialog()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
Object.assign(searchForm, {})
|
||||
paging()
|
||||
}
|
||||
|
||||
function paging() {
|
||||
searching.value = true
|
||||
${ucc}Api.paging(searchForm)
|
||||
.then(res => {
|
||||
tableData.value = res.data?.records ?? []
|
||||
})
|
||||
.finally(() => {
|
||||
searching.value = false
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
paging()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.table-list {
|
||||
flex 1;
|
||||
width 100%
|
||||
|
||||
:deep(.table-header) {
|
||||
color #454C59
|
||||
|
||||
th {
|
||||
background-color #EDF1F7
|
||||
font-weight 500
|
||||
position relative
|
||||
|
||||
& > div {
|
||||
display flex
|
||||
gap 5px
|
||||
align-items center
|
||||
}
|
||||
|
||||
&:not(:first-child) > div::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1px;
|
||||
width: 1px;
|
||||
background-color: #D3D7DE;
|
||||
transform: translateY(-50%);
|
||||
content: "";
|
||||
height 50%
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.table-cell) {
|
||||
color #2F3540
|
||||
}
|
||||
.action-btn {
|
||||
width 100%
|
||||
display flex
|
||||
flex-wrap wrap
|
||||
|
||||
& > button {
|
||||
margin 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tool-bar {
|
||||
display flex
|
||||
justify-content space-between
|
||||
margin 0 0 20px 0
|
||||
}
|
||||
|
||||
.pagination {
|
||||
justify-content: end;
|
||||
margin: 8px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue