Commit 0a6078610b4b3abd237fb2d6d7f3127b555ab0bb

Authored by YunaiV
1 parent fc8eac54

common 包,基础组件

src/main/java/cn/iocoder/dashboard/common/exception/ErrorCode.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.exception;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.enums.ServiceErrorCodeRange;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 错误码对象
  8 + *
  9 + * 全局错误码,占用 [0, 999],参见 {@link GlobalException}
  10 + * 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
  11 + *
  12 + * TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
  13 + */
  14 +@Data
  15 +public class ErrorCode {
  16 +
  17 + /**
  18 + * 错误码
  19 + */
  20 + private final Integer code;
  21 + /**
  22 + * 错误提示
  23 + */
  24 + private final String message;
  25 +
  26 + public ErrorCode(Integer code, String message) {
  27 + this.code = code;
  28 + this.message = message;
  29 + }
  30 +
  31 +}
src/main/java/cn/iocoder/dashboard/common/exception/GlobalException.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.exception;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +/**
  8 + * 全局异常 Exception
  9 + */
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public class GlobalException extends RuntimeException {
  13 +
  14 + /**
  15 + * 全局错误码
  16 + *
  17 + * @see GlobalErrorCodeConstants
  18 + */
  19 + private Integer code;
  20 + /**
  21 + * 错误提示
  22 + */
  23 + private String message;
  24 +
  25 + /**
  26 + * 空构造方法,避免反序列化问题
  27 + */
  28 + public GlobalException() {
  29 + }
  30 +
  31 + public GlobalException(ErrorCode errorCode) {
  32 + this.code = errorCode.getCode();
  33 + this.message = errorCode.getMessage();
  34 + }
  35 +
  36 + public GlobalException(Integer code, String message) {
  37 + this.code = code;
  38 + this.message = message;
  39 + }
  40 +
  41 +}
src/main/java/cn/iocoder/dashboard/common/exception/ServiceException.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.exception;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.enums.ServiceErrorCodeRange;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +
  7 +/**
  8 + * 业务逻辑异常 Exception
  9 + */
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public final class ServiceException extends RuntimeException {
  13 +
  14 + /**
  15 + * 业务错误码
  16 + *
  17 + * @see ServiceErrorCodeRange
  18 + */
  19 + private Integer code;
  20 + /**
  21 + * 错误提示
  22 + */
  23 + private String message;
  24 +
  25 + /**
  26 + * 空构造方法,避免反序列化问题
  27 + */
  28 + public ServiceException() {
  29 + }
  30 +
  31 + public ServiceException(ErrorCode errorCode) {
  32 + this.code = errorCode.getCode();
  33 + this.message = errorCode.getMessage();
  34 + }
  35 +
  36 + public ServiceException(Integer code, String message) {
  37 + this.code = code;
  38 + this.message = message;
  39 + }
  40 +
  41 + public Integer getCode() {
  42 + return code;
  43 + }
  44 +
  45 + public ServiceException setCode(Integer code) {
  46 + this.code = code;
  47 + return this;
  48 + }
  49 +
  50 + public String getMessage() {
  51 + return message;
  52 + }
  53 +
  54 + public ServiceException setMessage(String message) {
  55 + this.message = message;
  56 + return this;
  57 + }
  58 +
  59 +}
