云快充1.5.0 初始化

This commit is contained in:
3god
2024-10-08 09:38:54 +08:00
parent dea6774942
commit cb19b45919
297 changed files with 18020 additions and 28 deletions

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
抖音关注:程序员三丙
知识星球https://t.zsxq.com/j9b21
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>sanbing</groupId>
<artifactId>jcpp-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jcpp-infrastructure-util</artifactId>
<packaging>jar</packaging>
<name>JChargePointProtocol Infrastructure Util Module</name>
<description>基础工具模块</description>
<properties>
<main.dir>${basedir}/..</main.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,34 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* @author baigod
*/
public class JCPPHashUtil {
public static HashFunction forName(String name) {
return switch (name) {
case "murmur3_32" -> Hashing.murmur3_32_fixed();
case "murmur3_128" -> Hashing.murmur3_128();
case "sha256" -> Hashing.sha256();
default -> throw new IllegalArgumentException("Can't find hash function with name " + name);
};
}
public static int hash(HashFunction hashFunction, String key) {
return hashFunction.hashString(key, StandardCharsets.UTF_8).asInt();
}
public static int hash(HashFunction hashFunction, UUID key) {
return hashFunction.hashString(key.toString(), StandardCharsets.UTF_8).asInt();
}
}

View File

@@ -0,0 +1,19 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class JCPPPair<S, T> {
private S first;
private T second;
public static <S, T> JCPPPair<S, T> of(S first, T second) {
return new JCPPPair<>(first, second);
}
}

View File

@@ -0,0 +1,95 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util;
import lombok.extern.slf4j.Slf4j;
import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;
@Slf4j
public class SystemUtil {
private static final HardwareAbstractionLayer HARDWARE;
static {
HARDWARE = new SystemInfo().getHardware();
}
public static Optional<Integer> getMemoryUsage() {
try {
GlobalMemory memory = HARDWARE.getMemory();
long total = memory.getTotal();
long available = memory.getAvailable();
return Optional.of(toPercent(total - available, total));
} catch (Throwable e) {
log.debug("Failed to get memory usage!!!", e);
}
return Optional.empty();
}
public static Optional<Long> getTotalMemory() {
try {
return Optional.of(HARDWARE.getMemory().getTotal());
} catch (Throwable e) {
log.debug("Failed to get total memory!!!", e);
}
return Optional.empty();
}
public static Optional<Integer> getCpuUsage() {
try {
return Optional.of((int) (HARDWARE.getProcessor().getSystemCpuLoad(1000) * 100.0));
} catch (Throwable e) {
log.debug("Failed to get cpu usage!!!", e);
}
return Optional.empty();
}
public static Optional<Integer> getCpuCount() {
try {
return Optional.of(HARDWARE.getProcessor().getLogicalProcessorCount());
} catch (Throwable e) {
log.debug("Failed to get total cpu count!!!", e);
}
return Optional.empty();
}
public static Optional<Integer> getDiscSpaceUsage() {
try {
FileStore store = Files.getFileStore(Paths.get("/"));
long total = store.getTotalSpace();
long available = store.getUsableSpace();
return Optional.of(toPercent(total - available, total));
} catch (Throwable e) {
log.debug("Failed to get free disc space!!!", e);
}
return Optional.empty();
}
public static Optional<Long> getTotalDiscSpace() {
try {
FileStore store = Files.getFileStore(Paths.get("/"));
return Optional.of(store.getTotalSpace());
} catch (Throwable e) {
log.debug("Failed to get total disc space!!!", e);
}
return Optional.empty();
}
private static int toPercent(long used, long total) {
BigDecimal u = new BigDecimal(used);
BigDecimal t = new BigDecimal(total);
BigDecimal i = new BigDecimal(100);
return u.multiply(i).divide(t, RoundingMode.HALF_UP).intValue();
}
}

View File

@@ -0,0 +1,28 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.annotation;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@EventListener(ApplicationReadyEvent.class)
@Order
public @interface AfterStartUp {
int DISCOVERY_SERVICE = 1;
int REGULAR_SERVICE = 2;
@AliasFor(annotation = Order.class, attribute = "value")
int order();
}

View File

@@ -0,0 +1,25 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.annotation;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author baigod
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='app'")
@Component
public @interface AppComponent {
}

View File

@@ -0,0 +1,49 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.annotation;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.stereotype.Component;
import sanbing.jcpp.infrastructure.util.annotation.ProtocolComponent.ProtocolCondition;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author baigod
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Conditional(ProtocolCondition.class)
@Component
public @interface ProtocolComponent {
@AliasFor(annotation = Component.class)
String value() default "";
class ProtocolCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (!metadata.isAnnotated(ProtocolComponent.class.getName())) {
return true;
}
String serviceType = context.getEnvironment().getProperty("service.type", "null");
String protocolName = (String) metadata.getAnnotationAttributes(ProtocolComponent.class.getName()).get("value");
String enabled = context.getEnvironment().getProperty("service.protocols." + protocolName + ".enabled", "false");
return ("monolith".equals(serviceType) || "protocol".equals(serviceType)) && "true".equals(enabled);
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.async;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class JCPPAsynchron {
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
Consumer<Throwable> onFailure) {
withCallback(future, onSuccess, onFailure, null);
}
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess,
Consumer<Throwable> onFailure, Executor executor) {
FutureCallback<T> callback = new FutureCallback<>() {
@Override
public void onSuccess(T result) {
try {
onSuccess.accept(result);
} catch (Throwable th) {
onFailure(th);
}
}
@Override
public void onFailure(Throwable t) {
onFailure.accept(t);
}
};
Futures.addCallback(future, callback, Objects.requireNonNullElseGet(executor, MoreExecutors::directExecutor));
}
public static <T> ListenableFuture<T> submit(Callable<T> task, Consumer<T> onSuccess, Consumer<Throwable> onFailure, Executor executor) {
return submit(task, onSuccess, onFailure, executor, null);
}
public static <T> ListenableFuture<T> submit(Callable<T> task, Consumer<T> onSuccess, Consumer<Throwable> onFailure, Executor executor, Executor callbackExecutor) {
ListenableFuture<T> future = Futures.submit(task, executor);
withCallback(future, onSuccess, onFailure, callbackExecutor);
return future;
}
}

