因为jdk升级到了17 , 所以引起了一些包的无法正常使用,这里,做了修改
This commit is contained in:
parent
258f6e823f
commit
3d76b9a103
2
.gitignore
vendored
2
.gitignore
vendored
@ -30,4 +30,4 @@ replay_pid*
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
*.ipr!/.flattened-pom.xml
|
||||
|
2
dependencies/pom.xml
vendored
2
dependencies/pom.xml
vendored
@ -53,7 +53,7 @@
|
||||
<easyexcel.verion>4.0.3</easyexcel.verion>
|
||||
<velocity.version>2.4</velocity.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<guava.version>33.2.1-jre</guava.version>
|
||||
<guava.version>33.3.1-jre</guava.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<commons-net.version>3.11.1</commons-net.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
|
153
framework/commons/pom.xml
Normal file
153
framework/commons/pom.xml
Normal file
@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>framework</artifactId>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>commons</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>定义基础 pojo 类、枚举、工具类等等</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-trace</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,15 @@
|
||||
package cd.casic.framework.commons.core;
|
||||
|
||||
/**
|
||||
* 可生成 Int 数组的接口
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public interface IntArrayValuable {
|
||||
|
||||
/**
|
||||
* @return int 数组
|
||||
*/
|
||||
int[] array();
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cd.casic.framework.commons.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Key Value 的键值对
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class KeyValue<K, V> implements Serializable {
|
||||
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package cd.casic.framework.commons.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 通用状态枚举
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CommonStatusEnum implements IntArrayValuable {
|
||||
|
||||
ENABLE(0, "开启"),
|
||||
DISABLE(1, "关闭");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static boolean isEnable(Integer status) {
|
||||
return ObjUtil.equal(ENABLE.status, status);
|
||||
}
|
||||
|
||||
public static boolean isDisable(Integer status) {
|
||||
return ObjUtil.equal(DISABLE.status, status);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package cd.casic.framework.commons.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 时间间隔的枚举
|
||||
*
|
||||
* @author dhb52
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DateIntervalEnum implements IntArrayValuable {
|
||||
|
||||
DAY(1, "天"),
|
||||
WEEK(2, "周"),
|
||||
MONTH(3, "月"),
|
||||
QUARTER(4, "季度"),
|
||||
YEAR(5, "年")
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer interval;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
public static DateIntervalEnum valueOf(Integer interval) {
|
||||
return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package cd.casic.framework.commons.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 文档地址
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DocumentEnum {
|
||||
;
|
||||
private final String url;
|
||||
private final String memo;
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.framework.commons.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 终端的枚举
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum TerminalEnum implements IntArrayValuable {
|
||||
|
||||
UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
|
||||
H5(20, "H5 网页"),
|
||||
APP(31, "手机 App"),
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
|
||||
|
||||
/**
|
||||
* 终端
|
||||
*/
|
||||
private final Integer terminal;
|
||||
/**
|
||||
* 终端名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package cd.casic.framework.commons.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 全局用户类型枚举
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum UserTypeEnum implements IntArrayValuable {
|
||||
|
||||
MEMBER(1, "会员"), // 面向 c 端,普通用户
|
||||
ADMIN(2, "管理员"); // 面向 b 端,管理后台
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(UserTypeEnum::getValue).toArray();
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer value;
|
||||
/**
|
||||
* 类型名
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
public static UserTypeEnum valueOf(Integer value) {
|
||||
return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package cd.casic.framework.commons.enums;
|
||||
|
||||
/**
|
||||
* Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
|
||||
*
|
||||
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public interface WebFilterOrderEnum {
|
||||
|
||||
int CORS_FILTER = Integer.MIN_VALUE;
|
||||
|
||||
int TRACE_FILTER = CORS_FILTER + 1;
|
||||
|
||||
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
|
||||
|
||||
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
|
||||
|
||||
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
|
||||
|
||||
int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
|
||||
|
||||
int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面
|
||||
|
||||
// Spring Security Filter 默认为 -100,可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类
|
||||
|
||||
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
|
||||
|
||||
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
|
||||
|
||||
int DEMO_FILTER = Integer.MAX_VALUE;
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package cd.casic.framework.commons.exception;
|
||||
|
||||
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
||||
import cd.casic.framework.commons.exception.enums.ServiceErrorCodeRange;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 错误码对象
|
||||
*
|
||||
* 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
|
||||
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
|
||||
*
|
||||
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
|
||||
*/
|
||||
@Data
|
||||
public class ErrorCode {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private final String msg;
|
||||
|
||||
public ErrorCode(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.msg = message;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package cd.casic.framework.commons.exception;
|
||||
|
||||
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 服务器异常 Exception
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class ServerException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 全局错误码
|
||||
*
|
||||
* @see GlobalErrorCodeConstants
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 空构造方法,避免反序列化问题
|
||||
*/
|
||||
public ServerException() {
|
||||
}
|
||||
|
||||
public ServerException(ErrorCode errorCode) {
|
||||
this.code = errorCode.getCode();
|
||||
this.message = errorCode.getMsg();
|
||||
}
|
||||
|
||||
public ServerException(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public ServerException setCode(Integer code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public ServerException setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package cd.casic.framework.commons.exception;
|
||||
|
||||
import cd.casic.framework.commons.exception.enums.ServiceErrorCodeRange;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 业务逻辑异常 Exception
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class ServiceException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 业务错误码
|
||||
*
|
||||
* @see ServiceErrorCodeRange
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 错误提示
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 空构造方法,避免反序列化问题
|
||||
*/
|
||||
public ServiceException() {
|
||||
}
|
||||
|
||||
public ServiceException(ErrorCode errorCode) {
|
||||
this.code = errorCode.getCode();
|
||||
this.message = errorCode.getMsg();
|
||||
}
|
||||
|
||||
public ServiceException(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public ServiceException setCode(Integer code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public ServiceException setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package cd.casic.framework.commons.exception.enums;
|
||||
|
||||
|
||||
import cd.casic.framework.commons.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 全局错误码枚举
|
||||
* 0-999 系统异常编码保留
|
||||
*
|
||||
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
|
||||
* 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
|
||||
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public interface GlobalErrorCodeConstants {
|
||||
|
||||
ErrorCode SUCCESS = new ErrorCode(0, "成功");
|
||||
|
||||
// ========== 客户端错误段 ==========
|
||||
|
||||
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
|
||||
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
|
||||
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
|
||||
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
|
||||
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
|
||||
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
|
||||
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
|
||||
|
||||
// ========== 服务端错误段 ==========
|
||||
|
||||
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
|
||||
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
|
||||
ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
|
||||
|
||||
// ========== 自定义错误段 ==========
|
||||
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
|
||||
ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
|
||||
|
||||
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package cd.casic.framework.commons.exception.enums;
|
||||
|
||||
/**
|
||||
* 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
|
||||
*
|
||||
* 一共 10 位,分成四段
|
||||
*
|
||||
* 第一段,1 位,类型
|
||||
* 1 - 业务级别异常
|
||||
* x - 预留
|
||||
* 第二段,3 位,系统类型
|
||||
* 001 - 用户系统
|
||||
* 002 - 商品系统
|
||||
* 003 - 订单系统
|
||||
* 004 - 支付系统
|
||||
* 005 - 优惠劵系统
|
||||
* ... - ...
|
||||
* 第三段,3 位,模块
|
||||
* 不限制规则。
|
||||
* 一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
|
||||
* 001 - OAuth2 模块
|
||||
* 002 - User 模块
|
||||
* 003 - MobileCode 模块
|
||||
* 第四段,3 位,错误码
|
||||
* 不限制规则。
|
||||
* 一般建议,每个模块自增。
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class ServiceErrorCodeRange {
|
||||
|
||||
// 模块 infra 错误码区间 [1-001-000-000 ~ 1-002-000-000)
|
||||
// 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)
|
||||
// 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000)
|
||||
// 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
|
||||
// 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)
|
||||
// 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
|
||||
// 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
|
||||
|
||||
// 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
|
||||
// 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)
|
||||
// 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)
|
||||
|
||||
// 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000)
|
||||
|
||||
// 模块 ai 错误码区间 [1-022-000-000 ~ 1-023-000-000)
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package cd.casic.framework.commons.exception.util;
|
||||
|
||||
import cd.casic.framework.commons.exception.ErrorCode;
|
||||
import cd.casic.framework.commons.exception.ServiceException;
|
||||
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* {@link ServiceException} 工具类
|
||||
*
|
||||
* 目的在于,格式化异常信息提示。
|
||||
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServiceExceptionUtil {
|
||||
|
||||
// ========== 和 ServiceException 的集成 ==========
|
||||
|
||||
public static ServiceException exception(ErrorCode errorCode) {
|
||||
return exception0(errorCode.getCode(), errorCode.getMsg());
|
||||
}
|
||||
|
||||
public static ServiceException exception(ErrorCode errorCode, Object... params) {
|
||||
return exception0(errorCode.getCode(), errorCode.getMsg(), params);
|
||||
}
|
||||
|
||||
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
|
||||
String message = doFormat(code, messagePattern, params);
|
||||
return new ServiceException(code, message);
|
||||
}
|
||||
|
||||
public static ServiceException invalidParamException(String messagePattern, Object... params) {
|
||||
return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);
|
||||
}
|
||||
|
||||
// ========== 格式化方法 ==========
|
||||
|
||||
/**
|
||||
* 将错误编号对应的消息使用 params 进行格式化。
|
||||
*
|
||||
* @param code 错误编号
|
||||
* @param messagePattern 消息模版
|
||||
* @param params 参数
|
||||
* @return 格式化后的提示
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static String doFormat(int code, String messagePattern, Object... params) {
|
||||
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
|
||||
int i = 0;
|
||||
int j;
|
||||
int l;
|
||||
for (l = 0; l < params.length; l++) {
|
||||
j = messagePattern.indexOf("{}", i);
|
||||
if (j == -1) {
|
||||
log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||
if (i == 0) {
|
||||
return messagePattern;
|
||||
} else {
|
||||
sbuf.append(messagePattern.substring(i));
|
||||
return sbuf.toString();
|
||||
}
|
||||
} else {
|
||||
sbuf.append(messagePattern, i, j);
|
||||
sbuf.append(params[l]);
|
||||
i = j + 2;
|
||||
}
|
||||
}
|
||||
if (messagePattern.indexOf("{}", i) != -1) {
|
||||
log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
|
||||
}
|
||||
sbuf.append(messagePattern.substring(i));
|
||||
return sbuf.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package cd.casic.framework.commons.pojo;
|
||||
|
||||
import cd.casic.framework.commons.exception.ErrorCode;
|
||||
import cd.casic.framework.commons.exception.ServiceException;
|
||||
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
||||
import cd.casic.framework.commons.exception.util.ServiceExceptionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 通用返回
|
||||
*
|
||||
* @param <T> 数据泛型
|
||||
*/
|
||||
@Data
|
||||
public class CommonResult<T> implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*
|
||||
* @see ErrorCode#getCode()
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 返回数据
|
||||
*/
|
||||
private T data;
|
||||
/**
|
||||
* 错误提示,用户可阅读
|
||||
*
|
||||
* @see ErrorCode#getMsg() ()
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 将传入的 result 对象,转换成另外一个泛型结果的对象
|
||||
*
|
||||
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
|
||||
*
|
||||
* @param result 传入的 result 对象
|
||||
* @param <T> 返回的泛型
|
||||
* @return 新的 CommonResult 对象
|
||||
*/
|
||||
public static <T> CommonResult<T> error(CommonResult<?> result) {
|
||||
return error(result.getCode(), result.getMsg());
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(Integer code, String message) {
|
||||
cn.hutool.core.lang.Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = code;
|
||||
result.msg = message;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
|
||||
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = errorCode.getCode();
|
||||
result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(ErrorCode errorCode) {
|
||||
return error(errorCode.getCode(), errorCode.getMsg());
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> success(T data) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
|
||||
result.data = data;
|
||||
result.msg = "";
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isSuccess(Integer code) {
|
||||
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isSuccess() {
|
||||
return isSuccess(code);
|
||||
}
|
||||
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public boolean isError() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
// ========= 和 Exception 异常体系集成 =========
|
||||
|
||||
/**
|
||||
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
|
||||
*/
|
||||
public void checkError() throws ServiceException {
|
||||
if (isSuccess()) {
|
||||
return;
|
||||
}
|
||||
// 业务异常
|
||||
throw new ServiceException(code, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
|
||||
* 如果没有,则返回 {@link #data} 数据
|
||||
*/
|
||||
@JsonIgnore // 避免 jackson 序列化
|
||||
public T getCheckedData() {
|
||||
checkError();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static <T> CommonResult<T> error(ServiceException serviceException) {
|
||||
return error(serviceException.getCode(), serviceException.getMessage());
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.framework.commons.pojo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Schema(description="分页参数")
|
||||
@Data
|
||||
public class PageParam implements Serializable {
|
||||
|
||||
private static final Integer PAGE_NO = 1;
|
||||
private static final Integer PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* 每页条数 - 不分页
|
||||
*
|
||||
* 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
|
||||
*/
|
||||
public static final Integer PAGE_SIZE_NONE = -1;
|
||||
|
||||
@Schema(description = "页码,从 1 开始", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
|
||||
@NotNull(message = "页码不能为空")
|
||||
@Min(value = 1, message = "页码最小值为 1")
|
||||
private Integer pageNo = PAGE_NO;
|
||||
|
||||
@Schema(description = "每页条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
@NotNull(message = "每页条数不能为空")
|
||||
@Min(value = 1, message = "每页条数最小值为 1")
|
||||
@Max(value = 100, message = "每页条数最大值为 100")
|
||||
private Integer pageSize = PAGE_SIZE;
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package cd.casic.framework.commons.pojo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "分页结果")
|
||||
@Data
|
||||
public final class PageResult<T> implements Serializable {
|
||||
|
||||
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<T> list;
|
||||
|
||||
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long total;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public PageResult(List<T> list, Long total) {
|
||||
this.list = list;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public PageResult(Long total) {
|
||||
this.list = new ArrayList<>();
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> empty() {
|
||||
return new PageResult<>(0L);
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> empty(Long total) {
|
||||
return new PageResult<>(total);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.framework.commons.pojo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "可排序的分页参数")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class SortablePageParam extends PageParam {
|
||||
|
||||
@Schema(description = "排序字段")
|
||||
private List<SortingField> sortingFields;
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package cd.casic.framework.commons.pojo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 排序字段 DTO
|
||||
*
|
||||
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SortingField implements Serializable {
|
||||
|
||||
/**
|
||||
* 顺序 - 升序
|
||||
*/
|
||||
public static final String ORDER_ASC = "asc";
|
||||
/**
|
||||
* 顺序 - 降序
|
||||
*/
|
||||
public static final String ORDER_DESC = "desc";
|
||||
|
||||
/**
|
||||
* 字段
|
||||
*/
|
||||
private String field;
|
||||
/**
|
||||
* 顺序
|
||||
*/
|
||||
private String order;
|
||||
|
||||
}
|
49
framework/commons/src/main/java/cd/casic/framework/commons/util/cache/CacheUtils.java
vendored
Normal file
49
framework/commons/src/main/java/cd/casic/framework/commons/util/cache/CacheUtils.java
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package cd.casic.framework.commons.util.cache;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Cache 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class CacheUtils {
|
||||
|
||||
/**
|
||||
* 构建异步刷新的 LoadingCache 对象
|
||||
*
|
||||
* 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||
*
|
||||
* 或者简单理解:
|
||||
* 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法
|
||||
* 2、和“全局”、“系统”相关的,使用当前缓存方法
|
||||
*
|
||||
* @param duration 过期时间
|
||||
* @param loader CacheLoader 对象
|
||||
* @return LoadingCache 对象
|
||||
*/
|
||||
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
|
||||
return CacheBuilder.newBuilder()
|
||||
// 只阻塞当前数据加载线程,其他线程返回旧值
|
||||
.refreshAfterWrite(duration)
|
||||
// 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
|
||||
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建同步刷新的 LoadingCache 对象
|
||||
*
|
||||
* @param duration 过期时间
|
||||
* @param loader CacheLoader 对象
|
||||
* @return LoadingCache 对象
|
||||
*/
|
||||
public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
|
||||
return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package cd.casic.framework.commons.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.collection.IterUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static cd.casic.framework.commons.util.collection.CollectionUtils.convertList;
|
||||
|
||||
|
||||
/**
|
||||
* Array 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class ArrayUtils {
|
||||
|
||||
/**
|
||||
* 将 object 和 newElements 合并成一个数组
|
||||
*
|
||||
* @param object 对象
|
||||
* @param newElements 数组
|
||||
* @param <T> 泛型
|
||||
* @return 结果数组
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
|
||||
if (object == null) {
|
||||
return newElements;
|
||||
}
|
||||
Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
|
||||
result[0] = object;
|
||||
System.arraycopy(newElements, 0, result, 1, newElements.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {
|
||||
return toArray(convertList(from, mapper));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] toArray(Collection<T> from) {
|
||||
if (CollectionUtil.isEmpty(from)) {
|
||||
return (T[]) (new Object[0]);
|
||||
}
|
||||
return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
|
||||
}
|
||||
|
||||
public static <T> T get(T[] array, int index) {
|
||||
if (null == array || index >= array.length) {
|
||||
return null;
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,338 @@
|
||||
package cd.casic.framework.commons.util.collection;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* Collection 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class CollectionUtils {
|
||||
|
||||
public static boolean containsAny(Object source, Object... targets) {
|
||||
return asList(targets).contains(source);
|
||||
}
|
||||
|
||||
public static boolean isAnyEmpty(Collection<?>... collections) {
|
||||
return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
|
||||
}
|
||||
|
||||
public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
|
||||
return from.stream().anyMatch(predicate);
|
||||
}
|
||||
|
||||
public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return distinct(from, keyMapper, (t1, t2) -> t1);
|
||||
}
|
||||
|
||||
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
|
||||
if (ArrayUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return convertList(Arrays.asList(from), func);
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
|
||||
if (ArrayUtil.isEmpty(from)) {
|
||||
return new PageResult<>(from.getTotal());
|
||||
}
|
||||
return new PageResult<>(convertList(from.getList(), func), from.getTotal());
|
||||
}
|
||||
|
||||
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
|
||||
Function<T, ? extends Stream<? extends U>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
|
||||
Function<? super T, ? extends U> mapper,
|
||||
Function<U, ? extends Stream<? extends R>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
|
||||
return map.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T> Set<T> convertSet(Collection<T> from) {
|
||||
return convertSet(from, v -> v);
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
|
||||
}
|
||||
|
||||
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
|
||||
Function<T, ? extends Stream<? extends U>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
|
||||
Function<? super T, ? extends U> mapper,
|
||||
Function<U, ? extends Stream<? extends R>> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, Function.identity());
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return supplier.get();
|
||||
}
|
||||
return convertMap(from, keyFunc, Function.identity(), supplier);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return supplier.get();
|
||||
}
|
||||
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
|
||||
}
|
||||
|
||||
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream()
|
||||
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
|
||||
}
|
||||
|
||||
// 暂时没想好名字,先以 2 结尾噶
|
||||
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
|
||||
}
|
||||
|
||||
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
|
||||
from.forEach(item -> builder.put(keyFunc.apply(item), item));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比老、新两个列表,找出新增、修改、删除的数据
|
||||
*
|
||||
* @param oldList 老列表
|
||||
* @param newList 新列表
|
||||
* @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
|
||||
* 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
|
||||
* @return [新增列表、修改列表、删除列表]
|
||||
*/
|
||||
public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
|
||||
BiFunction<T, T, Boolean> sameFunc) {
|
||||
List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
|
||||
List<T> updateList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
|
||||
for (T oldObj : oldList) {
|
||||
// 1. 寻找是否有匹配的
|
||||
T foundObj = null;
|
||||
for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
|
||||
T newObj = iterator.next();
|
||||
// 1.1 不匹配,则直接跳过
|
||||
if (!sameFunc.apply(oldObj, newObj)) {
|
||||
continue;
|
||||
}
|
||||
// 1.2 匹配,则移除,并结束寻找
|
||||
iterator.remove();
|
||||
foundObj = newObj;
|
||||
break;
|
||||
}
|
||||
// 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
|
||||
if (foundObj != null) {
|
||||
updateList.add(foundObj);
|
||||
} else {
|
||||
deleteList.add(oldObj);
|
||||
}
|
||||
}
|
||||
return asList(createList, updateList, deleteList);
|
||||
}
|
||||
|
||||
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
|
||||
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
|
||||
}
|
||||
|
||||
public static <T> T getFirst(List<T> from) {
|
||||
return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
|
||||
}
|
||||
|
||||
public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
|
||||
return findFirst(from, predicate, Function.identity());
|
||||
}
|
||||
|
||||
public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
return from.stream().filter(predicate).findFirst().map(func).orElse(null);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert !from.isEmpty(); // 断言,避免告警
|
||||
T t = from.stream().max(Comparator.comparing(valueFunc)).get();
|
||||
return valueFunc.apply(t);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert from.size() > 0; // 断言,避免告警
|
||||
T t = from.stream().min(Comparator.comparing(valueFunc)).get();
|
||||
return valueFunc.apply(t);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return null;
|
||||
}
|
||||
assert from.size() > 0; // 断言,避免告警
|
||||
return from.stream().min(Comparator.comparing(valueFunc)).get();
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
|
||||
BinaryOperator<V> accumulator) {
|
||||
return getSumValue(from, valueFunc, accumulator, null);
|
||||
}
|
||||
|
||||
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
|
||||
BinaryOperator<V> accumulator, V defaultValue) {
|
||||
if (CollUtil.isEmpty(from)) {
|
||||
return defaultValue;
|
||||
}
|
||||
assert !from.isEmpty(); // 断言,避免告警
|
||||
return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
|
||||
}
|
||||
|
||||
public static <T> void addIfNotNull(Collection<T> coll, T item) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
coll.add(item);
|
||||
}
|
||||
|
||||
public static <T> Collection<T> singleton(T obj) {
|
||||
return obj == null ? Collections.emptyList() : Collections.singleton(obj);
|
||||
}
|
||||
|
||||
public static <T> List<T> newArrayList(List<List<T>> list) {
|
||||
return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package cd.casic.framework.commons.util.collection;
|
||||
|
||||
import cd.casic.framework.commons.core.KeyValue;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Map 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class MapUtils {
|
||||
|
||||
/**
|
||||
* 从哈希表表中,获得 keys 对应的所有 value 数组
|
||||
*
|
||||
* @param multimap 哈希表
|
||||
* @param keys keys
|
||||
* @return value 数组
|
||||
*/
|
||||
public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {
|
||||
List<V> result = new ArrayList<>();
|
||||
keys.forEach(k -> {
|
||||
Collection<V> values = multimap.get(k);
|
||||
if (CollectionUtil.isEmpty(values)) {
|
||||
return;
|
||||
}
|
||||
result.addAll(values);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从哈希表查找到 key 对应的 value,然后进一步处理
|
||||
* key 为 null 时, 不处理
|
||||
* 注意,如果查找到的 value 为 null 时,不进行处理
|
||||
*
|
||||
* @param map 哈希表
|
||||
* @param key key
|
||||
* @param consumer 进一步处理的逻辑
|
||||
*/
|
||||
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
|
||||
if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
V value = map.get(key);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
consumer.accept(value);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {
|
||||
Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
|
||||
keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.framework.commons.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Set 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class SetUtils {
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> asSet(T... objs) {
|
||||
return CollUtil.newHashSet(objs);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package cd.casic.framework.commons.util.date;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 时间工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
/**
|
||||
* 时区 - 默认
|
||||
*/
|
||||
public static final String TIME_ZONE_DEFAULT = "GMT+8";
|
||||
|
||||
/**
|
||||
* 秒转换成毫秒
|
||||
*/
|
||||
public static final long SECOND_MILLIS = 1000;
|
||||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
|
||||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 将 LocalDateTime 转换成 Date
|
||||
*
|
||||
* @param date LocalDateTime
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static Date of(LocalDateTime date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
// 将此日期时间与时区相结合以创建 ZonedDateTime
|
||||
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
|
||||
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
|
||||
Instant instant = zonedDateTime.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return Date.from(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Date 转换成 LocalDateTime
|
||||
*
|
||||
* @param date Date
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static LocalDateTime of(Date date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
// 转为时间戳
|
||||
Instant instant = date.toInstant();
|
||||
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public static Date addTime(Duration duration) {
|
||||
return new Date(System.currentTimeMillis() + duration.toMillis());
|
||||
}
|
||||
|
||||
public static boolean isExpired(LocalDateTime time) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
return now.isAfter(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int mouth, int day) {
|
||||
return buildTime(year, mouth, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @param hour 小时
|
||||
* @param minute 分钟
|
||||
* @param second 秒
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static Date buildTime(int year, int mouth, int day,
|
||||
int hour, int minute, int second) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(Calendar.YEAR, year);
|
||||
calendar.set(Calendar.MONTH, mouth - 1);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, day);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||
calendar.set(Calendar.MINUTE, minute);
|
||||
calendar.set(Calendar.SECOND, second);
|
||||
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
public static Date max(Date a, Date b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
return a.compareTo(b) > 0 ? a : b;
|
||||
}
|
||||
|
||||
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
|
||||
if (a == null) {
|
||||
return b;
|
||||
}
|
||||
if (b == null) {
|
||||
return a;
|
||||
}
|
||||
return a.isAfter(b) ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否今天
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isToday(LocalDateTime date) {
|
||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否昨天
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isYesterday(LocalDateTime date) {
|
||||
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,309 @@
|
||||
package cd.casic.framework.commons.util.date;
|
||||
|
||||
import cd.casic.framework.commons.enums.DateIntervalEnum;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 时间工具类,用于 {@link LocalDateTime}
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class LocalDateTimeUtils {
|
||||
|
||||
/**
|
||||
* 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
|
||||
*/
|
||||
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
|
||||
|
||||
/**
|
||||
* 解析时间
|
||||
*
|
||||
* 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功
|
||||
*
|
||||
* @param time 时间
|
||||
* @return 时间字符串
|
||||
*/
|
||||
public static LocalDateTime parse(String time) {
|
||||
try {
|
||||
return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);
|
||||
} catch (DateTimeParseException e) {
|
||||
return LocalDateTimeUtil.parse(time);
|
||||
}
|
||||
}
|
||||
|
||||
public static LocalDateTime addTime(Duration duration) {
|
||||
return LocalDateTime.now().plus(duration);
|
||||
}
|
||||
|
||||
public static LocalDateTime minusTime(Duration duration) {
|
||||
return LocalDateTime.now().minus(duration);
|
||||
}
|
||||
|
||||
public static boolean beforeNow(LocalDateTime date) {
|
||||
return date.isBefore(LocalDateTime.now());
|
||||
}
|
||||
|
||||
public static boolean afterNow(LocalDateTime date) {
|
||||
return date.isAfter(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
* @param year 年
|
||||
* @param mouth 月
|
||||
* @param day 日
|
||||
* @return 指定时间
|
||||
*/
|
||||
public static LocalDateTime buildTime(int year, int mouth, int day) {
|
||||
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
|
||||
int year2, int mouth2, int day2) {
|
||||
return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
|
||||
}
|
||||
|
||||
/**
|
||||
* 判指定断时间,是否在该时间范围内
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param time 指定时间
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {
|
||||
if (startTime == null || endTime == null || time == null) {
|
||||
return false;
|
||||
}
|
||||
return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否在该时间范围内
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
if (startTime == null || endTime == null) {
|
||||
return false;
|
||||
}
|
||||
return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否在该时间范围内
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean isBetween(String startTime, String endTime) {
|
||||
if (startTime == null || endTime == null) {
|
||||
return false;
|
||||
}
|
||||
LocalDate nowDate = LocalDate.now();
|
||||
return LocalDateTimeUtil.isIn(LocalDateTime.now(),
|
||||
LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
|
||||
LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断时间段是否重叠
|
||||
*
|
||||
* @param startTime1 开始 time1
|
||||
* @param endTime1 结束 time1
|
||||
* @param startTime2 开始 time2
|
||||
* @param endTime2 结束 time2
|
||||
* @return 重叠:true 不重叠:false
|
||||
*/
|
||||
public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
|
||||
LocalDate nowDate = LocalDate.now();
|
||||
return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),
|
||||
LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在的月份的开始时间
|
||||
* 例如:2023-09-30 00:00:00,000
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 月份的开始时间
|
||||
*/
|
||||
public static LocalDateTime beginOfMonth(LocalDateTime date) {
|
||||
return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期所在的月份的最后时间
|
||||
* 例如:2023-09-30 23:59:59,999
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 月份的结束时间
|
||||
*/
|
||||
public static LocalDateTime endOfMonth(LocalDateTime date) {
|
||||
return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期所在季度
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 所在季度
|
||||
*/
|
||||
public static int getQuarterOfYear(LocalDateTime date) {
|
||||
return (date.getMonthValue() - 1) / 3 + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负
|
||||
*
|
||||
* @param dateTime 日期
|
||||
* @return 相差天数
|
||||
*/
|
||||
public static Long between(LocalDateTime dateTime) {
|
||||
return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今天的开始时间
|
||||
*
|
||||
* @return 今天
|
||||
*/
|
||||
public static LocalDateTime getToday() {
|
||||
return LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取昨天的开始时间
|
||||
*
|
||||
* @return 昨天
|
||||
*/
|
||||
public static LocalDateTime getYesterday() {
|
||||
return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月的开始时间
|
||||
*
|
||||
* @return 本月
|
||||
*/
|
||||
public static LocalDateTime getMonth() {
|
||||
return beginOfMonth(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本年的开始时间
|
||||
*
|
||||
* @return 本年
|
||||
*/
|
||||
public static LocalDateTime getYear() {
|
||||
return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
|
||||
}
|
||||
|
||||
public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
|
||||
LocalDateTime endTime,
|
||||
Integer interval) {
|
||||
// 1.1 找到枚举
|
||||
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
|
||||
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
|
||||
// 1.2 将时间对齐
|
||||
startTime = LocalDateTimeUtil.beginOfDay(startTime);
|
||||
endTime = LocalDateTimeUtil.endOfDay(endTime);
|
||||
|
||||
// 2. 循环,生成时间范围
|
||||
List<LocalDateTime[]> timeRanges = new ArrayList<>();
|
||||
switch (intervalEnum) {
|
||||
case DAY:
|
||||
while (startTime.isBefore(endTime)) {
|
||||
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
|
||||
startTime = startTime.plusDays(1);
|
||||
}
|
||||
break;
|
||||
case WEEK:
|
||||
while (startTime.isBefore(endTime)) {
|
||||
LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
|
||||
timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
|
||||
startTime = endOfWeek.plusNanos(1);
|
||||
}
|
||||
break;
|
||||
case MONTH:
|
||||
while (startTime.isBefore(endTime)) {
|
||||
LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
|
||||
timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
|
||||
startTime = endOfMonth.plusNanos(1);
|
||||
}
|
||||
break;
|
||||
case QUARTER:
|
||||
while (startTime.isBefore(endTime)) {
|
||||
int quarterOfYear = getQuarterOfYear(startTime);
|
||||
LocalDateTime quarterEnd = quarterOfYear == 4
|
||||
? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1)
|
||||
: startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1);
|
||||
timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});
|
||||
startTime = quarterEnd.plusNanos(1);
|
||||
}
|
||||
break;
|
||||
case YEAR:
|
||||
while (startTime.isBefore(endTime)) {
|
||||
LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
|
||||
timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
|
||||
startTime = endOfYear.plusNanos(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid interval: " + interval);
|
||||
}
|
||||
// 3. 兜底,最后一个时间,需要保持在 endTime 之前
|
||||
LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);
|
||||
if (lastTimeRange != null) {
|
||||
lastTimeRange[1] = endTime;
|
||||
}
|
||||
return timeRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间范围
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param interval 时间间隔
|
||||
* @return 时间范围
|
||||
*/
|
||||
public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {
|
||||
// 1. 找到枚举
|
||||
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
|
||||
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
|
||||
|
||||
// 2. 循环,生成时间范围
|
||||
switch (intervalEnum) {
|
||||
case DAY:
|
||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
|
||||
case WEEK:
|
||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
|
||||
+ StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
|
||||
case MONTH:
|
||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
|
||||
case QUARTER:
|
||||
return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
|
||||
case YEAR:
|
||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid interval: " + interval);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package cd.casic.framework.commons.util.http;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.map.TableMap;
|
||||
import cn.hutool.core.net.url.UrlBuilder;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* HTTP 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class HttpUtils {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static String replaceUrlQuery(String url, String key, String value) {
|
||||
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
|
||||
// 先移除
|
||||
TableMap<CharSequence, CharSequence> query = (TableMap<CharSequence, CharSequence>)
|
||||
ReflectUtil.getFieldValue(builder.getQuery(), "query");
|
||||
query.remove(key);
|
||||
// 后添加
|
||||
builder.addQuery(key, value);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private String append(String base, Map<String, ?> query, boolean fragment) {
|
||||
return append(base, query, null, fragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接 URL
|
||||
*
|
||||
* copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法
|
||||
*
|
||||
* @param base 基础 URL
|
||||
* @param query 查询参数
|
||||
* @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射
|
||||
* @param fragment URL 的 fragment,即拼接到 # 中
|
||||
* @return 拼接后的 URL
|
||||
*/
|
||||
public static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
|
||||
UriComponentsBuilder template = UriComponentsBuilder.newInstance();
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
|
||||
URI redirectUri;
|
||||
try {
|
||||
// assume it's encoded to start with (if it came in over the wire)
|
||||
redirectUri = builder.build(true).toUri();
|
||||
} catch (Exception e) {
|
||||
// ... but allow client registrations to contain hard-coded non-encoded values
|
||||
redirectUri = builder.build().toUri();
|
||||
builder = UriComponentsBuilder.fromUri(redirectUri);
|
||||
}
|
||||
template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
|
||||
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
|
||||
|
||||
if (fragment) {
|
||||
StringBuilder values = new StringBuilder();
|
||||
if (redirectUri.getFragment() != null) {
|
||||
String append = redirectUri.getFragment();
|
||||
values.append(append);
|
||||
}
|
||||
for (String key : query.keySet()) {
|
||||
if (values.length() > 0) {
|
||||
values.append("&");
|
||||
}
|
||||
String name = key;
|
||||
if (keys != null && keys.containsKey(key)) {
|
||||
name = keys.get(key);
|
||||
}
|
||||
values.append(name).append("={").append(key).append("}");
|
||||
}
|
||||
if (values.length() > 0) {
|
||||
template.fragment(values.toString());
|
||||
}
|
||||
UriComponents encoded = template.build().expand(query).encode();
|
||||
builder.fragment(encoded.getFragment());
|
||||
} else {
|
||||
for (String key : query.keySet()) {
|
||||
String name = key;
|
||||
if (keys != null && keys.containsKey(key)) {
|
||||
name = keys.get(key);
|
||||
}
|
||||
template.queryParam(name, "{" + key + "}");
|
||||
}
|
||||
template.fragment(redirectUri.getFragment());
|
||||
UriComponents encoded = template.build().expand(query).encode();
|
||||
builder.query(encoded.getQuery());
|
||||
}
|
||||
return builder.build().toUriString();
|
||||
}
|
||||
|
||||
public static String[] obtainBasicAuthorization(HttpServletRequest request) {
|
||||
String clientId;
|
||||
String clientSecret;
|
||||
// 先从 Header 中获取
|
||||
String authorization = request.getHeader("Authorization");
|
||||
authorization = StrUtil.subAfter(authorization, "Basic ", true);
|
||||
if (StringUtils.hasText(authorization)) {
|
||||
authorization = Base64.decodeStr(authorization);
|
||||
clientId = StrUtil.subBefore(authorization, ":", false);
|
||||
clientSecret = StrUtil.subAfter(authorization, ":", false);
|
||||
// 再从 Param 中获取
|
||||
} else {
|
||||
clientId = request.getParameter("client_id");
|
||||
clientSecret = request.getParameter("client_secret");
|
||||
}
|
||||
|
||||
// 如果两者非空,则返回
|
||||
if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) {
|
||||
return new String[]{clientId, clientSecret};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
|
||||
*
|
||||
* 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
|
||||
*
|
||||
* @param url URL
|
||||
* @param headers 请求头
|
||||
* @param requestBody 请求体
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static String post(String url, Map<String, String> headers, String requestBody) {
|
||||
try (HttpResponse response = HttpRequest.post(url)
|
||||
.addHeaders(headers)
|
||||
.body(requestBody)
|
||||
.execute()) {
|
||||
return response.body();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
|
||||
*
|
||||
* 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
|
||||
*
|
||||
* @param url URL
|
||||
* @param headers 请求头
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static String get(String url, Map<String, String> headers) {
|
||||
try (HttpResponse response = HttpRequest.get(url)
|
||||
.addHeaders(headers)
|
||||
.execute()) {
|
||||
return response.body();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package cd.casic.framework.commons.util.io;
|
||||
|
||||
import cn.hutool.core.io.FileTypeUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 文件工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* 创建临时文件
|
||||
* 该文件会在 JVM 退出时,进行删除
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @return 文件
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static File createTempFile(String data) {
|
||||
File file = createTempFile();
|
||||
// 写入内容
|
||||
FileUtil.writeUtf8String(data, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时文件
|
||||
* 该文件会在 JVM 退出时,进行删除
|
||||
*
|
||||
* @param data 文件内容
|
||||
* @return 文件
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static File createTempFile(byte[] data) {
|
||||
File file = createTempFile();
|
||||
// 写入内容
|
||||
FileUtil.writeBytes(data, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时文件,无内容
|
||||
* 该文件会在 JVM 退出时,进行删除
|
||||
*
|
||||
* @return 文件
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static File createTempFile() {
|
||||
// 创建文件,通过 UUID 保证唯一
|
||||
File file = File.createTempFile(IdUtil.simpleUUID(), null);
|
||||
// 标记 JVM 退出时,自动删除
|
||||
file.deleteOnExit();
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件路径
|
||||
*
|
||||
* @param content 文件内容
|
||||
* @param originalName 原始文件名
|
||||
* @return path,唯一不可重复
|
||||
*/
|
||||
public static String generatePath(byte[] content, String originalName) {
|
||||
String sha256Hex = DigestUtil.sha256Hex(content);
|
||||
// 情况一:如果存在 name,则优先使用 name 的后缀
|
||||
if (StrUtil.isNotBlank(originalName)) {
|
||||
String extName = FileNameUtil.extName(originalName);
|
||||
return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
|
||||
}
|
||||
// 情况二:基于 content 计算
|
||||
return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.framework.commons.util.io;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* IO 工具类,用于 {@link IoUtil} 缺失的方法
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class IoUtils {
|
||||
|
||||
/**
|
||||
* 从流中读取 UTF8 编码的内容
|
||||
*
|
||||
* @param in 输入流
|
||||
* @param isClose 是否关闭
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO 异常
|
||||
*/
|
||||
public static String readUtf8(InputStream in, boolean isClose) throws IORuntimeException {
|
||||
return StrUtil.utf8Str(IoUtil.read(in, isClose));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
package cd.casic.framework.commons.util.json;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JSON 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
@Slf4j
|
||||
public class JsonUtils {
|
||||
|
||||
private static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
|
||||
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 objectMapper 属性
|
||||
* <p>
|
||||
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
|
||||
*
|
||||
* @param objectMapper ObjectMapper 对象
|
||||
*/
|
||||
public static void init(ObjectMapper objectMapper) {
|
||||
JsonUtils.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String toJsonString(Object object) {
|
||||
return objectMapper.writeValueAsString(object);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static byte[] toJsonByte(Object object) {
|
||||
return objectMapper.writeValueAsBytes(object);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String toJsonPrettyString(Object object) {
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String text, Class<T> clazz) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, clazz);
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String text, String path, Class<T> clazz) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode treeNode = objectMapper.readTree(text);
|
||||
JsonNode pathNode = treeNode.path(path);
|
||||
return objectMapper.readValue(pathNode.toString(), clazz);
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String text, Type type) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串解析成指定类型的对象
|
||||
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
|
||||
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
|
||||
*
|
||||
* @param text 字符串
|
||||
* @param clazz 类型
|
||||
* @return 对象
|
||||
*/
|
||||
public static <T> T parseObject2(String text, Class<T> clazz) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
return JSONUtil.toBean(text, clazz);
|
||||
}
|
||||
|
||||
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
|
||||
if (ArrayUtil.isEmpty(bytes)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(bytes, clazz);
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", bytes, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
|
||||
try {
|
||||
return objectMapper.readValue(text, typeReference);
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
|
||||
*
|
||||
* @param text 字符串
|
||||
* @param typeReference 类型引用
|
||||
* @return 指定类型的对象
|
||||
*/
|
||||
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
|
||||
try {
|
||||
return objectMapper.readValue(text, typeReference);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> parseArray(String text, Class<T> clazz) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode treeNode = objectMapper.readTree(text);
|
||||
JsonNode pathNode = treeNode.path(path);
|
||||
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonNode parseTree(String text) {
|
||||
try {
|
||||
return objectMapper.readTree(text);
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonNode parseTree(byte[] text) {
|
||||
try {
|
||||
return objectMapper.readTree(text);
|
||||
} catch (IOException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isJson(String text) {
|
||||
return JSONUtil.isTypeJSON(text);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package cd.casic.framework.commons.util.json.databind;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Long 序列化规则
|
||||
*
|
||||
* 会将超长 long 值转换为 string,解决前端 JavaScript 最大安全整数是 2^53-1 的问题
|
||||
*
|
||||
* @author 星语
|
||||
*/
|
||||
@JacksonStdImpl
|
||||
public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer {
|
||||
|
||||
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
|
||||
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
|
||||
|
||||
public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class);
|
||||
|
||||
public NumberSerializer(Class<? extends Number> rawType) {
|
||||
super(rawType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
// 超出范围 序列化位字符串
|
||||
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
|
||||
super.serialize(value, gen, serializers);
|
||||
} else {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.framework.commons.util.json.databind;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* 基于时间戳的 LocalDateTime 反序列化器
|
||||
*
|
||||
* @author 老五
|
||||
*/
|
||||
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
|
||||
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
// 将 Long 时间戳,转换为 LocalDateTime 对象
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package cd.casic.framework.commons.util.json.databind;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* 基于时间戳的 LocalDateTime 序列化器
|
||||
*
|
||||
* @author 老五
|
||||
*/
|
||||
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
|
||||
|
||||
public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
// 将 LocalDateTime 对象,转换为 Long 时间戳
|
||||
gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package cd.casic.framework.commons.util.monitor;
|
||||
|
||||
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
|
||||
|
||||
/**
|
||||
* 链路追踪工具类
|
||||
*
|
||||
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class TracerUtils {
|
||||
|
||||
/**
|
||||
* 私有化构造方法
|
||||
*/
|
||||
private TracerUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得链路追踪编号,直接返回 SkyWalking 的 TraceId。
|
||||
* 如果不存在的话为空字符串!!!
|
||||
*
|
||||
* @return 链路追踪编号
|
||||
*/
|
||||
public static String getTraceId() {
|
||||
return TraceContext.traceId();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package cd.casic.framework.commons.util.number;
|
||||
|
||||
import cn.hutool.core.math.Money;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* 金额工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class MoneyUtils {
|
||||
|
||||
/**
|
||||
* 金额的小数位数
|
||||
*/
|
||||
private static final int PRICE_SCALE = 2;
|
||||
|
||||
/**
|
||||
* 百分比对应的 BigDecimal 对象
|
||||
*/
|
||||
public static final BigDecimal PERCENT_100 = BigDecimal.valueOf(100);
|
||||
|
||||
/**
|
||||
* 计算百分比金额,四舍五入
|
||||
*
|
||||
* @param price 金额
|
||||
* @param rate 百分比,例如说 56.77% 则传入 56.77
|
||||
* @return 百分比金额
|
||||
*/
|
||||
public static Integer calculateRatePrice(Integer price, Double rate) {
|
||||
return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分比金额,向下传入
|
||||
*
|
||||
* @param price 金额
|
||||
* @param rate 百分比,例如说 56.77% 则传入 56.77
|
||||
* @return 百分比金额
|
||||
*/
|
||||
public static Integer calculateRatePriceFloor(Integer price, Double rate) {
|
||||
return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分比金额
|
||||
*
|
||||
* @param price 金额(单位分)
|
||||
* @param count 数量
|
||||
* @param percent 折扣(单位分),列如 60.2%,则传入 6020
|
||||
* @return 商品总价
|
||||
*/
|
||||
public static Integer calculator(Integer price, Integer count, Integer percent) {
|
||||
price = price * count;
|
||||
if (percent == null) {
|
||||
return price;
|
||||
}
|
||||
return MoneyUtils.calculateRatePriceFloor(price, (double) (percent / 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分比金额
|
||||
*
|
||||
* @param price 金额
|
||||
* @param rate 百分比,例如说 56.77% 则传入 56.77
|
||||
* @param scale 保留小数位数
|
||||
* @param roundingMode 舍入模式
|
||||
*/
|
||||
public static BigDecimal calculateRatePrice(Number price, Number rate, int scale, RoundingMode roundingMode) {
|
||||
return NumberUtil.toBigDecimal(price).multiply(NumberUtil.toBigDecimal(rate)) // 乘以
|
||||
.divide(BigDecimal.valueOf(100), scale, roundingMode); // 除以 100
|
||||
}
|
||||
|
||||
/**
|
||||
* 分转元
|
||||
*
|
||||
* @param fen 分
|
||||
* @return 元
|
||||
*/
|
||||
public static BigDecimal fenToYuan(int fen) {
|
||||
return new Money(0, fen).getAmount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分转元(字符串)
|
||||
*
|
||||
* 例如说 fen 为 1 时,则结果为 0.01
|
||||
*
|
||||
* @param fen 分
|
||||
* @return 元
|
||||
*/
|
||||
public static String fenToYuanStr(int fen) {
|
||||
return new Money(0, fen).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额相乘,默认进行四舍五入
|
||||
*
|
||||
* 位数:{@link #PRICE_SCALE}
|
||||
*
|
||||
* @param price 金额
|
||||
* @param count 数量
|
||||
* @return 金额相乘结果
|
||||
*/
|
||||
public static BigDecimal priceMultiply(BigDecimal price, BigDecimal count) {
|
||||
if (price == null || count == null) {
|
||||
return null;
|
||||
}
|
||||
return price.multiply(count).setScale(PRICE_SCALE, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额相乘(百分比),默认进行四舍五入
|
||||
*
|
||||
* 位数:{@link #PRICE_SCALE}
|
||||
*
|
||||
* @param price 金额
|
||||
* @param percent 百分比
|
||||
* @return 金额相乘结果
|
||||
*/
|
||||
public static BigDecimal priceMultiplyPercent(BigDecimal price, BigDecimal percent) {
|
||||
if (price == null || percent == null) {
|
||||
return null;
|
||||
}
|
||||
return price.multiply(percent).divide(PERCENT_100, PRICE_SCALE, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package cd.casic.framework.commons.util.number;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 数字的工具类,补全 {@link NumberUtil} 的功能
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class NumberUtils {
|
||||
|
||||
public static Long parseLong(String str) {
|
||||
return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null;
|
||||
}
|
||||
|
||||
public static Integer parseInt(String str) {
|
||||
return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过经纬度获取地球上两点之间的距离
|
||||
*
|
||||
* 参考 <<a href="https://gitee.com/dromara/hutool/blob/1caabb586b1f95aec66a21d039c5695df5e0f4c1/hutool-core/src/main/java/cn/hutool/core/util/DistanceUtil.java">DistanceUtil</a>> 实现,目前它已经被 hutool 删除
|
||||
*
|
||||
* @param lat1 经度1
|
||||
* @param lng1 纬度1
|
||||
* @param lat2 经度2
|
||||
* @param lng2 纬度2
|
||||
* @return 距离,单位:千米
|
||||
*/
|
||||
public static double getDistance(double lat1, double lng1, double lat2, double lng2) {
|
||||
double radLat1 = lat1 * Math.PI / 180.0;
|
||||
double radLat2 = lat2 * Math.PI / 180.0;
|
||||
double a = radLat1 - radLat2;
|
||||
double b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
|
||||
double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
|
||||
+ Math.cos(radLat1) * Math.cos(radLat2)
|
||||
* Math.pow(Math.sin(b / 2), 2)));
|
||||
distance = distance * 6378.137;
|
||||
distance = Math.round(distance * 10000d) / 10000d;
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供精确的乘法运算
|
||||
*
|
||||
* 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null
|
||||
*
|
||||
* @param values 多个被乘值
|
||||
* @return 积
|
||||
*/
|
||||
public static BigDecimal mul(BigDecimal... values) {
|
||||
for (BigDecimal value : values) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return NumberUtil.mul(values);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package cd.casic.framework.commons.util.object;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.commons.util.collection.CollectionUtils;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Bean 工具类
|
||||
*
|
||||
* 1. 默认使用 {@link BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
|
||||
* 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class BeanUtils {
|
||||
|
||||
public static <T> T toBean(Object source, Class<T> targetClass) {
|
||||
return BeanUtil.toBean(source, targetClass);
|
||||
}
|
||||
|
||||
public static <T> T toBean(Object source, Class<T> targetClass, Consumer<T> peek) {
|
||||
T target = toBean(source, targetClass);
|
||||
if (target != null) {
|
||||
peek.accept(target);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return CollectionUtils.convertList(source, s -> toBean(s, targetType));
|
||||
}
|
||||
|
||||
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType, Consumer<T> peek) {
|
||||
List<T> list = toBean(source, targetType);
|
||||
if (list != null) {
|
||||
list.forEach(peek);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {
|
||||
return toBean(source, targetType, null);
|
||||
}
|
||||
|
||||
public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType, Consumer<T> peek) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
List<T> list = toBean(source.getList(), targetType);
|
||||
if (peek != null) {
|
||||
list.forEach(peek);
|
||||
}
|
||||
return new PageResult<>(list, source.getTotal());
|
||||
}
|
||||
|
||||
public static void copyProperties(Object source, Object target) {
|
||||
if (source == null || target == null) {
|
||||
return;
|
||||
}
|
||||
BeanUtil.copyProperties(source, target, false);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package cd.casic.framework.commons.util.object;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Object 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class ObjectUtils {
|
||||
|
||||
/**
|
||||
* 复制对象,并忽略 Id 编号
|
||||
*
|
||||
* @param object 被复制对象
|
||||
* @param consumer 消费者,可以二次编辑被复制对象
|
||||
* @return 复制后的对象
|
||||
*/
|
||||
public static <T> T cloneIgnoreId(T object, Consumer<T> consumer) {
|
||||
T result = ObjectUtil.clone(object);
|
||||
// 忽略 id 编号
|
||||
Field field = ReflectUtil.getField(object.getClass(), "id");
|
||||
if (field != null) {
|
||||
ReflectUtil.setFieldValue(result, field, null);
|
||||
}
|
||||
// 二次编辑
|
||||
if (result != null) {
|
||||
consumer.accept(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T extends Comparable<T>> T max(T obj1, T obj2) {
|
||||
if (obj1 == null) {
|
||||
return obj2;
|
||||
}
|
||||
if (obj2 == null) {
|
||||
return obj1;
|
||||
}
|
||||
return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> T defaultIfNull(T... array) {
|
||||
for (T item : array) {
|
||||
if (item != null) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> boolean equalsAny(T obj, T... array) {
|
||||
return Arrays.asList(array).contains(obj);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package cd.casic.framework.commons.util.object;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageParam;
|
||||
import cd.casic.framework.commons.pojo.SortablePageParam;
|
||||
import cd.casic.framework.commons.pojo.SortingField;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.lang.func.LambdaUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* {@link PageParam} 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class PageUtils {
|
||||
|
||||
private static final Object[] ORDER_TYPES = new String[]{SortingField.ORDER_ASC, SortingField.ORDER_DESC};
|
||||
|
||||
public static int getStart(PageParam pageParam) {
|
||||
return (pageParam.getPageNo() - 1) * pageParam.getPageSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建排序字段(默认倒序)
|
||||
*
|
||||
* @param func 排序字段的 Lambda 表达式
|
||||
* @param <T> 排序字段所属的类型
|
||||
* @return 排序字段
|
||||
*/
|
||||
public static <T> SortingField buildSortingField(Func1<T, ?> func) {
|
||||
return buildSortingField(func, SortingField.ORDER_DESC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建排序字段
|
||||
*
|
||||
* @param func 排序字段的 Lambda 表达式
|
||||
* @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC}
|
||||
* @param <T> 排序字段所属的类型
|
||||
* @return 排序字段
|
||||
*/
|
||||
public static <T> SortingField buildSortingField(Func1<T, ?> func, String order) {
|
||||
Assert.isTrue(ArrayUtil.contains(ORDER_TYPES, order), String.format("字段的排序类型只能是 %s/%s", ORDER_TYPES));
|
||||
|
||||
String fieldName = LambdaUtil.getFieldName(func);
|
||||
return new SortingField(fieldName, order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建默认的排序字段
|
||||
* 如果排序字段为空,则设置排序字段;否则忽略
|
||||
*
|
||||
* @param sortablePageParam 排序分页查询参数
|
||||
* @param func 排序字段的 Lambda 表达式
|
||||
* @param <T> 排序字段所属的类型
|
||||
*/
|
||||
public static <T> void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1<T, ?> func) {
|
||||
if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) {
|
||||
sortablePageParam.setSortingFields(singletonList(buildSortingField(func)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package cd.casic.framework.commons.util.servlet;
|
||||
|
||||
import cd.casic.framework.commons.util.json.JsonUtils;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 客户端工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class ServletUtils {
|
||||
|
||||
/**
|
||||
* 返回 JSON 字符串
|
||||
*
|
||||
* @param response 响应
|
||||
* @param object 对象,会序列化成 JSON 字符串
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
||||
public static void writeJSON(HttpServletResponse response, Object object) {
|
||||
String content = JsonUtils.toJsonString(object);
|
||||
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request 请求
|
||||
* @return ua
|
||||
*/
|
||||
public static String getUserAgent(HttpServletRequest request) {
|
||||
String ua = request.getHeader("User-Agent");
|
||||
return ua != null ? ua : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求
|
||||
*
|
||||
* @return HttpServletRequest
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
if (!(requestAttributes instanceof ServletRequestAttributes)) {
|
||||
return null;
|
||||
}
|
||||
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
}
|
||||
|
||||
public static String getUserAgent() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return getUserAgent(request);
|
||||
}
|
||||
|
||||
public static String getClientIP() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
return JakartaServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
public static boolean isJsonRequest(ServletRequest request) {
|
||||
return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
|
||||
}
|
||||
|
||||
public static String getBody(HttpServletRequest request) {
|
||||
// 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
|
||||
if (isJsonRequest(request)) {
|
||||
return JakartaServletUtil.getBody(request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] getBodyBytes(HttpServletRequest request) {
|
||||
// 只有在 json 请求在读取,因为只有 CacheRequestBodyFilter 才会进行缓存,支持重复读取
|
||||
if (isJsonRequest(request)) {
|
||||
return JakartaServletUtil.getBodyBytes(request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getClientIP(HttpServletRequest request) {
|
||||
return JakartaServletUtil.getClientIP(request);
|
||||
}
|
||||
|
||||
public static Map<String, String> getParamMap(HttpServletRequest request) {
|
||||
return JakartaServletUtil.getParamMap(request);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package cd.casic.framework.commons.util.spring;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Spring EL 表达式的工具类
|
||||
*
|
||||
* @author mashu
|
||||
*/
|
||||
public class SpringExpressionUtils {
|
||||
|
||||
/**
|
||||
* Spring EL 表达式解析器
|
||||
*/
|
||||
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
/**
|
||||
* 参数名发现器
|
||||
*/
|
||||
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||
|
||||
private SpringExpressionUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从切面中,单个解析 EL 表达式的结果
|
||||
*
|
||||
* @param joinPoint 切面点
|
||||
* @param expressionString EL 表达式数组
|
||||
* @return 执行界面
|
||||
*/
|
||||
public static Object parseExpression(JoinPoint joinPoint, String expressionString) {
|
||||
Map<String, Object> result = parseExpressions(joinPoint, Collections.singletonList(expressionString));
|
||||
return result.get(expressionString);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从切面中,批量解析 EL 表达式的结果
|
||||
*
|
||||
* @param joinPoint 切面点
|
||||
* @param expressionStrings EL 表达式数组
|
||||
* @return 结果,key 为表达式,value 为对应值
|
||||
*/
|
||||
public static Map<String, Object> parseExpressions(JoinPoint joinPoint, List<String> expressionStrings) {
|
||||
// 如果为空,则不进行解析
|
||||
if (CollUtil.isEmpty(expressionStrings)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
|
||||
// 第一步,构建解析的上下文 EvaluationContext
|
||||
// 通过 joinPoint 获取被注解方法
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = methodSignature.getMethod();
|
||||
// 使用 spring 的 ParameterNameDiscoverer 获取方法形参名数组
|
||||
String[] paramNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
|
||||
// Spring 的表达式上下文对象
|
||||
EvaluationContext context = new StandardEvaluationContext();
|
||||
// 给上下文赋值
|
||||
if (ArrayUtil.isNotEmpty(paramNames)) {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
for (int i = 0; i < paramNames.length; i++) {
|
||||
context.setVariable(paramNames[i], args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 第二步,逐个参数解析
|
||||
Map<String, Object> result = MapUtil.newHashMap(expressionStrings.size(), true);
|
||||
expressionStrings.forEach(key -> {
|
||||
Object value = EXPRESSION_PARSER.parseExpression(key).getValue(context);
|
||||
result.put(key, value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Bean 工厂,解析 EL 表达式的结果
|
||||
*
|
||||
* @param expressionString EL 表达式
|
||||
* @return 执行界面
|
||||
*/
|
||||
public static Object parseExpression(String expressionString) {
|
||||
if (StrUtil.isBlank(expressionString)) {
|
||||
return null;
|
||||
}
|
||||
Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext()));
|
||||
return expression.getValue(context);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.framework.commons.util.spring;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Spring 工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class SpringUtils extends SpringUtil {
|
||||
|
||||
/**
|
||||
* 是否为生产环境
|
||||
*
|
||||
* @return 是否生产环境
|
||||
*/
|
||||
public static boolean isProd() {
|
||||
String activeProfile = getActiveProfile();
|
||||
return Objects.equals("prod", activeProfile);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package cd.casic.framework.commons.util.string;
|
||||
|
||||
import cn.hutool.core.text.StrPool;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 字符串工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class StrUtils {
|
||||
|
||||
public static String maxLength(CharSequence str, int maxLength) {
|
||||
return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否以任何一个字符串开始
|
||||
* 给定字符串和数组为空都返回 false
|
||||
*
|
||||
* @param str 给定字符串
|
||||
* @param prefixes 需要检测的开始字符串
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static boolean startWithAny(String str, Collection<String> prefixes) {
|
||||
if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CharSequence suffix : prefixes) {
|
||||
if (StrUtil.startWith(str, suffix, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<Long> splitToLong(String value, CharSequence separator) {
|
||||
long[] longs = StrUtil.splitToLong(value, separator);
|
||||
return Arrays.stream(longs).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static Set<Long> splitToLongSet(String value) {
|
||||
return splitToLongSet(value, StrPool.COMMA);
|
||||
}
|
||||
|
||||
public static Set<Long> splitToLongSet(String value, CharSequence separator) {
|
||||
long[] longs = StrUtil.splitToLong(value, separator);
|
||||
return Arrays.stream(longs).boxed().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static List<Integer> splitToInteger(String value, CharSequence separator) {
|
||||
int[] integers = StrUtil.splitToInt(value, separator);
|
||||
return Arrays.stream(integers).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除字符串中,包含指定字符串的行
|
||||
*
|
||||
* @param content 字符串
|
||||
* @param sequence 包含的字符串
|
||||
* @return 移除后的字符串
|
||||
*/
|
||||
public static String removeLineContains(String content, String sequence) {
|
||||
if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
|
||||
return content;
|
||||
}
|
||||
return Arrays.stream(content.split("\n"))
|
||||
.filter(line -> !line.contains(sequence))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package cd.casic.framework.commons.util.validation;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 校验工具类
|
||||
*
|
||||
* @author mianbin
|
||||
*/
|
||||
public class ValidationUtils {
|
||||
|
||||
private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[0-3,5-9]))\\d{8}$");
|
||||
|
||||
private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
|
||||
|
||||
private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*");
|
||||
|
||||
public static boolean isMobile(String mobile) {
|
||||
return StringUtils.hasText(mobile)
|
||||
&& PATTERN_MOBILE.matcher(mobile).matches();
|
||||
}
|
||||
|
||||
public static boolean isURL(String url) {
|
||||
return StringUtils.hasText(url)
|
||||
&& PATTERN_URL.matcher(url).matches();
|
||||
}
|
||||
|
||||
public static boolean isXmlNCName(String str) {
|
||||
return StringUtils.hasText(str)
|
||||
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static void validate(Object object, Class<?>... groups) {
|
||||
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
Assert.notNull(validator);
|
||||
validate(validator, object, groups);
|
||||
}
|
||||
|
||||
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||
if (CollUtil.isNotEmpty(constraintViolations)) {
|
||||
throw new ConstraintViolationException(constraintViolations);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
ElementType.FIELD,
|
||||
ElementType.ANNOTATION_TYPE,
|
||||
ElementType.CONSTRUCTOR,
|
||||
ElementType.PARAMETER,
|
||||
ElementType.TYPE_USE
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Constraint(
|
||||
validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}
|
||||
)
|
||||
public @interface InEnum {
|
||||
|
||||
/**
|
||||
* @return 实现 EnumValuable 接口的
|
||||
*/
|
||||
Class<? extends IntArrayValuable> value();
|
||||
|
||||
String message() default "必须在指定范围 {value}";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<Integer>> {
|
||||
|
||||
private List<Integer> values;
|
||||
|
||||
@Override
|
||||
public void initialize(InEnum annotation) {
|
||||
IntArrayValuable[] values = annotation.value().getEnumConstants();
|
||||
if (values.length == 0) {
|
||||
this.values = Collections.emptyList();
|
||||
} else {
|
||||
this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Collection<Integer> list, ConstraintValidatorContext context) {
|
||||
// 校验通过
|
||||
if (CollUtil.containsAll(values, list)) {
|
||||
return true;
|
||||
}
|
||||
// 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
|
||||
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
|
||||
.replaceAll("\\{value}", CollUtil.join(list, ","))).addConstraintViolation(); // 重新添加错误提示语句
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InEnumValidator implements ConstraintValidator<InEnum, Integer> {
|
||||
|
||||
private List<Integer> values;
|
||||
|
||||
@Override
|
||||
public void initialize(InEnum annotation) {
|
||||
IntArrayValuable[] values = annotation.value().getEnumConstants();
|
||||
if (values.length == 0) {
|
||||
this.values = Collections.emptyList();
|
||||
} else {
|
||||
this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(Integer value, ConstraintValidatorContext context) {
|
||||
// 为空时,默认不校验,即认为通过
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
// 校验通过
|
||||
if (values.contains(value)) {
|
||||
return true;
|
||||
}
|
||||
// 校验不通过,自定义提示语句(因为,注解上的 value 是枚举类,无法获得枚举类的实际值)
|
||||
context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
|
||||
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()
|
||||
.replaceAll("\\{value}", values.toString())).addConstraintViolation(); // 重新添加错误提示语句
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
ElementType.FIELD,
|
||||
ElementType.ANNOTATION_TYPE,
|
||||
ElementType.CONSTRUCTOR,
|
||||
ElementType.PARAMETER,
|
||||
ElementType.TYPE_USE
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Constraint(
|
||||
validatedBy = MobileValidator.class
|
||||
)
|
||||
public @interface Mobile {
|
||||
|
||||
String message() default "手机号格式不正确";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import cd.casic.framework.commons.util.validation.ValidationUtils;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
public class MobileValidator implements ConstraintValidator<Mobile, String> {
|
||||
|
||||
@Override
|
||||
public void initialize(Mobile annotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
// 如果手机号为空,默认不校验,即校验通过
|
||||
if (StrUtil.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
// 校验手机
|
||||
return ValidationUtils.isMobile(value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({
|
||||
ElementType.METHOD,
|
||||
ElementType.FIELD,
|
||||
ElementType.ANNOTATION_TYPE,
|
||||
ElementType.CONSTRUCTOR,
|
||||
ElementType.PARAMETER,
|
||||
ElementType.TYPE_USE
|
||||
})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Constraint(
|
||||
validatedBy = TelephoneValidator.class
|
||||
)
|
||||
public @interface Telephone {
|
||||
|
||||
String message() default "电话格式不正确";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package cd.casic.framework.commons.validation;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.PhoneUtil;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
|
||||
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
|
||||
|
||||
@Override
|
||||
public void initialize(Telephone annotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
// 如果手机号为空,默认不校验,即校验通过
|
||||
if (CharSequenceUtil.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
// 校验手机
|
||||
return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package collection;
|
||||
|
||||
import cd.casic.framework.commons.util.collection.CollectionUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/**
|
||||
* {@link CollectionUtils} 的单元测试
|
||||
*/
|
||||
public class CollectionUtilsTest {
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
private static class Dog {
|
||||
|
||||
private Integer id;
|
||||
private String name;
|
||||
private String code;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiffList() {
|
||||
// 准备参数
|
||||
Collection<Dog> oldList = Arrays.asList(
|
||||
new Dog(1, "花花", "hh"),
|
||||
new Dog(2, "旺财", "wc")
|
||||
);
|
||||
Collection<Dog> newList = Arrays.asList(
|
||||
new Dog(null, "花花2", "hh"),
|
||||
new Dog(null, "小白", "xb")
|
||||
);
|
||||
BiFunction<Dog, Dog, Boolean> sameFunc = (oldObj, newObj) -> {
|
||||
boolean same = oldObj.getCode().equals(newObj.getCode());
|
||||
// 如果相等的情况下,需要设置下 id,后续好更新
|
||||
if (same) {
|
||||
newObj.setId(oldObj.getId());
|
||||
}
|
||||
return same;
|
||||
};
|
||||
|
||||
// 调用
|
||||
List<List<Dog>> result = CollectionUtils.diffList(oldList, newList, sameFunc);
|
||||
// 断言
|
||||
assertEquals(result.size(), 3);
|
||||
// 断言 create
|
||||
assertEquals(result.get(0).size(), 1);
|
||||
assertEquals(result.get(0).get(0), new Dog(null, "小白", "xb"));
|
||||
// 断言 update
|
||||
assertEquals(result.get(1).size(), 1);
|
||||
assertEquals(result.get(1).get(0), new Dog(1, "花花2", "hh"));
|
||||
// 断言 delete
|
||||
assertEquals(result.get(2).size(), 1);
|
||||
assertEquals(result.get(2).get(0), new Dog(2, "旺财", "wc"));
|
||||
}
|
||||
|
||||
}
|
@ -9,6 +9,10 @@
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>commons</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>framework</artifactId>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user