src/main/java/cn/iocoder/dashboard/common/exception/enums/GlobalErrorCodeConstants.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.exception.enums;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +
  5 +/**
  6 + * 全局错误码枚举
  7 + * 0-999 系统异常编码保留
  8 + *
  9 + * 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
  10 + * 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
  11 + * 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public interface GlobalErrorCodeConstants {
  16 +
  17 + ErrorCode SUCCESS = new ErrorCode(0, "成功");
  18 +
  19 + // ========== 客户端错误段 ==========
  20 +
  21 + ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
  22 + ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
  23 + ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
  24 + ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
  25 + ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
  26 +
  27 + // ========== 服务端错误段 ==========
  28 +
  29 + ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
  30 +
  31 + ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
  32 +
  33 + static boolean isMatch(Integer code) {
  34 + return code != null
  35 + && code >= SUCCESS.getCode() && code <= UNKNOWN.getCode();
  36 + }
  37 +
  38 +}
src/main/java/cn/iocoder/dashboard/common/exception/enums/ServiceErrorCodeRange.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.exception.enums;
  2 +
  3 +/**
  4 + * 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
  5 + *
  6 + * 一共 10 位,分成四段
  7 + *
  8 + * 第一段,1 位,类型
  9 + * 1 - 业务级别异常
  10 + * x - 预留
  11 + * 第二段,3 位,系统类型
  12 + * 001 - 用户系统
  13 + * 002 - 商品系统
  14 + * 003 - 订单系统
  15 + * 004 - 支付系统
  16 + * 005 - 优惠劵系统
  17 + * ... - ...
  18 + * 第三段,3 位,模块
  19 + * 不限制规则。
  20 + * 一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
  21 + * 001 - OAuth2 模块
  22 + * 002 - User 模块
  23 + * 003 - MobileCode 模块
  24 + * 第四段,3 位,错误码
  25 + * 不限制规则。
  26 + * 一般建议,每个模块自增。
  27 + *
  28 + * @author 芋道源码
  29 + */
  30 +public class ServiceErrorCodeRange {
  31 +
  32 + // 模块 system 错误码区间 [1-000-001-000 ~ 1-000-002-000]
  33 +
  34 +}
src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.exception.util;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.common.exception.ServiceException;
  5 +import org.slf4j.Logger;
  6 +import org.slf4j.LoggerFactory;
  7 +
  8 +import java.util.Map;
  9 +import java.util.concurrent.ConcurrentHashMap;
  10 +import java.util.concurrent.ConcurrentMap;
  11 +
  12 +/**
  13 + * {@link ServiceException} 工具类
  14 + *
  15 + * 目的在于,格式化异常信息提示。
  16 + * 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
  17 + *
  18 + * 因为 {@link #MESSAGES} 里面默认是没有异常信息提示的模板的,所以需要使用方自己初始化进去。目前想到的有几种方式:
  19 + *
  20 + * 1. 异常提示信息,写在枚举类中,例如说,cn.iocoder.oceans.user.api.constants.ErrorCodeEnum 类 + ServiceExceptionConfiguration
  21 + * 2. 异常提示信息,写在 .properties 等等配置文件
  22 + * 3. 异常提示信息,写在 Apollo 等等配置中心中,从而实现可动态刷新
  23 + * 4. 异常提示信息,存储在 db 等等数据库中,从而实现可动态刷新
  24 + */
  25 +public class ServiceExceptionUtil {
  26 +
  27 + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExceptionUtil.class);
  28 +
  29 + /**
  30 + * 错误码提示模板
  31 + */
  32 + private static final ConcurrentMap<Integer, String> MESSAGES = new ConcurrentHashMap<>();
  33 +
  34 + public static void putAll(Map<Integer, String> messages) {
  35 + ServiceExceptionUtil.MESSAGES.putAll(messages);
  36 + }
  37 +
  38 + public static void put(Integer code, String message) {
  39 + ServiceExceptionUtil.MESSAGES.put(code, message);
  40 + }
  41 +
  42 + public static void delete(Integer code, String message) {
  43 + ServiceExceptionUtil.MESSAGES.remove(code, message);
  44 + }
  45 +
  46 + // ========== 和 ServiceException 的集成 ==========
  47 +
  48 + public static ServiceException exception(ErrorCode errorCode) {
  49 + String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
  50 + return exception0(errorCode.getCode(), messagePattern);
  51 + }
  52 +
  53 + public static ServiceException exception(ErrorCode errorCode, Object... params) {
  54 + String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMessage());
  55 + return exception0(errorCode.getCode(), messagePattern, params);
  56 + }
  57 +
  58 + /**
  59 + * 创建指定编号的 ServiceException 的异常
  60 + *
  61 + * @param code 编号
  62 + * @return 异常
  63 + */
  64 + public static ServiceException exception(Integer code) {
  65 + return exception0(code, MESSAGES.get(code));
  66 + }
  67 +
  68 + /**
  69 + * 创建指定编号的 ServiceException 的异常
  70 + *
  71 + * @param code 编号
  72 + * @param params 消息提示的占位符对应的参数
  73 + * @return 异常
  74 + */
  75 + public static ServiceException exception(Integer code, Object... params) {
  76 + return exception0(code, MESSAGES.get(code), params);
  77 + }
  78 +
  79 + public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
  80 + String message = doFormat(code, messagePattern, params);
  81 + return new ServiceException(code, message);
  82 + }
  83 +
  84 + // ========== 格式化方法 ==========
  85 +
  86 + /**
  87 + * 将错误编号对应的消息使用 params 进行格式化。
  88 + *
  89 + * @param code 错误编号
  90 + * @param messagePattern 消息模版
  91 + * @param params 参数
  92 + * @return 格式化后的提示
  93 + */
  94 + private static String doFormat(int code, String messagePattern, Object... params) {
  95 + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
  96 + int i = 0;
  97 + int j;
  98 + int l;
  99 + for (l = 0; l < params.length; l++) {
  100 + j = messagePattern.indexOf("{}", i);
  101 + if (j == -1) {
  102 + LOGGER.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
  103 + if (i == 0) {
  104 + return messagePattern;
  105 + } else {
  106 + sbuf.append(messagePattern.substring(i, messagePattern.length()));
  107 + return sbuf.toString();
  108 + }
  109 + } else {
  110 + sbuf.append(messagePattern.substring(i, j));
  111 + sbuf.append(params[l]);
  112 + i = j + 2;
  113 + }
  114 + }
  115 + if (messagePattern.indexOf("{}", i) != -1) {
  116 + LOGGER.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
  117 + }
  118 + sbuf.append(messagePattern.substring(i, messagePattern.length()));
  119 + return sbuf.toString();
  120 + }
  121 +
  122 +}