View File

@@ -0,0 +1,27 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.async;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
public class JCPPExecutors {
public static ExecutorService newWorkStealingPool(int parallelism, String namePrefix) {
return new ForkJoinPool(parallelism,
new JCPPForkJoinWorkerThreadFactory(namePrefix),
null, true);
}
public static ExecutorService newWorkStealingPool(int parallelism, Class<?> clazz) {
return newWorkStealingPool(parallelism, clazz.getSimpleName());
}
public static ExecutorService newVirtualThreadPool(String namePrefix) {
return Executors.newThreadPerTaskExecutor(new JCPPVirtualThreadFactory(namePrefix));
}
}

View File

@@ -0,0 +1,30 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.async;
import lombok.NonNull;
import lombok.ToString;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.atomic.AtomicLong;
@ToString
public class JCPPForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
private final String namePrefix;
private final AtomicLong threadNumber = new AtomicLong(1);
public JCPPForkJoinWorkerThreadFactory(@NonNull String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.setName(namePrefix + "-" + thread.getPoolIndex() + "-" + threadNumber.getAndIncrement());
return thread;
}
}

View File

@@ -0,0 +1,46 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.async;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ThreadFactory;
public class JCPPThreadFactory {
public static final String THREAD_TOPIC_SEPARATOR = " | ";
public static ThreadFactory forName(String name) {
return new ThreadFactoryBuilder()
.setNameFormat(name)
.setDaemon(true)
.setPriority(Thread.NORM_PRIORITY)
.build();
}
public static ThreadFactory forName(String name, int priority) {
return new ThreadFactoryBuilder()
.setNameFormat(name)
.setDaemon(true)
.setPriority(priority)
.build();
}
public static void updateCurrentThreadName(String threadSuffix) {
String name = Thread.currentThread().getName();
int spliteratorIndex = name.indexOf(THREAD_TOPIC_SEPARATOR);
if (spliteratorIndex > 0) {
name = name.substring(0, spliteratorIndex);
}
name = name + THREAD_TOPIC_SEPARATOR + threadSuffix;
Thread.currentThread().setName(name);
}
public static void addThreadNamePrefix(String prefix) {
String name = Thread.currentThread().getName();
name = prefix + "-" + name;
Thread.currentThread().setName(name);
}
}

View File

@@ -0,0 +1,24 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.async;
import sanbing.jcpp.infrastructure.util.trace.TracerRunnable;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
public class JCPPVirtualThreadFactory implements ThreadFactory {
private final String namePrefix;
private final AtomicLong threadNumber = new AtomicLong(1);
public JCPPVirtualThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
return Thread.ofVirtual().name(namePrefix + "-" + threadNumber.getAndIncrement()).unstarted(new TracerRunnable(r));
}
}

View File

@@ -0,0 +1,172 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.codec;
public class BCDUtil {
private static final String HEX = "0123456789ABCDEF";
/**
* 十进制 转 BCD字节数组
*
* @param num long 8字节
* @return byte[]
*/
public static byte[] longToBcdBytes(long num) {
int digits = 0;
long temp = num;
while (temp != 0) {
digits++;
temp /= 10;
}
int byteLen = digits % 2 == 0 ? digits / 2 : (digits + 1) / 2;
byte[] bcd = new byte[byteLen];
for (int i = 0; i < digits; i++) {
byte tmp = (byte) (num % 10);
if (i % 2 == 0) {
bcd[i / 2] = tmp;
} else {
bcd[i / 2] |= (byte) (tmp << 4);
}
num /= 10;
}
for (int i = 0; i < byteLen / 2; i++) {
byte tmp = bcd[i];
bcd[i] = bcd[byteLen - i - 1];
bcd[byteLen - i - 1] = tmp;
}
return bcd;
}
/**
* BCD字节数组 转 十进制
*
* @param bcd byte[]
* @return long
*/
public static long bcdBytesToLong(byte[] bcd) {
return Long.parseLong(BCDUtil.toString(bcd));
}
/**
* bcd字节数组 转 数字字符串
*
* @param bcd byte[]
* @return String
*/
public static String toString(byte[] bcd) {
StringBuilder sb = new StringBuilder();
for (byte b : bcd) {
sb.append(toString(b));
}
return sb.toString();
}
/**
* 单个字节BCD 转 数字字符串
*
* @param bcd byte
* @return String
*/
public static String toString(byte bcd) {
StringBuilder sb = new StringBuilder();
byte high = (byte) (bcd & 0xf0);
high >>>= (byte) 4;
high = (byte) (high & 0x0f);
byte low = (byte) (bcd & 0x0f);
sb.append(high);
sb.append(low);
return sb.toString();
}
/**
* 数字字符串 转 BCD字节数组
*
* @param str 数字字符串
* @return BCD字节数组
*/
public static byte[] numStrToBcdBytes(String str) {
//若为奇数补0为偶
if ((str.length() & 0x1) == 1) {
str = "0" + str;
}
byte[] ret = new byte[str.length() / 2];
byte[] bs = str.getBytes();
for (int i = 0; i < ret.length; i++) {
byte high = ascII2Bcd(bs[2 * i]);
byte low = ascII2Bcd(bs[2 * i + 1]);
ret[i] = (byte) ((high << 4) | low);
}
return ret;
}
public static byte ascII2Bcd(byte asc) {
if ((asc >= '0') && (asc <= '9'))
return (byte) (asc - '0');
else if ((asc >= 'A') && (asc <= 'F'))
return (byte) (asc - 'A' + 10);
else if ((asc >= 'a') && (asc <= 'f'))
return (byte) (asc - 'a' + 10);
else
return (byte) (asc - 48);
}
/**
* BCD 转 数字
*
* @param bcd byte
* @return int
*/
public static int bcdByteToInt(byte bcd) {
return ((bcd & 0xF0) >>> 4) * 10 + (bcd & 0x0F);
}
/**
* char to byte
*
* @param c char
* @return byte
*/
private static byte charToByte(char c) {
return (byte) HEX.indexOf(c);
}
/**
* Hex 转 BCD字节数组
*
* @param hex String
* @return BCD字节数组
*/
public static byte[] toBytes(String hex) {
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] cr = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (charToByte(cr[pos]) << 4 | charToByte(cr[pos + 1]));
}
return result;
}
/**
* BCD字节数组 转 Hex
*
* @param bcd BCD字节数组
* @return Hex
*/
public static String bcdBytesToHex(byte[] bcd) {
StringBuilder sb = new StringBuilder();
for (byte b : bcd) {
int highNibble = (b >> 4) & 0x0F;
int lowNibble = b & 0x0F;
sb.append(Integer.toHexString(highNibble));
sb.append(Integer.toHexString(lowNibble));
}
return sb.toString().toUpperCase();
}
}

