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 targetException; + + /** + * 创建异常比较器 + * + * @param exception 目标异常 + */ + public ExceptionDepthComparator(Throwable exception) { + Assert.notNull(exception, "目标异常不能为空"); + this.targetException = exception.getClass(); + } + + /** + * 创建异常比较器 + * + * @param exceptionType 目标异常类型 + */ + public ExceptionDepthComparator(Class exceptionType) { + Assert.notNull(exceptionType, "目标异常类型不能为空"); + this.targetException = exceptionType; + } + + /** + * 从给定异常中获取最接近目标异常的匹配项 + * + * @param exceptionTypes 待匹配的异常列表 + * @param targetException 目标异常 + * @return 匹配到的异常 + */ + public static Class 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 o1, Class 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} + + + + + + + + +