src/main/java/cn/iocoder/dashboard/common/package-info.java 0 → 100644
  1 +/**
  2 + * 基础的通用类,和框架无关
  3 + *
  4 + * 例如说,CommonResult 为通用返回
  5 + */
  6 +package cn.iocoder.dashboard.common;
src/main/java/cn/iocoder/dashboard/common/pojo/CommonResult.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.pojo;
  2 +
  3 +import cn.iocoder.dashboard.common.exception.ErrorCode;
  4 +import cn.iocoder.dashboard.common.exception.GlobalException;
  5 +import cn.iocoder.dashboard.common.exception.ServiceException;
  6 +import cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants;
  7 +import com.alibaba.fastjson.annotation.JSONField;
  8 +import lombok.Data;
  9 +import org.springframework.util.Assert;
  10 +
  11 +import java.io.Serializable;
  12 +
  13 +/**
  14 + * 通用返回
  15 + *
  16 + * @param <T> 数据泛型
  17 + */
  18 +@Data
  19 +public final class CommonResult<T> implements Serializable {
  20 +
  21 + /**
  22 + * 错误码
  23 + *
  24 + * @see ErrorCode#getCode()
  25 + */
  26 + private Integer code;
  27 + /**
  28 + * 返回数据
  29 + */
  30 + private T data;
  31 + /**
  32 + * 错误提示,用户可阅读
  33 + *
  34 + * @see ErrorCode#getMessage() ()
  35 + */
  36 + private String msg;
  37 +
  38 + /**
  39 + * 将传入的 result 对象,转换成另外一个泛型结果的对象
  40 + *
  41 + * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
  42 + *
  43 + * @param result 传入的 result 对象
  44 + * @param <T> 返回的泛型
  45 + * @return 新的 CommonResult 对象
  46 + */
  47 + public static <T> CommonResult<T> error(CommonResult<?> result) {
  48 + return error(result.getCode(), result.getMsg());
  49 + }
  50 +
  51 + public static <T> CommonResult<T> error(Integer code, String message) {
  52 + Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
  53 + CommonResult<T> result = new CommonResult<>();
  54 + result.code = code;
  55 + result.msg = message;
  56 + return result;
  57 + }
  58 +
  59 + public static <T> CommonResult<T> error(ErrorCode errorCode) {
  60 + return error(errorCode.getCode(), errorCode.getMessage());
  61 + }
  62 +
  63 + public static <T> CommonResult<T> success(T data) {
  64 + CommonResult<T> result = new CommonResult<>();
  65 + result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
  66 + result.data = data;
  67 + result.msg = "";
  68 + return result;
  69 + }
  70 +
  71 + @JSONField(serialize = false) // 避免序列化
  72 + public boolean isSuccess() {
  73 + return GlobalErrorCodeConstants.SUCCESS.getCode().equals(code);
  74 + }
  75 +
  76 + @JSONField(serialize = false) // 避免序列化
  77 + public boolean isError() {
  78 + return !isSuccess();
  79 + }
  80 +
  81 + // ========= 和 Exception 异常体系集成 =========
  82 +
  83 + /**
  84 + * 判断是否有异常。如果有,则抛出 {@link GlobalException} 或 {@link ServiceException} 异常
  85 + */
  86 + public void checkError() throws GlobalException, ServiceException {
  87 + if (isSuccess()) {
  88 + return;
  89 + }
  90 + // 全局异常
  91 + if (GlobalErrorCodeConstants.isMatch(code)) {
  92 + throw new GlobalException(code, msg);
  93 + }
  94 + // 业务异常
  95 + throw new ServiceException(code, msg);
  96 + }
  97 +
  98 + public static <T> CommonResult<T> error(ServiceException serviceException) {
  99 + return error(serviceException.getCode(), serviceException.getMessage());
  100 + }
  101 +
  102 + public static <T> CommonResult<T> error(GlobalException globalException) {
  103 + return error(globalException.getCode(), globalException.getMessage());
  104 + }
  105 +
  106 +}