View File

@@ -0,0 +1,81 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.codec;
import cn.hutool.core.io.checksum.crc16.CRC16Modbus;
import io.netty.buffer.ByteBuf;
import sanbing.jcpp.infrastructure.util.JCPPPair;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* @author baigod
*/
public class ByteUtil {
public static byte[] uuidToBytes(UUID uuid) {
ByteBuffer buf = ByteBuffer.allocate(16);
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
return buf.array();
}
public static UUID bytesToUuid(byte[] bytes) {
ByteBuffer bb = ByteBuffer.wrap(bytes);
long firstLong = bb.getLong();
long secondLong = bb.getLong();
return new UUID(firstLong, secondLong);
}
public static byte[] stringToBytes(String string) {
return string.getBytes(StandardCharsets.UTF_8);
}
public static String bytesToString(byte[] data) {
return new String(data, StandardCharsets.UTF_8);
}
public static byte[] longToBytes(long x) {
ByteBuffer longBuffer = ByteBuffer.allocate(Long.BYTES);
longBuffer.putLong(0, x);
return longBuffer.array();
}
public static long bytesToLong(byte[] bytes) {
return ByteBuffer.wrap(bytes).getLong();
}
/**
* 计算校验和
*/
public static int crcSum(byte[] data) {
CRC16Modbus crc16Modbus = new CRC16Modbus();
crc16Modbus.update(data);
return (int) crc16Modbus.getValue();
}
/**
* 验证校验和
*/
public static JCPPPair<Boolean, Integer> checkCrcSum(byte[] data, int checkSum) {
int expectedCs = crcSum(data);
return JCPPPair.of(expectedCs == checkSum, expectedCs);
}
/**
* ByteBuf转byte数组
*
* @param byteBuf
* @return
*/
public static byte[] toBytes(ByteBuf byteBuf) {
int msgLength = byteBuf.readableBytes();
byte[] bytes = new byte[msgLength];
byteBuf.readBytes(bytes);
return bytes;
}
}

View File

@@ -0,0 +1,65 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.codec;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class CP56Time2aUtil {
/**
* 解码 CP56Time2a 字节数组为 Instant 对象
*
* @param bytes 字节数组
* @return Instant 对象
*/
public static Instant decode(byte[] bytes) {
// 将字节数组解释为各个时间部分
int milliseconds = ((bytes[0] & 0xFF) + ((bytes[1] & 0xFF) << 8)); // 处理字节的无符号值
int minutes = bytes[2] & 0x3F;
int hours = bytes[3] & 0x1F;
int days = bytes[4] & 0x1F;
int months = bytes[5] & 0x0F;
int years = bytes[6] & 0x7F;
// 将 CP56Time2a 转换为 LocalDateTime
LocalDateTime dateTime = LocalDateTime.of(
years + 2000,
months,
days,
hours,
minutes,
milliseconds / 1000 // 秒数
);
// 返回对应的 Instant 对象
return dateTime.atZone(ZoneId.systemDefault()).toInstant();
}
/**
* 编码 Instant 对象为 CP56Time2a 字节数组
*
* @param instant Instant 对象
* @return 字节数组
*/
public static byte[] encode(Instant instant) {
// 将 Instant 转换到 LocalDateTime
LocalDateTime aTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
byte[] result = new byte[7];
int milliseconds = aTime.getSecond() * 1000; // 获取毫秒部分
// 填充字节数组
result[0] = (byte) (milliseconds % 256);
result[1] = (byte) (milliseconds / 256);
result[2] = (byte) aTime.getMinute();
result[3] = (byte) aTime.getHour();
result[4] = (byte) aTime.getDayOfMonth();
result[5] = (byte) aTime.getMonthValue(); // 1-12
result[6] = (byte) (aTime.getYear() % 100); // 00-99
return result;
}
}

View File

