commit 2b3e56352caa5da98f58c2c5a6b4111a7bc37a97
Author: lzq
Date: Thu Sep 25 18:56:24 2025 +0800
1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1849138
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/**/logs
+/**/*.iml
+/**/.idea
+/**/target
+/**/.DS_Store
+/**/.back*
+db-model/*/
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..92e0323
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,94 @@
+
+ 4.0.0
+
+ com.njzscloud
+ gps
+ 0.0.1
+ jar
+
+
+ UTF-8
+
+
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.51
+
+
+
+
+ cn.hutool
+ hutool-cache
+ 5.8.28
+
+
+ cn.hutool
+ hutool-core
+ 5.8.28
+
+
+ cn.hutool
+ hutool-extra
+ 5.8.28
+
+
+ cn.hutool
+ hutool-captcha
+ 5.8.28
+
+
+ cn.hutool
+ hutool-crypto
+ 5.8.28
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.11
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.13.4
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.13.4
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+ 2.13.4
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.13.4
+
+
+
+
diff --git a/src/main/java/com/njzscloud/common/ex/CliException.java b/src/main/java/com/njzscloud/common/ex/CliException.java
new file mode 100644
index 0000000..50d5d49
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/CliException.java
@@ -0,0 +1,25 @@
+package com.njzscloud.common.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);
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/ex/ExceptionDepthComparator.java b/src/main/java/com/njzscloud/common/ex/ExceptionDepthComparator.java
new file mode 100644
index 0000000..2ef846a
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/ExceptionDepthComparator.java
@@ -0,0 +1,93 @@
+package com.njzscloud.common.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;
+
+/**
+ * 用于两个异常之间的排序
+ * 根据当前异常与目标异常在异常继承体系中的层级进行比较
+ *
+ * @see org.springframework.core.ExceptionDepthComparator
+ */
+public class ExceptionDepthComparator implements Comparator> {
+
+ /**
+ * 比较器缓存
+ */
+ private static final SimpleCache, 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> exceptionTypes, Throwable targetException) {
+ Assert.notEmpty(exceptionTypes, "不能为空");
+ Assert.notNull(targetException, "不能为空");
+ if (exceptionTypes.size() == 1) {
+ return exceptionTypes.iterator().next();
+ }
+ List> 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);
+ }
+
+}
diff --git a/src/main/java/com/njzscloud/common/ex/ExceptionMsg.java b/src/main/java/com/njzscloud/common/ex/ExceptionMsg.java
new file mode 100644
index 0000000..ba516a1
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/ExceptionMsg.java
@@ -0,0 +1,53 @@
+package com.njzscloud.common.ex;
+
+
+/**
+ * 异常信息
+ * 默认:
+ * 系统异常:SYS_EXP_MSG
+ * 客户端错误:CLI_ERR_MSG
+ * 系统错误:SYS_ERR_MSG
+ */
+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;
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/ex/ExceptionType.java b/src/main/java/com/njzscloud/common/ex/ExceptionType.java
new file mode 100644
index 0000000..f8c1da9
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/ExceptionType.java
@@ -0,0 +1,21 @@
+package com.njzscloud.common.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;
+
+}
diff --git a/src/main/java/com/njzscloud/common/ex/Exceptions.java b/src/main/java/com/njzscloud/common/ex/Exceptions.java
new file mode 100644
index 0000000..b90cf41
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/Exceptions.java
@@ -0,0 +1,77 @@
+package com.njzscloud.common.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);
+ }
+
+ /**
+ * 创建异常对象
+ *
+ * @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;
+ }
+
+}
diff --git a/src/main/java/com/njzscloud/common/ex/ExpectData.java b/src/main/java/com/njzscloud/common/ex/ExpectData.java
new file mode 100644
index 0000000..b1e8d4d
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/ExpectData.java
@@ -0,0 +1,60 @@
+package com.njzscloud.common.ex;
+
+import lombok.AllArgsConstructor;
+
+import java.util.Collections;
+
+/**
+ * 期望值
+ * HTTP 响应时 响应体的 JSON 内容
+ */
+@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 getData() {
+ return (T) data;
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/ex/SysError.java b/src/main/java/com/njzscloud/common/ex/SysError.java
new file mode 100644
index 0000000..17c541e
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/SysError.java
@@ -0,0 +1,26 @@
+package com.njzscloud.common.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);
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/ex/SysException.java b/src/main/java/com/njzscloud/common/ex/SysException.java
new file mode 100644
index 0000000..13f45b2
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/SysException.java
@@ -0,0 +1,25 @@
+package com.njzscloud.common.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);
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/ex/SysThrowable.java b/src/main/java/com/njzscloud/common/ex/SysThrowable.java
new file mode 100644
index 0000000..8744760
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ex/SysThrowable.java
@@ -0,0 +1,52 @@
+package com.njzscloud.common.ex;
+
+import cn.hutool.core.map.MapUtil;
+import com.njzscloud.common.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());
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/fastjson/Fastjson.java b/src/main/java/com/njzscloud/common/fastjson/Fastjson.java
new file mode 100644
index 0000000..a6fe987
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/fastjson/Fastjson.java
@@ -0,0 +1,139 @@
+package com.njzscloud.common.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;
+
+/**
+ * Fastjson 工具
+ * 已开启类型序列化/反序列化支持
+ */
+@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 toBean(String json, Type type) {
+ if (json == null || json.isEmpty()) {
+ return null;
+ }
+ return toBean(JSONReader.of(json, JSON_READER_CONTEXT), type);
+ }
+
+ public static 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 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 toBean(JSONReader reader, Type type) {
+ ObjectReader 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();
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/fastjson/serializer/DictObjectDeserializer.java b/src/main/java/com/njzscloud/common/fastjson/serializer/DictObjectDeserializer.java
new file mode 100644
index 0000000..9069cdf
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/fastjson/serializer/DictObjectDeserializer.java
@@ -0,0 +1,107 @@
+package com.njzscloud.common.fastjson.serializer;
+
+
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.reader.ObjectReader;
+import com.alibaba.fastjson2.util.TypeUtils;
+import com.njzscloud.common.ex.Exceptions;
+import com.njzscloud.common.ienum.Dict;
+import com.njzscloud.common.ienum.DictInt;
+import com.njzscloud.common.ienum.DictStr;
+import com.njzscloud.common.ienum.IEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * Dict 枚举的 Fastjson 反序列化器
+ * 使用方式(二选一即可):
+ * 1、在字段上使用 JSONField 进行指定
+ * 如,@JSONField(deserializeUsing = DictObjectSerializer.class)
+ * 2、在枚举类上使用 JSONType 进行指定(在接口上指定无效)
+ * 如,@JSONType(writeEnumAsJavaBean = true, deserializer = DictObjectSerializer.class)
+ * JSON 格式见对应的序列化器 {@link DictObjectSerializer}
+ *
+ * @see Dict
+ * @see DictInt
+ * @see DictStr
+ * @see DictObjectSerializer
+ */
+@Slf4j
+public class DictObjectDeserializer implements ObjectReader {
+
+ 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 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);
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/fastjson/serializer/DictObjectSerializer.java b/src/main/java/com/njzscloud/common/fastjson/serializer/DictObjectSerializer.java
new file mode 100644
index 0000000..1d7f668
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/fastjson/serializer/DictObjectSerializer.java
@@ -0,0 +1,97 @@
+package com.njzscloud.common.fastjson.serializer;
+
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.util.TypeUtils;
+import com.alibaba.fastjson2.writer.ObjectWriter;
+import com.njzscloud.common.ienum.Dict;
+import com.njzscloud.common.ienum.DictInt;
+import com.njzscloud.common.ienum.DictStr;
+import com.njzscloud.common.ienum.IEnum;
+
+import java.lang.reflect.Type;
+
+/**
+ * Dict 枚举的 Fastjson 序列化器
+ * 使用方式(二选一即可):
+ * 1、在字段上使用 JSONField 进行指定
+ * 如,@JSONField(serializeUsing = DictObjectSerializer.class)
+ * 2、在枚举类上使用 JSONType 进行指定(在接口上指定无效)
+ * 如,@JSONType(writeEnumAsJavaBean = true, serializer = DictObjectSerializer.class)
+ * JSON 格式
+ * 1、枚举不是其他对象的属性
+ *
+ * {
+ * "type": "", // 枚举全限定类名, 反序列化时会用到
+ * "name": "", // name 属性
+ * "ordinal": 0, // ordinal 属性
+ * "val": 1, // val 属性(字符串/数字), 反序列化时会用到
+ * "txt": "1" // txt 属性
+ * }
+ * 2、枚举是其他对象的属性
+ *
+ * {
+ * // ... 其他属性
+ * "原字段名称": 1, // val 属性(字符串/数字), 反序列化时会用到
+ * "原字段名称Txt": "1" // txt 属性
+ * }
+ *
+ * @see Dict
+ * @see DictInt
+ * @see DictStr
+ */
+public class DictObjectSerializer implements ObjectWriter {
+
+ @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();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/ienum/Dict.java b/src/main/java/com/njzscloud/common/ienum/Dict.java
new file mode 100644
index 0000000..c06e017
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ienum/Dict.java
@@ -0,0 +1,70 @@
+package com.njzscloud.common.ienum;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.njzscloud.common.fastjson.serializer.DictObjectDeserializer;
+import com.njzscloud.common.fastjson.serializer.DictObjectSerializer;
+import com.njzscloud.common.jackson.serializer.DictDeserializer;
+import com.njzscloud.common.jackson.serializer.DictSerializer;
+
+/**
+ * 字典枚举
+ * 仅两个子接口 DictInt、DictStr
+ *
+ * @see DictInt
+ * @see DictStr
+ * @see DictDeserializer
+ * @see DictSerializer
+ * @see DictObjectDeserializer
+ * @see DictObjectSerializer
+ */
+@JsonDeserialize(using = DictDeserializer.class)
+@JsonSerialize(using = DictSerializer.class)
+public interface Dict extends IEnum {
+
+ /**
+ * 枚举单独序列化时的属性
+ * 存放枚举的 val 属性值
+ */
+ String ENUM_VAL = "val";
+
+ /**
+ * 枚举单独序列化时的属性
+ * 存放枚举的 txt 属性值
+ */
+ String ENUM_TXT = "txt";
+
+ /**
+ * 根据 "值" 获取到对应的枚举对象
+ *
+ * @param val 值
+ * @param ds 枚举对象数组
+ * @param 值类型
+ * @param 枚举类型
+ * @return Dict
+ */
+ static > 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();
+
+
+}
diff --git a/src/main/java/com/njzscloud/common/ienum/DictInt.java b/src/main/java/com/njzscloud/common/ienum/DictInt.java
new file mode 100644
index 0000000..3761461
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ienum/DictInt.java
@@ -0,0 +1,11 @@
+package com.njzscloud.common.ienum;
+
+
+/**
+ * "值" 类型为 Integer
+ * 枚举应实现此接口
+ *
+ * @see DictStr
+ */
+public interface DictInt extends Dict {
+}
diff --git a/src/main/java/com/njzscloud/common/ienum/DictStr.java b/src/main/java/com/njzscloud/common/ienum/DictStr.java
new file mode 100644
index 0000000..45c4442
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ienum/DictStr.java
@@ -0,0 +1,10 @@
+package com.njzscloud.common.ienum;
+
+/**
+ * "值" 类型为 String
+ * 枚举应实现此接口
+ *
+ * @see DictInt
+ */
+public interface DictStr extends Dict {
+}
diff --git a/src/main/java/com/njzscloud/common/ienum/IEnum.java b/src/main/java/com/njzscloud/common/ienum/IEnum.java
new file mode 100644
index 0000000..865c60b
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/ienum/IEnum.java
@@ -0,0 +1,24 @@
+package com.njzscloud.common.ienum;
+
+/**
+ * 枚举接口
+ */
+public interface IEnum {
+ /**
+ * 枚举单独序列化时的属性
+ * 存放枚举的全限定类名
+ */
+ String ENUM_TYPE = "type";
+
+ /**
+ * 枚举单独序列化时的属性
+ * 存放枚举的 name 属性值
+ */
+ String ENUM_NAME = "name";
+
+ /**
+ * 枚举单独序列化时的属性
+ * 存放枚举的 ordinal 属性值
+ */
+ String ENUM_ORDINAL = "ordinal";
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/Jackson.java b/src/main/java/com/njzscloud/common/jackson/Jackson.java
new file mode 100644
index 0000000..b533645
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/Jackson.java
@@ -0,0 +1,214 @@
+package com.njzscloud.common.jackson;
+
+import cn.hutool.core.date.DatePattern;
+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.ex.Exceptions;
+import com.njzscloud.common.jackson.serializer.BigDecimalModule;
+import com.njzscloud.common.jackson.serializer.LongModule;
+import com.njzscloud.common.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 工具
+ * 从 Spring 中获取 ObjectMapper
+ */
+@Slf4j
+public class Jackson {
+ private static final ObjectMapper objectMapper;
+ private static final XmlMapper xmlMapper;
+
+ static {
+ objectMapper = createObjectMapper();
+ xmlMapper = createXmlMapper();
+ }
+
+
+ /**
+ * 序列化为 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 序列化失败");
+ }
+ }
+
+ /**
+ * 反序列化(泛型支持)
+ * 如: new TypeReference<List<String>>(){}, 类型对象建议缓存
+ *
+ * @param json json JSON 字符串
+ * @param type 类型
+ * @return T
+ * @see TypeReference
+ */
+ public static 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 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 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 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 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 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;
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/BigDecimalModule.java b/src/main/java/com/njzscloud/common/jackson/serializer/BigDecimalModule.java
new file mode 100644
index 0000000..b9990a0
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/BigDecimalModule.java
@@ -0,0 +1,16 @@
+package com.njzscloud.common.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());
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/BigDecimalSerializer.java b/src/main/java/com/njzscloud/common/jackson/serializer/BigDecimalSerializer.java
new file mode 100644
index 0000000..8c68a1a
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/BigDecimalSerializer.java
@@ -0,0 +1,22 @@
+package com.njzscloud.common.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 {
+ @Override
+ public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ if (value != null) {
+ gen.writeString(value.toPlainString());
+ } else {
+ gen.writeNull();
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/DictDeserializer.java b/src/main/java/com/njzscloud/common/jackson/serializer/DictDeserializer.java
new file mode 100644
index 0000000..b84de6a
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/DictDeserializer.java
@@ -0,0 +1,102 @@
+package com.njzscloud.common.jackson.serializer;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.njzscloud.common.ex.Exceptions;
+import com.njzscloud.common.ienum.Dict;
+import com.njzscloud.common.ienum.DictInt;
+import com.njzscloud.common.ienum.DictStr;
+import com.njzscloud.common.ienum.IEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+/**
+ * Dict 枚举的 Jackson 反序列化器
+ * JSON 格式见对应的序列化器 {@link DictSerializer}
+ *
+ * @see Dict
+ * @see DictInt
+ * @see DictStr
+ * @see DictSerializer
+ */
+@Slf4j
+public class DictDeserializer extends JsonDeserializer {
+ private static final ClassLoader CLASSLOADER = DictDeserializer.class.getClassLoader();
+
+ @Override
+ public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
+ JsonToken currentToken = p.getCurrentToken();
+
+ if (currentToken == JsonToken.START_OBJECT) {
+ TreeNode treeNode = p.getCodec().readTree(p);
+ TreeNode enumType_node = treeNode.get(IEnum.ENUM_TYPE);
+ String enumTypeField = ((TextNode) enumType_node).textValue();
+ TreeNode val_node = treeNode.get(Dict.ENUM_VAL);
+
+ Class> clazz;
+ try {
+ clazz = CLASSLOADER.loadClass(enumTypeField);
+ } catch (ClassNotFoundException e) {
+ throw Exceptions.error(e, "类型加载失败:{}", enumTypeField);
+ }
+ if (val_node instanceof TextNode) {
+ if (DictStr.class.isAssignableFrom(clazz)) {
+ String val = ((TextNode) val_node).textValue();
+ DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
+ return Dict.parse(val, constants);
+ } else if (DictInt.class.isAssignableFrom(clazz)) {
+ String val = ((TextNode) val_node).textValue();
+ DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
+ return Dict.parse(Integer.parseInt(val), constants);
+ } else {
+ return null;
+ }
+ } else if (val_node instanceof IntNode) {
+ if (DictStr.class.isAssignableFrom(clazz)) {
+ int val = ((IntNode) val_node).intValue();
+ DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
+ return Dict.parse(String.valueOf(val), constants);
+ } else if (DictInt.class.isAssignableFrom(clazz)) {
+ int val = ((IntNode) val_node).intValue();
+ DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
+ return Dict.parse(val, constants);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+
+ } else {
+ JsonStreamContext context = p.getParsingContext();
+ String currentName = context.getCurrentName();
+
+ Object currentValue = p.getCurrentValue();
+
+ Class> valueClazz = currentValue.getClass();
+ try {
+ Field field = valueClazz.getDeclaredField(currentName);
+ Class> clazz = field.getType();
+ if (DictStr.class.isAssignableFrom(clazz)) {
+ String val = p.getValueAsString();
+ DictStr[] constants = (DictStr[]) clazz.getEnumConstants();
+ return Dict.parse(val, constants);
+ } else if (DictInt.class.isAssignableFrom(clazz)) {
+ int val = p.getValueAsInt();
+ DictInt[] constants = (DictInt[]) clazz.getEnumConstants();
+ return Dict.parse(val, constants);
+ } else {
+ return null;
+ }
+ } catch (Exception e) {
+ log.error("字典枚举反序列化失败", e);
+ throw Exceptions.error(e, "字典枚举反序列化失败,字段名:{},值:{}", currentName, currentValue);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/DictSerializer.java b/src/main/java/com/njzscloud/common/jackson/serializer/DictSerializer.java
new file mode 100644
index 0000000..4dbe41e
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/DictSerializer.java
@@ -0,0 +1,61 @@
+package com.njzscloud.common.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.ienum.Dict;
+import com.njzscloud.common.ienum.DictInt;
+import com.njzscloud.common.ienum.DictStr;
+import com.njzscloud.common.ienum.IEnum;
+
+import java.io.IOException;
+
+/**
+ * Dict 枚举的 Jackson 序列化器
+ * JSON 格式
+ * 1、枚举不是其他对象的属性
+ *
+ * {
+ * "type": "", // 枚举全限定类名, 反序列化时会用到
+ * "name": "", // name 属性
+ * "ordinal": 0, // ordinal 属性
+ * "val": 1, // val 属性(字符串/数字), 反序列化时会用到
+ * "txt": "1" // txt 属性
+ * }
+ * 2、枚举是其他对象的属性
+ *
+ * {
+ * // ... 其他属性
+ * "原字段名称": 1, // val 属性(字符串/数字), 反序列化时会用到
+ * "原字段名称Txt": "1" // txt 属性
+ * }
+ *
+ * @see Dict
+ * @see DictInt
+ * @see DictStr
+ * @see DictDeserializer
+ */
+public class DictSerializer extends JsonSerializer {
+ @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());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/LongModule.java b/src/main/java/com/njzscloud/common/jackson/serializer/LongModule.java
new file mode 100644
index 0000000..003223a
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/LongModule.java
@@ -0,0 +1,15 @@
+package com.njzscloud.common.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));
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/LongSerializer.java b/src/main/java/com/njzscloud/common/jackson/serializer/LongSerializer.java
new file mode 100644
index 0000000..e5d60e8
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/LongSerializer.java
@@ -0,0 +1,21 @@
+package com.njzscloud.common.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 {
+ @Override
+ public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ if (value != null) {
+ gen.writeString(value.toString());
+ } else {
+ gen.writeNull();
+ }
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/jackson/serializer/TimeModule.java b/src/main/java/com/njzscloud/common/jackson/serializer/TimeModule.java
new file mode 100644
index 0000000..57eb081
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/jackson/serializer/TimeModule.java
@@ -0,0 +1,29 @@
+package com.njzscloud.common.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)));
+ }
+}
diff --git a/src/main/java/com/njzscloud/common/utils/GroupUtil.java b/src/main/java/com/njzscloud/common/utils/GroupUtil.java
new file mode 100644
index 0000000..d3410ce
--- /dev/null
+++ b/src/main/java/com/njzscloud/common/utils/GroupUtil.java
@@ -0,0 +1,625 @@
+package com.njzscloud.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import com.njzscloud.common.ex.Exceptions;
+import com.njzscloud.common.fastjson.Fastjson;
+
+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
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param 值类型 / 源集合元素类型
+ * @param 键类型
+ * @return Map<K, SV>
+ */
+ public static Map k_o(Collection src, Function kf) {
+ return k_o(src, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, V>
+ */
+ public static Map k_o(Collection src, Function kf, Function vf) {
+ return k_o(src, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 源集合元素
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param mf Map 提供函数
+ * @param 值类型 / 源集合元素类型
+ * @param 键类型
+ * @param Map 类型
+ * @return Map<K, SV>
+ */
+ public static > M k_o(Collection src, Function kf, Supplier mf) {
+ return k_o(src, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, V>
+ */
+ public static > M k_o(Collection src, Function kf, Function vf, Supplier mf) {
+ if (CollUtil.isEmpty(src)) return MapUtil.empty(null);
+
+ return k_o(src.stream(), kf, vf, mf);
+ }
+
+ // endregion
+
+ // region Collection 索引
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ * kf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @return Map<K, VT>
+ */
+ public static Map k_o(Collection src, BiFunction kf) {
+ return k_o(src, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ * kf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf K 提供函数
+ * @param vf V 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, V>
+ */
+ public static Map k_o(Collection src, BiFunction kf, Function vf) {
+ return k_o(src, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 源集合元素
+ * kf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf K 提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param Map 类型
+ * @return Map<K, SV>
+ */
+ public static > M k_o(Collection src, BiFunction kf, Supplier mf) {
+ return k_o(src, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ * kf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf K 提供函数
+ * @param vf V 提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, V>
+ */
+ public static > M k_o(Collection src, BiFunction kf, Function vf, Supplier 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;
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ * vf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, V>
+ */
+ public static Map k_o(Collection src, Function kf, BiFunction vf) {
+ return k_o(src, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ * vf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, V>
+ */
+ public static > M k_o(Collection src, Function kf, BiFunction vf, Supplier 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;
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ * kf vf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, V>
+ */
+ public static Map k_o(Collection src, BiFunction kf, BiFunction vf) {
+ return k_o(src, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ * kf vf 中可得到 源集合元素 索引
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, V>
+ */
+ public static > M k_o(Collection src, BiFunction kf, BiFunction vf, Supplier 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
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @return Map<K, SV>
+ */
+ public static Map k_o(Stream stream, Function kf) {
+ return k_o(stream, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, V>
+ */
+ public static Map k_o(Stream stream, Function kf, Function vf) {
+ return k_o(stream, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param Map 类型
+ * @return Map<K, SV>
+ */
+ public static > M k_o(Stream stream, Function kf, Supplier mf) {
+ return k_o(stream, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, V>
+ */
+ public static > M k_o(Stream stream, Function kf, Function vf, Supplier mf) {
+ if (stream == null) return MapUtil.empty(null);
+ BinaryOperator op = (oldVal, val) -> {
+ throw Exceptions.error("值【{}】与【{}】对应的键重复", Fastjson.toJsonStr(oldVal), Fastjson.toJsonStr(val));
+ };
+ return stream.collect(Collectors.toMap(kf, vf, op, mf));
+ }
+ // endregion
+
+ // endregion
+
+ // region K-A
+
+ // region Collection
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param 键类型
+ * @param 源集合元素类型
+ * @return Map<K, List<V>>
+ */
+ public static Map> k_a(Collection src, Function kf) {
+ return k_a(src, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, List<V>>
+ */
+ public static Map> k_a(Collection src, Function kf, Function vf) {
+ return k_a(src, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 源集合元素
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param Map 类型
+ * @return Map<K, List<SV>>
+ */
+ public static >> M k_a(Collection src, Function kf, Supplier mf) {
+ return k_a(src, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, List<V>>
+ */
+ public static >> M k_a(Collection src, Function kf, Function vf, Supplier 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
+
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param 键类型
+ * @param 源集合元素类型
+ * @return Map<K, List<V>>
+ */
+ public static Map> k_a(Stream stream, Function kf) {
+ return k_a(stream, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, List<V>>
+ */
+ public static Map> k_a(Stream stream, Function kf, Function vf) {
+ return k_a(stream, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 源集合元素
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param Map 类型
+ * @return Map<K, List<SV>>
+ */
+ public static >> M k_a(Stream stream, Function kf, Supplier mf) {
+ return k_a(stream, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, List<V>>
+ */
+ public static >> M k_a(Stream stream, Function kf, Function vf, Supplier 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
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @return Map<K, Set<SV>>
+ */
+ public static Map> k_s(Collection src, Function kf) {
+ return k_s(src, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, Set<V>>
+ */
+ public static Map> k_s(Collection src, Function kf, Function vf) {
+ return k_s(src, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param mf Map 提供函数
+ * @param 键类型
+ * @param 源集合元素类型
+ * @param Map 类型
+ * @return Map<K, Set<SV>>
+ */
+ public static >> M k_s(Collection src, Function kf, Supplier mf) {
+ return k_s(src, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param src 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, Set<V>>
+ */
+ public static >> M k_s(Collection src, Function kf, Function vf, Supplier 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
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 源集合元素
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @return Map<K, Set<SV>>
+ */
+ public static Map> k_s(Stream stream, Function kf) {
+ return k_s(stream, kf, it -> it, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 HashMap, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @return Map<K, Set<V>>
+ */
+ public static Map> k_s(Stream stream, Function kf, Function vf) {
+ return k_s(stream, kf, vf, HashMap::new);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param mf Map 提供函数
+ * @param 键类型
+ * @param 源集合元素类型
+ * @param Map 类型
+ * @return Map<K, Set<SV>>
+ */
+ public static >> M k_s(Stream stream, Function kf, Supplier mf) {
+ return k_s(stream, kf, it -> it, mf);
+ }
+
+ /**
+ * 分组
+ * Map 为 mf, 键为 kf, 值为 vf
+ *
+ * @param stream 源集合
+ * @param kf 键提供函数
+ * @param vf 值提供函数
+ * @param mf Map 提供函数
+ * @param 源集合元素类型
+ * @param 键类型
+ * @param 值类型
+ * @param Map 类型
+ * @return Map<K, Set<V>>
+ */
+ public static >> M k_s(Stream stream, Function kf, Function vf, Supplier mf) {
+ if (stream == null) return MapUtil.empty(null);
+ return stream.collect(Collectors.groupingBy(kf, mf, Collectors.mapping(vf, Collectors.toSet())));
+ }
+ // endregion
+
+ // endregion
+
+}
diff --git a/src/main/java/com/njzscloud/gps/App.java b/src/main/java/com/njzscloud/gps/App.java
new file mode 100644
index 0000000..c3ed860
--- /dev/null
+++ b/src/main/java/com/njzscloud/gps/App.java
@@ -0,0 +1,11 @@
+package com.njzscloud.gps;
+
+/**
+ * Hello world!
+ *
+ */
+public class App {
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ }
+}
diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..af71b14
--- /dev/null
+++ b/src/main/resources/logback-spring.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+ ${console_pattern}
+
+
+
+
+ ${log_path}/${service_name}.log
+
+ ${log_path}/%d{yyyy-MM, aux}/${service_name}.%d{yyyy-MM-dd}.%i.log.zip
+ 50MB
+ 15
+
+
+ ${file_pattern}
+
+
+
+
+
+
+
+
+