src/main/java/cn/iocoder/dashboard/common/pojo/PageParam.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.pojo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import org.hibernate.validator.constraints.Range;
  7 +
  8 +import javax.validation.constraints.Min;
  9 +import javax.validation.constraints.NotNull;
  10 +import java.io.Serializable;
  11 +
  12 +@ApiModel("分页参数")
  13 +@Data
  14 +public class PageParam implements Serializable {
  15 +
  16 + @ApiModelProperty(value = "页码,从 1 开始", required = true,example = "1")
  17 + @NotNull(message = "页码不能为空")
  18 + @Min(value = 1, message = "页码最小值为 1")
  19 + private Integer pageNo;
  20 +
  21 + @ApiModelProperty(value = "每页条数,最大值为 100", required = true, example = "10")
  22 + @NotNull(message = "每页条数不能为空")
  23 + @Range(min = 1, max = 100, message = "条数范围为 [1, 100]")
  24 + private Integer pageSize;
  25 +
  26 +}
src/main/java/cn/iocoder/dashboard/common/pojo/PageResult.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.pojo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +import java.io.Serializable;
  8 +import java.util.List;
  9 +
  10 +@ApiModel("分页结果")
  11 +@Data
  12 +public final class PageResult<T> implements Serializable {
  13 +
  14 + @ApiModelProperty(value = "数据", required = true)
  15 + private List<T> list;
  16 +
  17 + @ApiModelProperty(value = "总量", required = true)
  18 + private Long total;
  19 +
  20 +}
src/main/java/cn/iocoder/dashboard/common/pojo/SortingField.java 0 → 100644
  1 +package cn.iocoder.dashboard.common.pojo;
  2 +
  3 +import java.io.Serializable;
  4 +
  5 +/**
  6 + * 排序字段 DTO
  7 + *
  8 + * 类名加了 ing 的原因是,避免和 ES SortField 重名。
  9 + */
  10 +public class SortingField implements Serializable {
  11 +
  12 + /**
  13 + * 顺序 - 升序
  14 + */
  15 + public static final String ORDER_ASC = "asc";
  16 + /**
  17 + * 顺序 - 降序
  18 + */
  19 + public static final String ORDER_DESC = "desc";
  20 +
  21 + /**
  22 + * 字段
  23 + */
  24 + private String field;
  25 + /**
  26 + * 顺序
  27 + */
  28 + private String order;
  29 +
  30 + // 空构造方法,解决反序列化
  31 + public SortingField() {
  32 + }
  33 +
  34 + public SortingField(String field, String order) {
  35 + this.field = field;
  36 + this.order = order;
  37 + }
  38 +
  39 + public String getField() {
  40 + return field;
  41 + }
  42 +
  43 + public SortingField setField(String field) {
  44 + this.field = field;
  45 + return this;
  46 + }
  47 +
  48 + public String getOrder() {
  49 + return order;
  50 + }
  51 +
  52 + public SortingField setOrder(String order) {
  53 + this.order = order;
  54 + return this;
  55 + }
  56 +}