@@ -0,0 +1,97 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.config;
import com.google.common.collect.Iterators;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.metadata.ConstraintDescriptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.internal.engine.ConfigurationImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import sanbing.jcpp.infrastructure.util.exception.DataValidationException;
import sanbing.jcpp.infrastructure.util.validation.Length;
import sanbing.jcpp.infrastructure.util.validation.StringLengthValidator;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Configuration
public class ConstraintValidator {
private static Validator fieldsValidator;
static {
initializeValidators();
}
public static void validateFields(Object data) {
validateFields(data, "Validation error: ");
}
public static void validateFields(Object data, String errorPrefix) {
Set<ConstraintViolation<Object>> constraintsViolations = fieldsValidator.validate(data);
if (!constraintsViolations.isEmpty()) {
throw new DataValidationException(errorPrefix + getErrorMessage(constraintsViolations));
}
}
public static String getErrorMessage(Collection<ConstraintViolation<Object>> constraintsViolations) {
return constraintsViolations.stream()
.map(ConstraintValidator::getErrorMessage)
.distinct().sorted().collect(Collectors.joining(", "));
}
public static String getErrorMessage(ConstraintViolation<Object> constraintViolation) {
ConstraintDescriptor<?> constraintDescriptor = constraintViolation.getConstraintDescriptor();
String property = (String) constraintDescriptor.getAttributes().get("fieldName");
if (StringUtils.isEmpty(property) && !(constraintDescriptor.getAnnotation() instanceof AssertTrue)) {
property = Iterators.getLast(constraintViolation.getPropertyPath().iterator()).toString();
}
String error = "";
if (StringUtils.isNotEmpty(property)) {
error += property + " ";
}
error += constraintViolation.getMessage();
return error;
}
private static void initializeValidators() {
HibernateValidatorConfiguration validatorConfiguration = Validation.byProvider(HibernateValidator.class).configure();
ConstraintMapping constraintMapping = getCustomConstraintMapping();
validatorConfiguration.addMapping(constraintMapping);
fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator();
}
@Bean
public LocalValidatorFactoryBean validatorFactoryBean() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.setConfigurationInitializer(configuration -> {
((ConfigurationImpl) configuration).addMapping(getCustomConstraintMapping());
});
return localValidatorFactoryBean;
}
private static ConstraintMapping getCustomConstraintMapping() {
ConstraintMapping constraintMapping = new DefaultConstraintMapping(null);
constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class);
return constraintMapping;
}
}

View File

@@ -0,0 +1,82 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.config;
import com.google.common.hash.HashFunction;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory;
import sanbing.jcpp.infrastructure.util.trace.TracerRunnable;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import static sanbing.jcpp.infrastructure.util.JCPPHashUtil.forName;
import static sanbing.jcpp.infrastructure.util.JCPPHashUtil.hash;
/**
* @author baigod
*/
@Component
@Slf4j
public class ShardingThreadPool {
@Value("${thread-pool.sharding.hash_function_name:murmur3_128}")
private String hashFunctionName;
@Value("${thread-pool.sharding.parallelism:128}")
private int parallelism;
private HashFunction hashFunction;
private final Map<Integer, ExecutorService> EXECUTOR_SERVICE_MAP = new ConcurrentHashMap<>(128);
@PostConstruct
public void init() {
this.hashFunction = forName(hashFunctionName);
}
@PreDestroy
public void destroy() {
for (ExecutorService executorService : EXECUTOR_SERVICE_MAP.values()) {
executorService.shutdownNow();
log.info("Sharding Thread [{}] Shutdown completed.", executorService);
}
}
@Scheduled(fixedDelayString = "${thread-pool.sharding.stats-print-interval-ms:10000}")
public void printStats() {
EXECUTOR_SERVICE_MAP.forEach((k, v) -> {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) v;
log.info("分区 {}/{} 的线程池中剩余 {} 条待执行任务,当前正在执行的线程数 {}, 已完成任务 {} / {}",
k,
EXECUTOR_SERVICE_MAP.size(),
threadPoolExecutor.getQueue().size(),
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getTaskCount());
});
}
/**
* 提交分片任务
*/
public void execute(UUID hashKey, TracerRunnable runnable) {
int partition = hash(hashFunction, hashKey);
EXECUTOR_SERVICE_MAP.computeIfAbsent(partition % parallelism,
p -> Executors.newFixedThreadPool(1, JCPPThreadFactory.forName("sharding-threads-" + p)))
.execute(runnable);
}
}

View File

@@ -0,0 +1,43 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.config;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Configuration;
import sanbing.jcpp.infrastructure.util.async.JCPPExecutors;
import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author baigod
*/
@Configuration
public class ThreadPoolConfiguration {
public static final ExecutorService JCPP_COMMON_THREAD_POOL = JCPPExecutors.newVirtualThreadPool("jcpp-common-virtual");
public static final ScheduledExecutorService PROTOCOL_SESSION_SCHEDULED = Executors.newSingleThreadScheduledExecutor(JCPPThreadFactory.forName("protocol-session-schedule"));
@PreDestroy
public void destroy() {
PROTOCOL_SESSION_SCHEDULED.shutdownNow();
JCPP_COMMON_THREAD_POOL.shutdown();
try {
if (!JCPP_COMMON_THREAD_POOL.awaitTermination(5, TimeUnit.SECONDS)) {
JCPP_COMMON_THREAD_POOL.shutdownNow();
}
} catch (InterruptedException e) {
JCPP_COMMON_THREAD_POOL.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,16 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.exception;
public class DataValidationException extends RuntimeException {
public DataValidationException(String message) {
super(message);
}
public DataValidationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,19 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.exception;
/**
* @author baigod
*/
public class DownlinkException extends RuntimeException {
public DownlinkException(String message) {
super(message);
}
public DownlinkException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,17 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.exception;
public class IncorrectParameterException extends RuntimeException {
public IncorrectParameterException(String message) {
super(message);
}
public IncorrectParameterException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,35 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import java.io.IOException;
@JacksonStdImpl
public class BigNumberSerializer extends NumberSerializer {
private static final long JS_NUM_MAX = 9007199254740992L;
private static final long JS_NUM_MIN = -9007199254740992L;
public static final BigNumberSerializer instance = new BigNumberSerializer(Number.class);
public BigNumberSerializer(Class<? extends Number> rawType) {
super(rawType);
}
@Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
long longValue = value.longValue();
if (longValue >= JS_NUM_MIN && longValue <= JS_NUM_MAX) {
super.serialize(value, gen, provider);
} else {
gen.writeString(value.toString());
}
}
}

View File

@@ -0,0 +1,56 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
/**
* 类型转换
*
* @author baigod
*/
public class DataTypeModule extends SimpleModule {
public static final DataTypeModule INSTANCE = new DataTypeModule();
private DataTypeModule() {
super(DataTypeModule.class.getName());
// number
this.addSerializer(Long.class, BigNumberSerializer.instance);
this.addSerializer(Long.TYPE, BigNumberSerializer.instance);
this.addSerializer(BigInteger.class, BigNumberSerializer.instance);
this.addSerializer(BigDecimal.class, BigNumberSerializer.instance);
// time
this.addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE);
this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
this.addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE);
this.addSerializer(Instant.class, InstantSerializer.INSTANCE);
this.addSerializer(Date.class, DateSerializer.INSTANCE);
this.addSerializer(java.sql.Date.class, SqlDateSerializer.INSTANCE);
this.addSerializer(Timestamp.class, TimestampSerializer.INSTANCE);
this.addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE);
this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
this.addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
this.addDeserializer(Instant.class, InstantDeserializer.INSTANCE);
this.addDeserializer(Date.class, DateDeserializer.INSTANCE);
this.addDeserializer(java.sql.Date.class, SqlDateDeserializer.INSTANCE);
this.addDeserializer(Timestamp.class, TimestampDeserializer.INSTANCE);
}
}

View File

@@ -0,0 +1,38 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Date;
/**
* 时间反序列化
*
* @author baigod
*/
public class DateDeserializer extends JsonDeserializer<Date> {
public static final DateDeserializer INSTANCE = new DateDeserializer();
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private DateDeserializer() {
}
@Override
public Date deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
String dateString = p.getText();
return dateString.length() > 19
? Date.from(LocalDateTime.parse(dateString, DATE_TIME_FORMATTER_MS).atZone(ZoneOffset.systemDefault()).toInstant())
: Date.from(LocalDateTime.parse(dateString, DATE_TIME_FORMATTER).atZone(ZoneOffset.systemDefault()).toInstant());
}
}

View File

@@ -0,0 +1,32 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.apache.commons.lang3.time.FastDateFormat;
import java.io.IOException;
import java.util.Date;
/**
* 时间序列化
*
* @author baigod
*/
public class DateSerializer extends StdSerializer<Date> {
public static final DateSerializer INSTANCE = new DateSerializer();
private static final FastDateFormat FAST_DATE_FORMAT_MS = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS");
private DateSerializer() {
super(Date.class);
}
@Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(FAST_DATE_FORMAT_MS.format(value));
}
}

View File

@@ -0,0 +1,41 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import lombok.SneakyThrows;
import org.apache.commons.lang3.time.FastDateFormat;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
/**
* Instant 反序列化
*
* @author baigod
*/
public class InstantDeserializer extends com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer<Instant> {
public static final InstantDeserializer INSTANCE = new InstantDeserializer();
private final FastDateFormat FAST_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
private final FastDateFormat FAST_DATE_FORMAT_MS = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS");
private InstantDeserializer() {
super(InstantDeserializer.INSTANT, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
@SneakyThrows
@Override
public Instant deserialize(JsonParser parser, DeserializationContext context) {
String timestamp = parser.getText();
return timestamp.length() > 19
? FAST_DATE_FORMAT_MS.parse(timestamp).toInstant()
: FAST_DATE_FORMAT.parse(timestamp).toInstant();
}
}

View File

@@ -0,0 +1,21 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import java.time.format.DateTimeFormatter;
/**
* Instant 序列化
*
* @author baigod
*/
public class InstantSerializer extends com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer {
public static final InstantSerializer INSTANCE = new InstantSerializer();
private InstantSerializer() {
super(com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer.INSTANCE, true,false, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
}
}

View File

@@ -0,0 +1,215 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.util.Arrays;
import java.util.TimeZone;
/**
* @author baigod
*/
public class JacksonUtil {
public static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder()
.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
.configure(Feature.ALLOW_SINGLE_QUOTES, true)
.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
.configure(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS, false)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.defaultTimeZone(TimeZone.getTimeZone("GMT+8"))
.build()
.registerModules(DataTypeModule.INSTANCE);
public static final ObjectMapper PRETTY_SORTED_JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
.serializationInclusion(Include.NON_NULL)
.defaultTimeZone(TimeZone.getTimeZone("GMT+8"))
.build()
.registerModules(DataTypeModule.INSTANCE);
public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
try {
return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueType) : null;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The given object value: "
+ fromValue + " cannot be converted to " + toValueType, e);
}
}
public static <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeRef) {
try {
return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueTypeRef) : null;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The given object value: "
+ fromValue + " cannot be converted to " + toValueTypeRef, e);
}
}
public static <T> T fromString(String string, Class<T> clazz) {
try {
return string != null ? OBJECT_MAPPER.readValue(string, clazz) : null;
} catch (IOException e) {
throw new IllegalArgumentException("The given string value: "
+ string + " cannot be transformed to Json object", e);
}
}
public static <T> T fromString(String string, TypeReference<T> valueTypeRef) {
try {
return string != null ? OBJECT_MAPPER.readValue(string, valueTypeRef) : null;
} catch (IOException e) {
throw new IllegalArgumentException("The given string value: "
+ string + " cannot be transformed to Json object", e);
}
}
public static <T> T fromBytes(byte[] bytes, Class<T> clazz) {
try {
return bytes != null ? OBJECT_MAPPER.readValue(bytes, clazz) : null;
} catch (IOException e) {
throw new IllegalArgumentException("The given string value: "
+ Arrays.toString(bytes) + " cannot be transformed to Json object", e);
}
}
public static JsonNode fromBytes(byte[] bytes) {
try {
return OBJECT_MAPPER.readTree(bytes);
} catch (IOException e) {
throw new IllegalArgumentException("The given byte[] value: "
+ Arrays.toString(bytes) + " cannot be transformed to Json object", e);
}
}
public static String toString(Object value) {
try {
return value != null ? OBJECT_MAPPER.writeValueAsString(value) : null;
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("The given Json object value: "
+ value + " cannot be transformed to a String", e);
}
}
public static String toPrettyString(Object o) {
try {
return PRETTY_SORTED_JSON_MAPPER.writeValueAsString(o);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T treeToValue(JsonNode node, Class<T> clazz) {
try {
return OBJECT_MAPPER.treeToValue(node, clazz);
} catch (IOException e) {
throw new IllegalArgumentException("Can't convert value: " + node.toString(), e);
}
}
public static JsonNode toJsonNode(String value) {
return toJsonNode(value, OBJECT_MAPPER);
}
public static JsonNode toJsonNode(String value, ObjectMapper mapper) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return mapper.readTree(value);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
public static ObjectNode newObjectNode() {
return newObjectNode(OBJECT_MAPPER);
}
public static ObjectNode newObjectNode(ObjectMapper mapper) {
return mapper.createObjectNode();
}
public static ArrayNode newArrayNode() {
return newArrayNode(OBJECT_MAPPER);
}
public static ArrayNode newArrayNode(ObjectMapper mapper) {
return mapper.createArrayNode();
}
public static <T> T clone(T value) {
@SuppressWarnings("unchecked")
Class<T> valueClass = (Class<T>) value.getClass();
return fromString(toString(value), valueClass);
}
public static <T> JsonNode valueToTree(T value) {
return OBJECT_MAPPER.valueToTree(value);
}
public static <T> byte[] writeValueAsBytes(T value) {
try {
return OBJECT_MAPPER.writeValueAsBytes(value);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("The given Json object value: "
+ value + " cannot be transformed to a String", e);
}
}
public static JsonNode getSafely(JsonNode node, String... path) {
if (node == null) {
return null;
}
for (String p : path) {
if (!node.has(p)) {
return null;
} else {
node = node.get(p);
}
}
return node;
}
/**
* 合并两个ObjectNode.
* 如果存在相同的字段优先保留第二个ObjectNode中的值。
*
* @param node1 the first ObjectNode
* @param node2 the second ObjectNode
* @return 合并后的结果
*/
public static ObjectNode merge(ObjectNode node1, ObjectNode node2) {
ObjectNode mergedNode = OBJECT_MAPPER.createObjectNode();
// 把第一个节点的所有字段添加到mergedNode中
node1.fields().forEachRemaining(entry -> {
mergedNode.set(entry.getKey(), entry.getValue());
});
// 把第二个节点的所有字段添加到mergedNode中覆盖相同字段
node2.fields().forEachRemaining(entry -> {
mergedNode.set(entry.getKey(), entry.getValue());
});
return mergedNode;
}
}

View File

@@ -0,0 +1,39 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* LocalDateTime类型反序列化
* 需要用到的字段上加 @JsonDeserialize(using = LocalDateTimeDeserializer.class)
*/
public class LocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private LocalDateTimeDeserializer() {
super(LocalDateTime.class);
}
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException {
String dateString = jsonParser.getText();
return dateString.length() > 19
? LocalDateTime.parse(dateString, DATE_TIME_FORMATTER_MS)
: LocalDateTime.parse(dateString, DATE_TIME_FORMATTER);
}
}