src/main/java/cn/iocoder/dashboard/util/collection/CollectionUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.util.collection;
  2 +
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +
  5 +import java.util.*;
  6 +import java.util.function.Function;
  7 +import java.util.stream.Collectors;
  8 +
  9 +/**
  10 + * Collection 工具类
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class CollectionUtils {
  15 +
  16 + public static <T> Set<T> asSet(T... objs) {
  17 + return new HashSet<>(Arrays.asList(objs));
  18 + }
  19 +
  20 + public static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
  21 + return from.stream().map(func).collect(Collectors.toList());
  22 + }
  23 +
  24 + public static <T, U> Set<U> convertSet(List<T> from, Function<T, U> func) {
  25 + return from.stream().map(func).collect(Collectors.toSet());
  26 + }
  27 +
  28 + public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
  29 + return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
  30 + }
  31 +
  32 + public static <T, K, V> Map<K, V> convertMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
  33 + return from.stream().collect(Collectors.toMap(keyFunc, valueFunc));
  34 + }
  35 +
  36 + public static <T, K> Map<K, List<T>> convertMultiMap(List<T> from, Function<T, K> keyFunc) {
  37 + return from.stream().collect(Collectors.groupingBy(keyFunc,
  38 + Collectors.mapping(t -> t, Collectors.toList())));
  39 + }
  40 +
  41 + public static <T, K, V> Map<K, List<V>> convertMultiMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
  42 + return from.stream().collect(Collectors.groupingBy(keyFunc,
  43 + Collectors.mapping(valueFunc, Collectors.toList())));
  44 + }
  45 +
  46 + // 暂时没想好名字,先以 2 结尾噶
  47 + public static <T, K, V> Map<K, Set<V>> convertMultiMap2(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
  48 + return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
  49 + }
  50 +
  51 + public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
  52 + return org.springframework.util.CollectionUtils.containsAny(source, candidates);
  53 + }
  54 +
  55 + public static <T> T getFirst(List<T> from) {
  56 + return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
  57 + }
  58 +
  59 + public static <T> void addIfNotNull(Collection<T> coll, T item) {
  60 + if (item == null) {
  61 + return;
  62 + }
  63 + coll.add(item);
  64 + }
  65 +
  66 +}
src/main/java/cn/iocoder/dashboard/util/collection/MapUtils.java 0 → 100644
  1 +package cn.iocoder.dashboard.util.collection;
  2 +
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +import com.google.common.collect.Multimap;
  5 +
  6 +import java.util.ArrayList;
  7 +import java.util.Collection;
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * Map 工具类
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public class MapUtils {
  16 +
  17 + public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {
  18 + List<V> result = new ArrayList<>();
  19 + keys.forEach(k -> {
  20 + Collection<V> values = multimap.get(k);
  21 + if (CollectionUtil.isEmpty(values)) {
  22 + return;
  23 + }
  24 + result.addAll(values);
  25 + });
  26 + return result;
  27 + }
  28 +
  29 +}
src/main/java/cn/iocoder/dashboard/util/package-info.java 0 → 100644
  1 +/**
  2 + * 对于工具类的选择,优先查找 Hutool 中有没对应的方法
  3 + * 如果没有,则自己封装对应的工具类,以 Utils 结尾,用于区分
  4 + *
  5 + * ps:如果担心 Hutool 存在坑的问题,可以阅读 Hutool 的实现源码,以确保可靠性。并且,可以补充相关的单元测试。
  6 + */
  7 +package cn.iocoder.dashboard.util;