View File

@@ -0,0 +1,32 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 时间类型序列化工具
*
* @author baigod
*/
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
public static final LocalDateTimeSerializer INSTANCE = new LocalDateTimeSerializer();
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private LocalDateTimeSerializer() {
}
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DATE_TIME_FORMATTER_MS));
}
}

View File

@@ -0,0 +1,42 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* LocalDateTime类型反序列化
* 需要用到的字段上加 @JsonDeserialize(using = EnergyLocalTimeDeserializer.class)
*/
public class LocalTimeDeserializer extends StdDeserializer<LocalTime> {
public static final LocalTimeDeserializer INSTANCE = new LocalTimeDeserializer();
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private LocalTimeDeserializer() {
super(LocalDateTime.class);
}
@Override
public LocalTime deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException {
String dateString = jsonParser.getText();
return dateString.length() > 8
? LocalTime.parse(dateString, DATE_TIME_FORMATTER_MS)
: LocalTime.parse(dateString, DATE_TIME_FORMATTER);
}
}

View File

@@ -0,0 +1,32 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 时间类型序列化工具
*
* @author baigod
*/
public class LocalTimeSerializer extends JsonSerializer<LocalTime> {
public static final LocalTimeSerializer INSTANCE = new LocalTimeSerializer();
private LocalTimeSerializer() {
}
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
@Override
public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DATE_TIME_FORMATTER_MS));
}
}

View File

@@ -0,0 +1,33 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 13位时间戳反序列化器
* @author baigod
*/
public class LongTimestampDeserializer extends JsonDeserializer<Long> {
@Override
public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
// 判定是否是long类型
if ("LONG".equals(jsonParser.getNumberType().name())) {
return jsonParser.getLongValue();
}
LocalDateTime localDateTime = LocalDateTime.parse(jsonParser.getValueAsString().replace(" ", "T").replace("Z", ""));
ZoneId systemDefaultZone = ZoneId.systemDefault();
return localDateTime.atZone(systemDefaultZone).toInstant().toEpochMilli();
}
}

View File

@@ -0,0 +1,39 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
/**
* sqlDate 反序列化
*
* @author baigod
*/
public class SqlDateDeserializer extends JsonDeserializer<Date> {
public static final SqlDateDeserializer INSTANCE = new SqlDateDeserializer();
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private SqlDateDeserializer() {
}
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String dateString = p.getText();
return dateString.length() > 19
? new Date(LocalDateTime.parse(dateString, DATE_TIME_FORMATTER_MS).atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli())
: new Date(LocalDateTime.parse(dateString, DATE_TIME_FORMATTER).atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli());
}
}

View File

@@ -0,0 +1,33 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.apache.commons.lang3.time.FastDateFormat;
import java.io.IOException;
import java.sql.Date;
/**
* sqlDate序列化
*
* @author baigod
*/
public class SqlDateSerializer extends StdSerializer<Date> {
public static final SqlDateSerializer INSTANCE = new SqlDateSerializer();
private static final FastDateFormat FAST_DATE_FORMAT_MS = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS");
private SqlDateSerializer() {
super(Date.class);
}
@Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(FAST_DATE_FORMAT_MS.format(value));
}
}

View File

@@ -0,0 +1,38 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
/**
* timestamp 反序列化
* @author baigod
*/
public class TimestampDeserializer extends JsonDeserializer<Timestamp> {
public static final TimestampDeserializer INSTANCE = new TimestampDeserializer();
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_TIME_FORMATTER_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private TimestampDeserializer() {
}
@Override
public Timestamp deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
String dateString = p.getText();
return dateString.length() > 19
? Timestamp.from(LocalDateTime.parse(dateString, DATE_TIME_FORMATTER_MS).atZone(ZoneOffset.systemDefault()).toInstant())
: Timestamp.from(LocalDateTime.parse(dateString, DATE_TIME_FORMATTER).atZone(ZoneOffset.systemDefault()).toInstant());
}
}

View File

@@ -0,0 +1,33 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.apache.commons.lang3.time.FastDateFormat;
import java.io.IOException;
import java.sql.Timestamp;
/**
* timestamp 序列化
*
* @author baigod
*/
public class TimestampSerializer extends StdSerializer<Timestamp> {
public static final TimestampSerializer INSTANCE = new TimestampSerializer();
private static final FastDateFormat FAST_DATE_FORMAT_MS = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS");
private TimestampSerializer() {
super(Timestamp.class);
}
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(FAST_DATE_FORMAT_MS.format(value));
}
}

View File

@@ -0,0 +1,43 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.mdc;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import sanbing.jcpp.infrastructure.util.trace.Tracer;
import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil;
public class MDCUtils {
private static final String TRACE_ID = "TRACE_ID";
public static String putIfAbsentTracer() {
String traceId = MDC.get(TRACE_ID);
if (StringUtils.isEmpty(traceId)) {
return recordTracer();
}
return traceId;
}
public static String recordTracer() {
Tracer tracer = TracerContextUtil.getCurrentTracer();
if (!StringUtils.isEmpty(tracer.getTraceId())) {
MDC.put(TRACE_ID, tracer.getTraceId());
} else {
MDC.remove(TRACE_ID);
}
return tracer.getTraceId();
}
public static void cleanTracer() {
MDC.remove(TRACE_ID);
}
}

View File

@@ -0,0 +1,14 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.property;
import lombok.Data;
@Data
public class JCPPProperty {
private String key;
private String value;
}

View File

@@ -0,0 +1,44 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.property;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class PropertyUtils {
public static Map<String, String> getProps(String properties) {
Map<String, String> configs = new HashMap<>();
if (StringUtils.isNotEmpty(properties)) {
for (String property : properties.split(";")) {
if (StringUtils.isNotEmpty(property)) {
int delimiterPosition = property.indexOf(":");
String key = property.substring(0, delimiterPosition);
String value = property.substring(delimiterPosition + 1);
configs.put(key, value);
}
}
}
return configs;
}
public static Map<String, String> getProps(Map<String, String> defaultProperties, String propertiesStr) {
return getProps(defaultProperties, propertiesStr, PropertyUtils::getProps);
}
public static Map<String, String> getProps(Map<String, String> defaultProperties, String propertiesStr, Function<String, Map<String, String>> parser) {
Map<String, String> properties = defaultProperties;
if (StringUtils.isNotBlank(propertiesStr)) {
properties = new HashMap<>(properties);
properties.putAll(parser.apply(propertiesStr));
}
return properties;
}
}

View File

@@ -0,0 +1,69 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.trace;
import java.net.InetAddress;
import java.util.concurrent.atomic.AtomicInteger;
public class TraceIdGenerator {
//127.0.0.1
private static String IP_16 = "7F000001";
private static final int MAX_COUNT_INDEX = 9000;
private static final AtomicInteger COUNT = new AtomicInteger(1000);
static {
try {
String ipAddress = InetAddress.getLocalHost().getHostAddress();
if (ipAddress != null) {
IP_16 = getIP_16(ipAddress);
}
} catch (Throwable ignored) {
}
}
public static String generate() {
return getTraceId(IP_16, System.currentTimeMillis(), getNextId());
}
private static String getIP_16(String ip) {
String[] ips = ip.split("\\.");
StringBuilder sb = new StringBuilder();
for (String column : ips) {
String hex = Integer.toHexString(Integer.parseInt(column)).toUpperCase();
if (hex.length() == 1) {
sb.append('0').append(hex);
} else {
sb.append(hex);
}
}
return sb.toString();
}
private static String getTraceId(String ip, long timestamp, String nextId) {
return ip + timestamp + nextId;
}
private static String getNextId() {
int count = COUNT.incrementAndGet();
if (count > 9000) {
synchronized (TraceIdGenerator.class) {
if (COUNT.get() > MAX_COUNT_INDEX) {
COUNT.set(1000);
}
}
return String.valueOf(COUNT.incrementAndGet());
} else {
return String.valueOf(count);
}
}
}

View File

@@ -0,0 +1,38 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.trace;
import lombok.Data;
import java.io.Serializable;
@Data
public class Tracer implements Serializable {
private String traceId;
private String origin;
private final long tracerTs;
public Tracer(String traceId, String origin) {
this.traceId = traceId;
this.origin = origin;
this.tracerTs = System.currentTimeMillis();
}
public Tracer(String traceId, String origin, long tracerTs) {
this.traceId = traceId;
this.origin = origin;
this.tracerTs = tracerTs;
}
public Tracer(String traceId, long tracerTs) {
this.traceId = traceId;
this.origin = "JCPP";
this.tracerTs = tracerTs;
}
}

View File

@@ -0,0 +1,41 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.trace;
import sanbing.jcpp.infrastructure.util.mdc.MDCUtils;
import java.util.concurrent.Callable;
public class TracerCallable<T> implements Callable<T> {
private Tracer tracer;
private final Callable<T> callable;
public TracerCallable(Callable<T> callable) {
this.tracer = TracerContextUtil.getCurrentTracer();
this.callable = callable;
}
@Override
public T call() throws Exception {
try {
if (this.tracer != null) {
TracerContextUtil.newTracer(tracer.getTraceId(), tracer.getOrigin(), tracer.getTracerTs());
MDCUtils.recordTracer();
}
return this.callable.call();
} finally {
TracerContextUtil.cleanTracer();
MDCUtils.cleanTracer();
this.tracer = null;
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.trace;
import org.apache.commons.lang3.StringUtils;
/**
* Tracer上下文工具类
*/
public class TracerContextUtil {
public static final String JCPP_TRACER_ID = "jcpp_tracer_id";
public static final String JCPP_TRACER_ORIGIN = "jcpp_tracer_origin";
public static final String JCPP_TRACER_TS = "jcpp_tracer_ts";
private static final ThreadLocal<Tracer> TRACE_ID_CONTAINER = new ThreadLocal<>();
public static Tracer newTracer(String traceId, String origin) {
Tracer tracer;
if (StringUtils.isEmpty(traceId)) {
tracer = new Tracer(TraceIdGenerator.generate(), origin);
} else {
tracer = new Tracer(traceId, origin);
}
TRACE_ID_CONTAINER.set(tracer);
return tracer;
}
public static Tracer newTracer(String traceId, String origin, long ts) {
final Tracer tracer;
if (StringUtils.isEmpty(traceId)) {
tracer = new Tracer(TraceIdGenerator.generate(), origin, ts);
} else {
tracer = new Tracer(traceId, origin, ts);
}
TRACE_ID_CONTAINER.set(tracer);
return tracer;
}
public static Tracer newTracer(String origin) {
return newTracer(TraceIdGenerator.generate(), origin);
}
public static Tracer newTracer() {
return newTracer(TraceIdGenerator.generate(), null);
}
public static Tracer getCurrentTracer() {
Tracer tracer = TRACE_ID_CONTAINER.get();
if (tracer == null) {
return newTracer();
}
return tracer;
}
public static void cleanTracer() {
TRACE_ID_CONTAINER.remove();
}
}

View File

@@ -0,0 +1,38 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.trace;
import sanbing.jcpp.infrastructure.util.mdc.MDCUtils;
public class TracerRunnable implements Runnable {
private Tracer tracer;
private final Runnable runnable;
public TracerRunnable(Runnable runnable) {
this.tracer = TracerContextUtil.getCurrentTracer();
this.runnable = runnable;
}
@Override
public void run() {
try {
if (this.tracer != null) {
TracerContextUtil.newTracer(tracer.getTraceId(), tracer.getOrigin(), tracer.getTracerTs());
MDCUtils.recordTracer();
}
this.runnable.run();
} finally {
TracerContextUtil.cleanTracer();
MDCUtils.cleanTracer();
this.tracer = null;
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = {})
public @interface Length {
String message() default "length must be equal or less than {max}";
String fieldName() default "";
int max() default 255;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,35 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.validation;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@Slf4j
public class StringLengthValidator implements ConstraintValidator<Length, Object> {
private int max;
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
String stringValue;
if (value instanceof CharSequence || value instanceof JsonNode) {
stringValue = value.toString();
} else {
return true;
}
if (StringUtils.isEmpty(stringValue)) {
return true;
}
return stringValue.length() <= max;
}
@Override
public void initialize(Length constraintAnnotation) {
this.max = constraintAnnotation.max();
}
}

View File

@@ -0,0 +1,55 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.validation;
import sanbing.jcpp.infrastructure.util.exception.IncorrectParameterException;
import java.util.UUID;
import java.util.function.Function;
public class Validator {
public static void validateString(String val, String errorMessage) {
if (val == null || val.isEmpty()) {
throw new IncorrectParameterException(errorMessage);
}
}
public static void validateString(String val, Function<String, String> errorMessageFunction) {
if (val == null || val.isEmpty()) {
throw new IncorrectParameterException(errorMessageFunction.apply(val));
}
}
public static void validatePositiveNumber(long val, String errorMessage) {
if (val <= 0) {
throw new IncorrectParameterException(errorMessage);
}
}
@Deprecated
public static void validateId(UUID id, String errorMessage) {
if (id == null) {
throw new IncorrectParameterException(errorMessage);
}
}
public static void validateId(UUID id, Function<UUID, String> errorMessageFunction) {
if (id == null) {
throw new IncorrectParameterException(errorMessageFunction.apply(id));
}
}
public static void checkNotNull(Object reference, String errorMessage) {
if (reference == null) {
throw new IncorrectParameterException(errorMessage);
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.codec;
import cn.hutool.core.util.HexUtil;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
class BCDUtilTest {
@Test
void toBytesTest() {
String pileCodeHex = "20231212000010";
byte[] bytes = HexUtil.decodeHex(pileCodeHex);
String pileCode = BCDUtil.toString(bytes);
assert pileCodeHex.equals(pileCode);
byte[] pileCodeBytes = BCDUtil.toBytes(pileCodeHex);
assertArrayEquals(pileCodeBytes, bytes);
}
}

View File

@@ -0,0 +1,27 @@
/**
* 抖音关注:程序员三丙
* 知识星球https://t.zsxq.com/j9b21
*/
package sanbing.jcpp.infrastructure.util.codec;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.Arrays;
class CP56Time2aUtilTest {
@Test
void encodeTest() {
Instant time = Instant.ofEpochMilli(1727798453000L);
byte[] bytes = CP56Time2aUtil.encode(time);
System.out.println(Arrays.toString(bytes));
Instant decode = CP56Time2aUtil.decode(bytes);
assert time.equals(decode);
}
}