Compare commits

...

3 Commits

Author SHA1 Message Date
Jiaju Zhuang 7409c58d51 新增demo 2024-07-03 21:41:00 +08:00
Jiaju Zhuang 51469ed376 异常 2024-07-03 21:14:28 +08:00
Jiaju Zhuang 4f55cb5f2e easytools 2024-07-02 21:00:57 +08:00
77 changed files with 7289 additions and 58 deletions

View File

@ -0,0 +1,50 @@
<?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>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>easytools-base</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastvalidator</groupId>
<artifactId>fastvalidator-constraints</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 不需要打包lombok -->
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,36 @@
package com.alibaba.easytools.base.constant;
/**
* 常量
*
* @author 是仪
*/
public interface EasyToolsConstant {
/**
* 最大分页大小
*/
int MAX_PAGE_SIZE = 500;
/**
* 序列化id
*/
long SERIAL_VERSION_UID = 1L;
/**
* 最大循环次数 防止很多循环进入死循环
*/
int MAXIMUM_ITERATIONS = 10 * 1000;
/**
* 缓存占位符
* 有些缓存只要判断有没有可以放入这个
*/
String CACHE_PLACEHOLDER = "C";
/**
* 鹰眼追踪id
*/
String EAGLEEYE_TRACE_ID = "EAGLEEYE_TRACE_ID";
}

View File

@ -0,0 +1,58 @@
package com.alibaba.easytools.base.constant;
/**
* 常见符号的常量枚举
*
* @author 是仪
**/
public class SymbolConstant {
/**
* +
*/
public static final String PLUS = "+";
/**
* -
*/
public static final String MINUS = "-";
/**
* *
*/
public static final String ASTERISK = "*";
/**
* /
*/
public static final String SLASH = "/";
/**
* 单引号"'"
*/
public static final String SQUOT = "'";
/**
* 空串 ""
*/
public static final String EMPTY = "";
/**
* 分隔符 "-"
*/
public static final String SEPARATOR = "-";
/**
* 等号 "="
*/
public static final String EQ = "=";
/**
* 分号 ";"
*/
public static final String SEMICOLON = ";";
/**
* 逗号 ","
*/
public static final String COMMA = ",";
/**
* "."
*/
public static final String DOT = ".";
/**
* 冒号 ":"
*/
public static final String COLON = ":";
}

View File

@ -0,0 +1,56 @@
package com.alibaba.easytools.base.constraints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import com.alibaba.easytools.base.constraints.validator.IsEnumValidator;
import com.alibaba.easytools.base.enums.BaseEnum;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 判断一个对象是否是{@link BaseEnum} 里面具体的值
*
* @author 是仪
*/
@Documented
@Constraint(validatedBy = {IsEnumValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
public @interface IsEnum {
/**
* 需要校验的枚举类
*
* @return 枚举类
*/
Class<? extends BaseEnum<?>> value();
String message() default "必须是枚举:{value} 中的值";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* Defines several {@code @IsEnum} annotations on the same element.
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface List {
IsEnum[] value();
}
}

View File

@ -0,0 +1,40 @@
package com.alibaba.easytools.base.constraints.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.alibaba.easytools.base.constraints.IsEnum;
import com.alibaba.easytools.base.enums.BaseEnum;
/**
* 枚举的校验器
*
* @author 是仪
*/
public class IsEnumValidator implements ConstraintValidator<IsEnum, String> {
private Class<? extends BaseEnum<?>> enumType;
@Override
public void initialize(IsEnum constraintAnnotation) {
enumType = constraintAnnotation.value();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
BaseEnum<?>[] baseEnums = enumType.getEnumConstants();
if (baseEnums == null) {
return false;
}
for (BaseEnum<?> baseEnum : baseEnums) {
if (baseEnum.getCode().equals(value)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,26 @@
package com.alibaba.easytools.base.enums;
/**
* 基础的枚举
*
* 由于java枚举继承的限制枚举基类只能设计为接口请自行保证子类一定是枚举类型
*
* @author Jiaju Zhuang
**/
public interface BaseEnum<T> {
/**
* 返回枚举的code一般建议直接返回枚举的name
*
* @return code
*/
T getCode();
/**
* 返回枚举的描述返回枚举的中文 方便前端下拉
*
* @return description
*/
String getDescription();
}

View File

@ -0,0 +1,8 @@
package com.alibaba.easytools.base.enums;
/**
* 基础的异常枚举
*
* @author 是仪
**/
public interface BaseErrorEnum extends BaseEnum<String> {}

View File

@ -0,0 +1,40 @@
package com.alibaba.easytools.base.enums;
import lombok.Getter;
/**
* 删除标记枚举
* <p>
* 为了兼容唯一主键+逻辑删除使用DeletedId来标记当前数据是否删除如果是0 则代表未删除其他任何情况都代表已经删除
* 删除的时候 执行语句update set deleted_id = di where 条件=条件
*
* @author 是仪
*/
@Getter
public enum DeletedIdEnum implements BaseEnum<Long> {
/**
* 未删除
*/
NOT_DELETED(0L, "未删除"),
;
final Long code;
final String description;
DeletedIdEnum(Long code, String description) {
this.code = code;
this.description = description;
}
/**
* 判断 当前数据是否已经逻辑删除
*
* @param deletedId 表中的deleted_id
* @return 是否已经删除
*/
public static boolean isDeleted(Long deletedId) {
return !NOT_DELETED.getCode().equals(deletedId);
}
}

View File

@ -0,0 +1,39 @@
package com.alibaba.easytools.base.enums;
import lombok.Getter;
/**
* 操作枚举
*
* @author 是仪
*/
@Getter
public enum OperationEnum implements BaseEnum<String> {
/**
* 新增
*/
CREATE("新增"),
/**
* 修改
*/
UPDATE("修改"),
/**
* 删除
*/
DELETE("删除"),
;
final String description;
OperationEnum(String description) {
this.description = description;
}
@Override
public String getCode() {
return this.name();
}
}

View File

@ -0,0 +1,35 @@
package com.alibaba.easytools.base.enums;
/**
* 排序方向的枚举
*
* @author 是仪
*/
public enum OrderByDirectionEnum implements BaseEnum<String> {
/**
* 升序
*/
ASC,
/**
* 降序
*/
DESC,
/**
* 智能排序
* 需要人工处理的排序 也就是先不指定到下一层去解析后处理
*/
SMART,
;
@Override
public String getCode() {
return this.name();
}
@Override
public String getDescription() {
return this.name();
}
}

View File

@ -0,0 +1,53 @@
package com.alibaba.easytools.base.enums;
import lombok.Getter;
/**
* 是否枚举
*
* @author 是仪
*/
@Getter
public enum YesOrNoEnum implements BaseEnum<String> {
/**
*
*/
YES("", true),
/**
* 未读
*/
NO("", false),
;
final String description;
final boolean booleanValue;
YesOrNoEnum(String description, boolean booleanValue) {
this.description = description;
this.booleanValue = booleanValue;
}
@Override
public String getCode() {
return this.name();
}
/**
* 根据布尔值转换
*
* @param booleanValue 布尔值
* @return
*/
public static YesOrNoEnum valueOf(Boolean booleanValue) {
if (booleanValue == null) {
return null;
}
if (booleanValue) {
return YesOrNoEnum.YES;
}
return YesOrNoEnum.NO;
}
}

View File

@ -0,0 +1,26 @@
package com.alibaba.easytools.base.enums.oss;
import com.alibaba.easytools.base.enums.BaseEnum;
/**
* oss枚举
*
* @author 是仪
*/
public interface BaseOssKindEnum extends BaseEnum<String> {
/**
* 获取权限控制
*
* @return
*/
OssObjectAclEnum getOssObjectAcl();
/**
* 样式处理
*
* @return
*/
String getProcess();
}

View File

@ -0,0 +1,41 @@
package com.alibaba.easytools.base.enums.oss;
import com.alibaba.easytools.base.enums.BaseEnum;
import lombok.Getter;
/**
* oss权限
*
* @author 是仪
*/
@Getter
public enum OssObjectAclEnum implements BaseEnum<String> {
/**
* 共有读
*/
PUBLIC_READ("public-read"),
/**
* 私有
*/
PRIVATE("private"),
// 分号
;
final String ossAcl;
OssObjectAclEnum(String ossAcl) {
this.ossAcl = ossAcl;
}
@Override
public String getCode() {
return this.name();
}
@Override
public String getDescription() {
return this.name();
}
}

View File

@ -0,0 +1,64 @@
package com.alibaba.easytools.base.excption;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import lombok.Data;
/**
* 业务异常不需要人工介入的叫做业务异常
*
* @author 是仪
*/
@Data
public class BusinessException extends RuntimeException {
/**
* 异常的编码
*/
private String code;
public BusinessException(String message) {
this(CommonErrorEnum.COMMON_BUSINESS_ERROR, message);
}
public BusinessException(String message, Throwable throwable) {
this(CommonErrorEnum.COMMON_BUSINESS_ERROR, message, throwable);
}
public BusinessException(BaseErrorEnum errorEnum, String message) {
this(errorEnum.getCode(), message);
}
public BusinessException(BaseErrorEnum errorEnum, String message, Throwable throwable) {
super(message, throwable);
this.code = errorEnum.getCode();
}
public BusinessException(BaseErrorEnum errorEnum) {
this(errorEnum.getCode(), errorEnum.getDescription());
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public static BusinessException of(String message) {
return new BusinessException(message);
}
public static BusinessException of(String code, String message) {
return new BusinessException(code, message);
}
public static BusinessException of(BaseErrorEnum errorEnum, String message, Throwable throwable) {
return new BusinessException(errorEnum, message, throwable);
}
public static BusinessException of(BaseErrorEnum errorEnum) {
return new BusinessException(errorEnum);
}
public static BusinessException of(BaseErrorEnum errorEnum, String message) {
return new BusinessException(errorEnum, message);
}
}

View File

@ -0,0 +1,85 @@
package com.alibaba.easytools.base.excption;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import lombok.Getter;
/**
* 通用的返回码定义
*
* @author 是仪
*/
@Getter
public enum CommonErrorEnum implements BaseErrorEnum {
/**
* 通用业务异常
*/
COMMON_BUSINESS_ERROR("填写的信息异常,请尝试刷新页面"),
/**
* 通用系统异常
*/
COMMON_SYSTEM_ERROR("系统开小差啦,请尝试刷新页面或者联系管理员"),
/**
* 通用系统异常
*/
PARAM_ERROR("参数错误"),
/**
* 找不到数据
*/
DATA_NOT_FOUND("找不到对应数据"),
/**
* 没有权限
*/
PERMISSION_DENIED("权限不够"),
/**
* 超过最大上传
*/
MAX_UPLOAD_SIZE("上传的文件超过最大限制"),
/**
* 需要登陆
* 前端需要跳转到登陆界面
*/
NEED_LOGGED_IN("未登录,请重新登陆"),
/**
* 没有登录
* 代表用户没有登陆不需要跳转展示为空即可
*/
NOT_LOGGED_IN("无法获取登陆信息,请尝试刷新页面或者重新登陆"),
/**
* 超过访问限制
*/
ACCESS_LIMIT_EXCEEDED("超过访问限制"),
/**
* 找不到指定页面
*/
PAGE_NOT_FOUND("找不到指定页面"),
/**
* 上传文件失败
*/
FAILED_TO_UPLOAD_FILE("上传文件失败"),
/**
* metaq重推专用异常
*/
METAQ_RECONSUME_LATER("metaq重推专用异常"),
;
CommonErrorEnum(String description) {
this.description = description;
}
final String description;
@Override
public String getCode() {
return this.name();
}
}

View File

@ -0,0 +1,30 @@
package com.alibaba.easytools.base.excption;
import lombok.Getter;
/**
* mq重推专用异常
*
* @author 是仪
*/
@Getter
public class MetaqReconsumeLaterBusinessException extends BusinessException {
public MetaqReconsumeLaterBusinessException() {
super(CommonErrorEnum.METAQ_RECONSUME_LATER, "mq消息重试");
}
public MetaqReconsumeLaterBusinessException(String message) {
super(CommonErrorEnum.METAQ_RECONSUME_LATER, message);
}
public static MetaqReconsumeLaterBusinessException of(String message) {
return new MetaqReconsumeLaterBusinessException(message);
}
public static MetaqReconsumeLaterBusinessException newInstance() {
return new MetaqReconsumeLaterBusinessException();
}
}

View File

@ -0,0 +1,66 @@
package com.alibaba.easytools.base.excption;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import lombok.Data;
/**
* 业务异常简单的说就是需要人工介入的异常叫做系统异常
*
* @author 是仪
*/
@Data
public class SystemException extends RuntimeException {
/**
* 异常的编码
*/
private String code;
public SystemException(String message) {
this(CommonErrorEnum.COMMON_SYSTEM_ERROR, message);
}
public SystemException(String message, Throwable throwable) {
this(CommonErrorEnum.COMMON_SYSTEM_ERROR, message, throwable);
}
public SystemException(String code, String message) {
super(message);
this.code = code;
}
public SystemException(BaseErrorEnum errorEnum, String message, Throwable throwable) {
super(message, throwable);
this.code = errorEnum.getCode();
}
public SystemException(BaseErrorEnum errorEnum) {
this(errorEnum.getCode(), errorEnum.getDescription());
}
public SystemException(BaseErrorEnum errorEnum, String message) {
this(errorEnum.getCode(), message);
}
public static SystemException of(String message) {
return new SystemException(message);
}
public static SystemException of(String code, String message) {
return new SystemException(code, message);
}
public static SystemException of(BaseErrorEnum errorEnum, String message, Throwable throwable) {
return new SystemException(errorEnum, message, throwable);
}
public static SystemException of(BaseErrorEnum errorEnum) {
return new SystemException(errorEnum);
}
public static SystemException of(BaseErrorEnum errorEnum, String message) {
return new SystemException(errorEnum, message);
}
}

View File

@ -0,0 +1,29 @@
package com.alibaba.easytools.base.handler;
/**
* 回调的处理器
* 比如meatq回调的时候 会执行这个方法
*
* @author 是仪
*/
public interface EasyCallBackHandler {
/**
* 在处理回调前调用
*/
default void preHandle() {
}
/**
* 在处理回调后调用
* 抛出异常了不会处理
*/
default void postHandle() {
}
/**
* 在处理回调后调用
* 无论是否异常 都会调用
*/
default void afterCompletion() {
}
}

View File

@ -0,0 +1,42 @@
package com.alibaba.easytools.base.wrapper;
import java.io.Serializable;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import lombok.Getter;
import lombok.Setter;
/**
* 封装一个对象
*
* @author 是仪
*/
@Getter
@Setter
public class ObjectWrapper<T> implements Serializable {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
private T value;
public ObjectWrapper(T value) {
this.value = value;
}
public static <T> ObjectWrapper<T> of(T value) {
return new ObjectWrapper<>(value);
}
public static <T> ObjectWrapper<T> ofNull() {
return new ObjectWrapper<>(null);
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
}
}

View File

@ -0,0 +1,52 @@
package com.alibaba.easytools.base.wrapper;
/**
* @author qiuyuyu
* @date 2022/01/20
*/
public interface Result<T> extends Traceable{
/**
* 是否成功
*
* @return
* @mock true
*/
boolean success();
/**
* 设置是否成功
*
* @return
*/
void success(boolean success);
/**
* 错误编码
*
* @return
* @mock 000000
*/
String errorCode();
/**
* 设置错误编码
*
* @param errorCode
*/
void errorCode(String errorCode);
/**
* 错误信息
*
* @return
*/
String errorMessage();
/**
* 设置错误信息
*
* @param errorMessage
*/
void errorMessage(String errorMessage);
}

View File

@ -0,0 +1,22 @@
package com.alibaba.easytools.base.wrapper;
/**
* 是否可以跟踪
*
* @author 是仪
*/
public interface Traceable {
/**
* 获取 traceId
*
* @return traceId
*/
String getTraceId();
/**
* 设置traceId
*
* @param traceId
*/
void setTraceId(String traceId);
}

View File

@ -0,0 +1,46 @@
package com.alibaba.easytools.base.wrapper.param;
import java.io.Serializable;
import com.alibaba.easytools.base.enums.OrderByDirectionEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 排序的对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class OrderBy implements Serializable {
/**
* 排序字段
*/
private String orderConditionName;
/**
* 排序方向
*/
private OrderByDirectionEnum direction;
public static OrderBy of(String property, OrderByDirectionEnum direction) {
return new OrderBy(property, direction);
}
public static OrderBy asc(String property) {
return new OrderBy(property, OrderByDirectionEnum.ASC);
}
public static OrderBy desc(String property) {
return new OrderBy(property, OrderByDirectionEnum.DESC);
}
public static OrderBy smart(String property) {
return new OrderBy(property, OrderByDirectionEnum.SMART);
}
}

View File

@ -0,0 +1,16 @@
package com.alibaba.easytools.base.wrapper.param;
/**
* 排序条件
*
* @author 是仪
*/
public interface OrderCondition {
/**
* 返回列的名字
*
* @return
*/
OrderBy getOrderBy();
}

View File

@ -0,0 +1,141 @@
package com.alibaba.easytools.base.wrapper.param;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.OrderByDirectionEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.hibernate.validator.constraints.Range;
/**
* 分页查询的参数
*
* @author zhuangjiaju
* @date 2021/06/26
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PageQueryParam implements Serializable {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 页码
*/
@NotNull(message = "分页页码不能为空")
@Min(value = 1, message = "分页页码必须大于0")
@Builder.Default
private Integer pageNo = 1;
/**
* 分页大小
*/
@NotNull(message = "分页大小不能为空")
@Range(min = 1, max = EasyToolsConstant.MAX_PAGE_SIZE,
message = "分页大小必须在1-" + EasyToolsConstant.MAX_PAGE_SIZE + "之间")
@Builder.Default
private Integer pageSize = 100;
/**
* 是否返回总条数
* 默认不返回 提高性能
*/
@Builder.Default
private Boolean enableReturnCount = Boolean.FALSE;
/**
* 排序
*/
private List<OrderBy> orderByList;
/**
* 查询全部数据
*/
public void queryAll() {
this.pageNo = 1;
this.pageSize = Integer.MAX_VALUE;
}
/**
* 查询一条数据
*/
public void queryOne() {
this.pageNo = 1;
this.pageSize = 1;
}
/**
* 新增一个排序 并替换原有排序
*
* @param orderBy 排序
* @return 排序参数
*/
public PageQueryParam orderBy(OrderBy orderBy) {
orderByList = new ArrayList<>();
orderByList.add(orderBy);
return this;
}
/**
* 新增一个排序 并替换原有排序
*
* @param orderConditionName 排序字段
* @param direction 排序方向
* @return 排序参数
*/
public PageQueryParam orderBy(String orderConditionName, OrderByDirectionEnum direction) {
return orderBy(new OrderBy(orderConditionName, direction));
}
/**
* 新增一个排序 并替换原有排序
*
* @param orderCondition 排序条件
* @return 排序参数
*/
public PageQueryParam orderBy(OrderCondition orderCondition) {
return orderBy(orderCondition.getOrderBy());
}
/**
* 新增一个排序
*
* @param orderBy 排序
* @return 排序参数
*/
public PageQueryParam andOrderBy(OrderBy orderBy) {
orderByList.add(orderBy);
return this;
}
/**
* 新增一个排序
*
* @param orderConditionName 排序字段
* @param direction 排序方向
* @return 排序参数
*/
public PageQueryParam andOrderBy(String orderConditionName, OrderByDirectionEnum direction) {
return andOrderBy(new OrderBy(orderConditionName, direction));
}
/**
* 新增一个排序
*
* @param orderCondition 排序条件
* @return 排序参数
*/
public PageQueryParam andOrderBy(OrderCondition orderCondition) {
return andOrderBy(orderCondition.getOrderBy());
}
}

View File

@ -0,0 +1,98 @@
package com.alibaba.easytools.base.wrapper.param;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.OrderByDirectionEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 查询的参数
*
* @author zhuangjiaju
* @date 2021/06/26
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class QueryParam implements Serializable {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 排序
*/
private List<OrderBy> orderByList;
/**
* 新增一个排序 并替换原有排序
*
* @param orderBy 排序
* @return 排序参数
*/
public QueryParam orderBy(OrderBy orderBy) {
orderByList = new ArrayList<>();
orderByList.add(orderBy);
return this;
}
/**
* 新增一个排序 并替换原有排序
*
* @param orderConditionName 排序字段
* @param direction 排序方向
* @return 排序参数
*/
public QueryParam orderBy(String orderConditionName, OrderByDirectionEnum direction) {
return orderBy(new OrderBy(orderConditionName, direction));
}
/**
* 新增一个排序 并替换原有排序
*
* @param orderCondition 排序条件
* @return 排序参数
*/
public QueryParam orderBy(OrderCondition orderCondition) {
return orderBy(orderCondition.getOrderBy());
}
/**
* 新增一个排序
*
* @param orderBy 排序
* @return 排序参数
*/
public QueryParam andOrderBy(OrderBy orderBy) {
orderByList.add(orderBy);
return this;
}
/**
* 新增一个排序
*
* @param orderConditionName 排序字段
* @param direction 排序方向
* @return 排序参数
*/
public QueryParam andOrderBy(String orderConditionName, OrderByDirectionEnum direction) {
return andOrderBy(new OrderBy(orderConditionName, direction));
}
/**
* 新增一个排序
*
* @param orderCondition 排序条件
* @return 排序参数
*/
public QueryParam andOrderBy(OrderCondition orderCondition) {
return andOrderBy(orderCondition.getOrderBy());
}
}

View File

@ -0,0 +1,43 @@
package com.alibaba.easytools.base.wrapper.request;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.hibernate.validator.constraints.Range;
/**
* 分页查询的参数
*
* @author zhuangjiaju
* @date 2021/06/26
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PageQueryRequest implements Serializable {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 页码
*/
@NotNull(message = "分页页码不能为空")
@Builder.Default
private Integer pageNo = 1;
/**
* 分页条数
*/
@NotNull(message = "分页大小不能为空")
@Range(min = 1, max = EasyToolsConstant.MAX_PAGE_SIZE,
message = "分页大小必须在1-" + EasyToolsConstant.MAX_PAGE_SIZE + "之间")
@Builder.Default
private Integer pageSize = 10;
}

View File

@ -0,0 +1,128 @@
package com.alibaba.easytools.base.wrapper.result;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.Result;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* action的返回对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ActionResult implements Serializable, Result {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 是否成功
*
* @mock true
*/
@NotNull
@Builder.Default
private Boolean success = Boolean.TRUE;
/**
* 错误编码
*
* @see CommonErrorEnum
*/
private String errorCode;
/**
* 错误信息
*/
private String errorMessage;
/**
* traceId
*/
private String traceId;
/**
* 返回成功
*
* @return 运行结果
*/
public static ActionResult isSuccess() {
return new ActionResult();
}
@Override
public boolean success() {
return success;
}
@Override
public void success(boolean success) {
this.success = success;
}
@Override
public String errorCode() {
return errorCode;
}
@Override
public void errorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public String errorMessage() {
return errorMessage;
}
@Override
public void errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
/**
* 返回失败
*
* @param errorCode 错误编码
* @param errorMessage 错误信息
* @return 运行结果
*/
public static ActionResult fail(String errorCode, String errorMessage) {
ActionResult result = new ActionResult();
result.errorCode = errorCode;
result.errorMessage = errorMessage;
result.success = Boolean.FALSE;
return result;
}
/**
* 返回失败
*
* @param errorEnum 错误枚举
* @return 运行结果
*/
public static ActionResult fail(BaseErrorEnum errorEnum) {
return fail(errorEnum.getCode(), errorEnum.getDescription());
}
/**
* 返回失败
*
* @param errorEnum 错误枚举
* @return 运行结果
*/
public static ActionResult fail(BaseErrorEnum errorEnum, String errorMessage) {
return fail(errorEnum.getCode(), errorMessage);
}
}

View File

@ -0,0 +1,171 @@
package com.alibaba.easytools.base.wrapper.result;
import java.io.Serializable;
import java.util.function.Function;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.Result;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* data的返回对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class DataResult<T> implements Serializable, Result<T> {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 是否成功
*
* @mock true
*/
@NotNull
@Builder.Default
private Boolean success = Boolean.TRUE;
/**
* 错误编码
*
* @see CommonErrorEnum
*/
private String errorCode;
/**
* 错误信息
*/
private String errorMessage;
/**
* 数据信息
*/
private T data;
/**
* traceId
*/
private String traceId;
private DataResult(T data) {
this();
this.data = data;
}
/**
* 构建返回对象
*
* @param data 需要构建的对象
* @param <T> 需要构建的对象类型
* @return 返回的结果
*/
public static <T> DataResult<T> of(T data) {
return new DataResult<>(data);
}
/**
* 构建空的返回对象
*
* @param <T> 需要构建的对象类型
* @return 返回的结果
*/
public static <T> DataResult<T> empty() {
return new DataResult<>();
}
/**
* 构建异常返回
*
* @param errorCode 错误编码
* @param errorMessage 错误信息
* @param <T> 需要构建的对象类型
* @return 返回的结果
*/
public static <T> DataResult<T> error(String errorCode, String errorMessage) {
DataResult<T> result = new DataResult<>();
result.errorCode = errorCode;
result.errorMessage = errorMessage;
result.success = false;
return result;
}
/**
* 构建异常返回
*
* @param errorEnum 错误枚举
* @param <T> 需要构建的对象类型
* @return 返回的结果
*/
public static <T> DataResult<T> error(BaseErrorEnum errorEnum) {
return error(errorEnum.getCode(), errorEnum.getDescription());
}
/**
* 判断是否存在数据
*
* @param dataResult
* @return 是否存在数据
*/
public static boolean hasData(DataResult<?> dataResult) {
return dataResult != null && dataResult.getSuccess() && dataResult.getData() != null;
}
/**
* 将当前的类型转换成另外一个类型
*
* @param mapper 转换的方法
* @param <R> 返回的类型
* @return 返回的结果
*/
public <R> DataResult<R> map(Function<T, R> mapper) {
R returnData = hasData(this) ? mapper.apply(getData()) : null;
DataResult<R> dataResult = new DataResult<>();
dataResult.setSuccess(getSuccess());
dataResult.setErrorCode(getErrorCode());
dataResult.setErrorMessage(getErrorMessage());
dataResult.setData(returnData);
dataResult.setTraceId(getTraceId());
return dataResult;
}
@Override
public boolean success() {
return success;
}
@Override
public void success(boolean success) {
this.success = success;
}
@Override
public String errorCode() {
return errorCode;
}
@Override
public void errorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public String errorMessage() {
return errorMessage;
}
@Override
public void errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@ -0,0 +1,173 @@
package com.alibaba.easytools.base.wrapper.result;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.Result;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* data的返回对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ListResult<T> implements Serializable, Result<T> {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 是否成功
*
* @mock true
*/
@NotNull
@Builder.Default
private Boolean success = Boolean.TRUE;
/**
* 错误编码
*
* @see CommonErrorEnum
*/
private String errorCode;
/**
* 异常信息
*/
private String errorMessage;
/**
* 数据信息
*/
private List<T> data;
/**
* traceId
*/
private String traceId;
private ListResult(List<T> data) {
this();
this.data = data;
}
/**
* 构建列表返回对象
*
* @param data 需要构建的对象
* @param <T> 需要构建的对象类型
* @return 返回的列表
*/
public static <T> ListResult<T> of(List<T> data) {
return new ListResult<>(data);
}
/**
* 构建空的列表返回对象
*
* @param <T> 需要构建的对象类型
* @return 返回的列表
*/
public static <T> ListResult<T> empty() {
return of(Collections.emptyList());
}
/**
* 构建异常返回列表
*
* @param errorCode 错误编码
* @param errorMessage 错误信息
* @param <T> 需要构建的对象类型
* @return 返回的列表
*/
public static <T> ListResult<T> error(String errorCode, String errorMessage) {
ListResult<T> result = new ListResult<>();
result.errorCode = errorCode;
result.errorMessage = errorMessage;
result.success = Boolean.TRUE;
return result;
}
/**
* 构建异常返回列表
*
* @param errorEnum 错误枚举
* @param <T> 需要构建的对象类型
* @return 返回的列表
*/
public static <T> ListResult<T> error(BaseErrorEnum errorEnum) {
return error(errorEnum.getCode(), errorEnum.getDescription());
}
/**
* 判断是否存在数据
*
* @param listResult
* @return 是否存在数据
*/
public static boolean hasData(ListResult<?> listResult) {
return listResult != null && listResult.getSuccess() && listResult.getData() != null && !listResult.getData()
.isEmpty();
}
/**
* 将当前的类型转换成另外一个类型
*
* @param mapper 转换的方法
* @param <R> 返回的类型
* @return 分页返回对象
*/
public <R> ListResult<R> map(Function<T, R> mapper) {
List<R> returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList())
: Collections.emptyList();
ListResult<R> listResult = new ListResult<>();
listResult.setSuccess(getSuccess());
listResult.setErrorCode(getErrorCode());
listResult.setErrorMessage(getErrorMessage());
listResult.setData(returnData);
listResult.setTraceId(getTraceId());
return listResult;
}
@Override
public boolean success() {
return success;
}
@Override
public void success(boolean success) {
this.success = success;
}
@Override
public String errorCode() {
return errorCode;
}
@Override
public void errorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public String errorMessage() {
return errorMessage;
}
@Override
public void errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@ -0,0 +1,360 @@
package com.alibaba.easytools.base.wrapper.result;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.Result;
import com.alibaba.easytools.base.wrapper.param.PageQueryParam;
import com.alibaba.easytools.base.wrapper.result.web.WebPageResult;
import com.alibaba.easytools.base.wrapper.result.web.WebPageResult.Page;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* data的返回对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> implements Serializable, Result<List<T>> {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 是否成功
*
* @mock true
*/
@NotNull
@Builder.Default
private Boolean success = Boolean.TRUE;
/**
* 错误编码
*
* @see CommonErrorEnum
*/
private String errorCode;
/**
* 异常信息
*/
private String errorMessage;
/**
* 数据信息
*/
private List<T> data;
/**
* 分页编码
*/
@Builder.Default
private Integer pageNo = 1;
/**
* 分页大小
*/
@Builder.Default
private Integer pageSize = 10;
/**
* 总的大小
*/
@Builder.Default
private Long total = 0L;
/**
* traceId
*/
private String traceId;
/**
* 是否存在下一页
*/
private Boolean hasNextPage;
private PageResult(List<T> data, Long total, Long pageNo, Long pageSize) {
this();
this.data = data;
this.total = total;
if (pageNo != null) {
this.pageNo = Math.toIntExact(pageNo);
}
if (pageSize != null) {
this.pageSize = Math.toIntExact(pageSize);
}
}
private PageResult(List<T> data, Long total, Integer pageNo, Integer pageSize) {
this();
this.data = data;
this.total = total;
if (pageNo != null) {
this.pageNo = pageNo;
}
if (pageSize != null) {
this.pageSize = pageSize;
}
}
/**
* 构建分页返回对象
*
* @param data 返回的对象
* @param total 总的条数
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> PageResult<T> of(List<T> data, Long total, Long pageNo, Long pageSize) {
return new PageResult<>(data, total, pageNo, pageSize);
}
/**
* 构建分页返回对象
*
* @param data 返回的对象
* @param total 总的条数
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> PageResult<T> of(List<T> data, Long total, Integer pageNo, Integer pageSize) {
return new PageResult<>(data, total, pageNo, pageSize);
}
/**
* 构建分页返回对象
*
* @param data 返回的对象
* @param total 总的条数
* @param param 分页参数
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> PageResult<T> of(List<T> data, Long total, PageQueryParam param) {
return new PageResult<>(data, total, param.getPageNo(), param.getPageSize());
}
/**
* 构建空的返回对象
*
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> PageResult<T> empty(Long pageNo, Long pageSize) {
return of(new ArrayList<>(), 0L, pageNo, pageSize);
}
/**
* 构建空的返回对象
*
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> PageResult<T> empty(Integer pageNo, Integer pageSize) {
return of(new ArrayList<>(), 0L, pageNo, pageSize);
}
/**
* 构建空的返回对象
*
* @param param 查询参数
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> PageResult<T> empty(PageQueryParam param) {
return of(new ArrayList<>(), 0L, param.getPageNo(), param.getPageSize());
}
/**
* 判断是否还有下一页
* 根据分页大小来计算 防止total为空
*
* @return 是否还有下一页
*/
public Boolean calculateHasNextPage() {
// 存在分页大小 根据分页来计算
if (total > 0) {
return (long)pageSize * pageNo <= total;
}
// 没有数据 肯定没有下一页
if (data == null || data.isEmpty()) {
return false;
}
// 当前数量小于分页数量
return data.size() >= pageSize;
}
/**
* 判断是否还有下一页
* 根据分页大小来计算 防止total为空
*
* @return 是否还有下一页
* @deprecated 使用 {@link #getHasNextPage()} ()}
*/
@Deprecated
public boolean hasNextPage() {
return getHasNextPage();
}
public Boolean getHasNextPage() {
if (hasNextPage == null) {
hasNextPage = calculateHasNextPage();
}
return hasNextPage;
}
/**
* 返回查询异常信息
*
* @param errorCode 错误编码
* @param errorMessage 错误信息
* @param <T> 返回的对象
* @return 分页返回对象
*/
public static <T> PageResult<T> error(String errorCode, String errorMessage) {
PageResult<T> result = new PageResult<>();
result.errorCode = errorCode;
result.errorMessage = errorMessage;
result.success = Boolean.FALSE;
return result;
}
/**
* 返回查询异常信息
*
* @param errorEnum 错误枚举
* @param <T> 返回的对象
* @return 分页返回对象
*/
public static <T> PageResult<T> error(BaseErrorEnum errorEnum) {
return error(errorEnum.getCode(), errorEnum.getDescription());
}
/**
* 判断是否存在数据
*
* @param pageResult
* @return 是否存在数据
*/
public static boolean hasData(PageResult<?> pageResult) {
return pageResult != null && pageResult.getSuccess() && pageResult.getData() != null && !pageResult.getData()
.isEmpty();
}
/**
* 将当前的类型转换成另外一个类型
*
* @param mapper 转换的方法
* @param <R> 返回的类型
* @return 分页返回对象
*/
public <R> PageResult<R> map(Function<T, R> mapper) {
List<R> returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList())
: new ArrayList<>();
PageResult<R> pageResult = new PageResult<>();
pageResult.setSuccess(getSuccess());
pageResult.setErrorCode(getErrorCode());
pageResult.setErrorMessage(getErrorMessage());
pageResult.setData(returnData);
pageResult.setPageNo(getPageNo());
pageResult.setPageSize(getPageSize());
pageResult.setTotal(getTotal());
pageResult.setTraceId(getTraceId());
return pageResult;
}
/**
* 将当前的类型转换成另外一个类型
* 并且转换成web的类型
* 这里注意如果当前项目在web层用的也是 <code>PageResult</code> 则直接使用 <code>map</code>方法接口即可
*
* @param mapper 转换的方法
* @param <R> 返回的类型
* @return 分页返回对象
*/
public <R> WebPageResult<R> mapToWeb(Function<T, R> mapper) {
List<R> returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList())
: new ArrayList<>();
WebPageResult<R> pageResult = new WebPageResult<>();
pageResult.setSuccess(getSuccess());
pageResult.setErrorCode(getErrorCode());
pageResult.setErrorMessage(getErrorMessage());
pageResult.setTraceId(getTraceId());
// 重新设置一个分页信息
Page<R> page = new Page<>();
pageResult.setData(page);
page.setData(returnData);
page.setPageNo(getPageNo());
page.setPageSize(getPageSize());
page.setTotal(getTotal());
pageResult.setData(page);
return pageResult;
}
/**
* 转成一个web 的分页
*
* @return 分页返回对象
*/
public WebPageResult<T> toWeb() {
WebPageResult<T> pageResult = new WebPageResult<>();
pageResult.setSuccess(getSuccess());
pageResult.setErrorCode(getErrorCode());
pageResult.setErrorMessage(getErrorMessage());
pageResult.setTraceId(getTraceId());
// 重新设置一个分页信息
Page<T> page = new Page<>();
pageResult.setData(page);
page.setData(getData());
page.setPageNo(getPageNo());
page.setPageSize(getPageSize());
page.setTotal(getTotal());
pageResult.setData(page);
return pageResult;
}
@Override
public boolean success() {
return success;
}
@Override
public void success(boolean success) {
this.success = success;
}
@Override
public String errorCode() {
return errorCode;
}
@Override
public void errorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public String errorMessage() {
return errorMessage;
}
@Override
public void errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@ -0,0 +1,331 @@
package com.alibaba.easytools.base.wrapper.result.web;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import com.alibaba.easytools.base.enums.BaseErrorEnum;
import com.alibaba.easytools.base.wrapper.Result;
import com.alibaba.easytools.base.wrapper.param.PageQueryParam;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* data的返回对象
* PageResult 比较一致 也可以直接用 PageResult 这个是部分项目前端需要将 data+pageNo 封装到一起 所以额外创建的类
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class WebPageResult<T> implements Serializable, Result<List<T>> {
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 是否成功
*
* @mock true
*/
@NotNull
@Builder.Default
private Boolean success = Boolean.TRUE;
/**
* 异常编码
*/
private String errorCode;
/**
* 异常信息
*/
private String errorMessage;
/**
* 数据信息
*/
@Builder.Default
private Page<T> data = new Page<>();
/**
* traceId
*/
private String traceId;
private WebPageResult(List<T> data, Long total, Long pageNo, Long pageSize) {
this.success = Boolean.TRUE;
this.data = new Page<>(data, total, pageNo, pageSize);
}
private WebPageResult(List<T> data, Long total, Integer pageNo, Integer pageSize) {
this.success = Boolean.TRUE;
this.data = new Page<>(data, total, pageNo, pageSize);
}
/**
* 构建分页返回对象
*
* @param data 返回的对象
* @param total 总的条数
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> WebPageResult<T> of(List<T> data, Long total, Long pageNo, Long pageSize) {
return new WebPageResult<>(data, total, pageNo, pageSize);
}
/**
* 构建分页返回对象
*
* @param data 返回的对象
* @param total 总的条数
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> WebPageResult<T> of(List<T> data, Long total, Integer pageNo, Integer pageSize) {
return new WebPageResult<>(data, total, pageNo, pageSize);
}
/**
* 构建分页返回对象
*
* @param data 返回的对象
* @param total 总的条数
* @param param 分页参数
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> WebPageResult<T> of(List<T> data, Long total, PageQueryParam param) {
return new WebPageResult<>(data, total, param.getPageNo(), param.getPageSize());
}
/**
* 构建空的返回对象
*
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> WebPageResult<T> empty(Long pageNo, Long pageSize) {
return of(Collections.emptyList(), 0L, pageNo, pageSize);
}
/**
* 构建空的返回对象
*
* @param pageNo 页码
* @param pageSize 分页大小
* @param <T> 返回的对象类型
* @return 分页返回对象
*/
public static <T> WebPageResult<T> empty(Integer pageNo, Integer pageSize) {
return of(Collections.emptyList(), 0L, pageNo, pageSize);
}
/**
* 判断是否还有下一页
* 根据分页大小来计算 防止total为空
*
* @return 是否还有下一页
* @deprecated 使用 {@link #getHasNextPage()} ()}
*/
@Deprecated
public boolean hasNextPage() {
return getHasNextPage();
}
public Boolean getHasNextPage() {
if (data == null) {
return Boolean.FALSE;
}
return data.getHasNextPage();
}
/**
* 返回查询异常信息
*
* @param errorCode 错误编码
* @param errorMessage 错误信息
* @param <T> 返回的对象
* @return 分页返回对象
*/
public static <T> WebPageResult<T> error(String errorCode, String errorMessage) {
WebPageResult<T> result = new WebPageResult<>();
result.errorCode = errorCode;
result.errorMessage = errorMessage;
result.success = Boolean.FALSE;
return result;
}
/**
* 返回查询异常信息
*
* @param errorEnum 错误枚举
* @param <T> 返回的对象
* @return 分页返回对象
*/
public static <T> WebPageResult<T> error(BaseErrorEnum errorEnum) {
return error(errorEnum.getCode(), errorEnum.getDescription());
}
/**
* 判断是否存在数据
*
* @param pageResult
* @return 是否存在数据
*/
public static boolean hasData(WebPageResult<?> pageResult) {
return pageResult != null && pageResult.getSuccess() && pageResult.getData() != null
&& pageResult.getData().getData() != null && !pageResult.getData().getData().isEmpty();
}
/**
* 将当前的类型转换成另外一个类型
*
* @param mapper 转换的方法
* @param <R> 返回的类型
* @return 分页返回对象
*/
public <R> WebPageResult<R> map(Function<T, R> mapper) {
List<R> returnData = hasData(this) ? getData().getData().stream().map(mapper).collect(Collectors.toList())
: Collections.emptyList();
WebPageResult<R> pageResult = new WebPageResult<>();
pageResult.setSuccess(getSuccess());
pageResult.setErrorCode(getErrorCode());
pageResult.setErrorMessage(getErrorMessage());
pageResult.setTraceId(getTraceId());
// 重新设置一个分页信息
Page<R> page = new Page<>();
pageResult.setData(page);
page.setData(returnData);
page.setPageNo(data.getPageNo());
page.setPageSize(data.getPageSize());
page.setTotal(data.getTotal());
return pageResult;
}
@Override
public boolean success() {
return success;
}
@Override
public void success(boolean success) {
this.success = success;
}
@Override
public String errorCode() {
return errorCode;
}
@Override
public void errorCode(String errorCode) {
this.errorCode = errorCode;
}
@Override
public String errorMessage() {
return errorMessage;
}
@Override
public void errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
/**
* 分页信息
*
* @param <T>
*/
@Data
public static class Page<T> {
/**
* 数据信息
*/
private List<T> data;
/**
* 分页编码
*/
private Integer pageNo;
/**
* 分页大小
*/
private Integer pageSize;
/**
* 总的大小
*/
private Long total;
/**
* 是否存在下一页
*/
private Boolean hasNextPage;
public Page() {
this.pageNo = 1;
this.pageSize = 10;
this.total = 0L;
}
private Page(List<T> data, Long total, Long pageNo, Long pageSize) {
this();
this.data = data;
this.total = total;
if (pageNo != null) {
this.pageNo = Math.toIntExact(pageNo);
}
if (pageSize != null) {
this.pageSize = Math.toIntExact(pageSize);
}
}
private Page(List<T> data, Long total, Integer pageNo, Integer pageSize) {
this();
this.data = data;
this.total = total;
if (pageNo != null) {
this.pageNo = pageNo;
}
if (pageSize != null) {
this.pageSize = pageSize;
}
}
public Boolean getHasNextPage() {
if (hasNextPage == null) {
hasNextPage = calculateHasNextPage();
}
return hasNextPage;
}
/**
* 判断是否还有下一页
* 根据分页大小来计算 防止total为空
*
* @return 是否还有下一页
*/
public Boolean calculateHasNextPage() {
// 存在分页大小 根据分页来计算
if (total > 0) {
return (long)pageSize * pageNo <= total;
}
// 没有数据 肯定没有下一页
if (data == null || data.isEmpty()) {
return false;
}
// 当前数量小于分页数量
return data.size() >= pageSize;
}
}
}

View File

@ -0,0 +1,90 @@
package com.alibaba.atasuper.web.controller.demo;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.param.DemoPageQueryParam;
import com.alibaba.atasuper.api.demo.param.DemoPageQueryParam.OrderCondition;
import com.alibaba.atasuper.api.demo.param.DemoSelector;
import com.alibaba.atasuper.api.demo.DemoCoreService;
import com.alibaba.atasuper.api.user.param.DomainUserSelector;
import com.alibaba.atasuper.tools.base.wrapper.result.AtaPageResult;
import com.alibaba.atasuper.tools.base.wrapper.result.AtaPojoResult;
import com.alibaba.atasuper.web.controller.demo.request.DemoCreateRequest;
import com.alibaba.atasuper.web.controller.demo.request.DemoPageQueryRequest;
import com.alibaba.atasuper.web.controller.demo.vo.DemoPageQueryVO;
import com.alibaba.atasuper.web.controller.demo.vo.DemoQueryVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO 模板的Controller 方法用到才复制 别全复制 参考<a href="https://aliyuque.antfin.com/ctoo-pic/rd/ixq0lu">...</a>
*
* @author 是仪
*/
@RestController
@RequestMapping(path = "/api/v1/demo")
@Slf4j
public class DemoController {
private static final DemoSelector DEMO_SELECTOR_GET = DemoSelector.builder()
.user(Boolean.TRUE)
.userDomainUserSelector(DomainUserSelector.builder()
.medals(Boolean.TRUE)
.build())
.image(Boolean.TRUE)
.build();
private static final DemoSelector DEMO_SELECTOR_PAGE_QUERY = DemoSelector.builder()
.createUser(Boolean.TRUE)
.build();
@Resource
private DemoCoreService demoCoreService;
@Resource
private DemoWebConverter demoWebConverter;
/**
* 创建一条数据
*
* @param request 创建参数
* @return id
*/
@PostMapping("create")
public AtaPojoResult<Long> create(@Valid @RequestBody DemoCreateRequest request) {
return AtaPojoResult.of(demoCoreService.create(demoWebConverter.request2param(request)));
}
/**
* 查询一条数据
*
* @param id 主键
* @return
*/
@GetMapping("query")
public AtaPojoResult<DemoQueryVO> query(@Valid @NotNull Long id) {
return AtaPojoResult.of(
demoWebConverter.dto2voQuery(demoCoreService.queryExistentWithPermission(id, DEMO_SELECTOR_GET)));
}
/**
* 分页查询列表数据
*
* @param request request
* @return
*/
@GetMapping("page-query")
public AtaPageResult<DemoPageQueryVO> pageQuery(@Valid DemoPageQueryRequest request) {
DemoPageQueryParam demoPageQueryParam = demoWebConverter.request2param(request);
demoPageQueryParam.orderBy(OrderCondition.GMT_MODIFIED_DESC);
return demoCoreService.pageQueryWithPermission(demoPageQueryParam, DEMO_SELECTOR_PAGE_QUERY)
.map(demoWebConverter::dto2voPageQuery);
}
}

View File

@ -0,0 +1,58 @@
package com.alibaba.atasuper.web.controller.demo;
import com.alibaba.atasuper.api.demo.DemoDTO;
import com.alibaba.atasuper.api.demo.param.DemoCreateParam;
import com.alibaba.atasuper.api.demo.param.DemoPageQueryParam;
import com.alibaba.atasuper.web.controller.demo.request.DemoCreateRequest;
import com.alibaba.atasuper.web.controller.demo.request.DemoPageQueryRequest;
import com.alibaba.atasuper.web.controller.demo.vo.DemoPageQueryVO;
import com.alibaba.atasuper.web.controller.demo.vo.DemoQueryVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
/**
* 转换器
*
* @author 是仪
*/
@Mapper(componentModel = "spring")
public abstract class DemoWebConverter {
/**
* 转换
*
* @param request
* @return
*/
public abstract DemoCreateParam request2param(DemoCreateRequest request);
/**
* 转换
*
* @param dto
* @return
*/
public abstract DemoQueryVO dto2voQuery(DemoDTO dto);
/**
* 转换
*
* @param dto
* @return
*/
public abstract DemoPageQueryVO dto2voPageQuery(DemoDTO dto);
/**
* 转换
*
* @param request
* @return
*/
@Mappings({
@Mapping(target = "userIdWhenPresent", source = "userId"),
})
public abstract DemoPageQueryParam request2param(DemoPageQueryRequest request);
}

View File

@ -0,0 +1,49 @@
package com.alibaba.atasuper.web.controller.demo.request;
import java.util.Date;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.enums.DemoStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 创建对象
*
* @author 是仪
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DemoCreateRequest {
/**
* 用户id
*/
@NotNull
private Long userId;
/**
* 状态一般非空还要有默认值
*
* @see DemoStatusEnum
*/
private String status;
/**
* 图片的key, 图片存储一般存储key别存储连接
*/
private String imageKey;
/**
* 加入时间必须gmt开头日期字段没办法默认值直接默认空
*/
private Date gmtJoin;
}

View File

@ -0,0 +1,25 @@
package com.alibaba.atasuper.web.controller.demo.request;
import com.alibaba.atasuper.tools.base.wrapper.request.WebPageRequest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 列表对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class DemoPageQueryRequest extends WebPageRequest {
/**
* 用户id
*/
private String userId;
}

View File

@ -0,0 +1,35 @@
package com.alibaba.atasuper.web.controller.demo.vo;
import com.alibaba.atasuper.api.user.vo.UserVO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 列表对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class DemoPageQueryVO {
/**
* 主键
*/
private Long id;
/**
* 姓名字符串字段一般默认空字符串不建议使用非空
*/
private String name;
/**
* 创建人
*/
private UserVO createUser;
}

View File

@ -0,0 +1,71 @@
package com.alibaba.atasuper.web.controller.demo.vo;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.enums.DemoStatusEnum;
import com.alibaba.atasuper.api.demo.param.DemoSelector;
import com.alibaba.atasuper.api.image.vo.ImageVO;
import com.alibaba.atasuper.api.user.param.DomainUserSelector;
import com.alibaba.atasuper.api.user.vo.UserVO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 单个对象
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class DemoQueryVO {
/**
* 主键
*/
private Long id;
/**
* 用户id
*/
@NotNull
private String userId;
/**
* 用户
*
* @see DemoSelector#setUser(Boolean)
* @see DemoSelector#setUserDomainUserSelector(DomainUserSelector)
*/
@NotNull
private UserVO user;
/**
* 状态一般非空还要有默认值
*
* @see DemoStatusEnum
*/
private String status;
/**
* 图片的key, 图片存储一般存储key别存储连接
*/
private String imageKey;
/**
* 图片
*
* @see DemoSelector#setImage(Boolean)
*/
private ImageVO image;
/**
* 姓名字符串字段一般默认空字符串不建议使用非空
*/
private String name;
}

View File

@ -0,0 +1,136 @@
package com.alibaba.easytools.spring.cache;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.annotation.Resource;
import com.alibaba.easytools.spring.cache.wrapper.CacheWrapper;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
/**
* 缓存操作
*
* @author qiuyuyu
* @date 2022/03/08
*/
public class EasyCache<V> {
/**
* 超时时间
*/
private static final long DEFAULT_TIMEOUT = Duration.ofMinutes(2L).toMillis();
/**
* 同步锁的前缀
*/
private static final String SYNCHRONIZED_PREFIX = "_EasyCache:";
@Resource
private HashOperations<String, String, CacheWrapper<V>> hashOperations;
@Resource
private ValueOperations<String, CacheWrapper<V>> valueOperations;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 去缓存里面获取一个值 并放入缓存
*
* @param key 缓存的key
* @param queryData 查询数据
* @return 缓存的值
*/
public V get(String key, Supplier<V> queryData) {
return get(key, queryData, DEFAULT_TIMEOUT);
}
/**
* 去缓存里面获取一个值 并放入缓存
*
* @param key 缓存的key
* @param queryData 查询数据
* @param timeout 超时时长 ms
* @return 缓存的值
*/
public V get(String key, Supplier<V> queryData, Long timeout) {
if (key == null) {
return null;
}
// 先去缓存获取
CacheWrapper<V> cacheWrapper = valueOperations.get(key);
if (cacheWrapper != null) {
return cacheWrapper.getData();
}
// 没有则锁住 然后第一个去获取
String lockKey = SYNCHRONIZED_PREFIX + key;
synchronized (lockKey.intern()) {
// 重新获取
cacheWrapper = valueOperations.get(key);
if (cacheWrapper != null) {
return cacheWrapper.getData();
}
// 真正的去查询数据
V value = queryData.get();
// 构建缓存
CacheWrapper<V> cacheWrapperData = new CacheWrapper<>();
cacheWrapperData.setData(value);
valueOperations.set(key, cacheWrapperData, timeout, TimeUnit.MILLISECONDS);
return value;
}
}
/**
* 去缓存里面获取一个值 并放入缓存
*
* @param key 缓存的key
* @param hashKey 缓存的hashKey
* @param queryData 查询数据
* @return 缓存的值
*/
public V hashGet(String key, String hashKey, Supplier<V> queryData) {
if (key == null || hashKey == null) {
return null;
}
// 先去缓存获取
CacheWrapper<V> cacheWrapper = hashOperations.get(key, hashKey);
if (cacheWrapper != null && System.currentTimeMillis() < cacheWrapper.getExpireTimeMillis()) {
return cacheWrapper.getData();
}
// 没有则锁住 然后第一个去获取
String lockKey = SYNCHRONIZED_PREFIX + key + ":" + hashKey;
synchronized (lockKey.intern()) {
// 重新获取
cacheWrapper = hashOperations.get(key, hashKey);
if (cacheWrapper != null && System.currentTimeMillis() < cacheWrapper.getExpireTimeMillis()) {
return cacheWrapper.getData();
}
// 真正的去查询数据
V value = queryData.get();
// 构建缓存
CacheWrapper<V> cacheWrapperData = new CacheWrapper<>();
cacheWrapperData.setData(value);
cacheWrapperData.setExpireTimeMillis(System.currentTimeMillis() + DEFAULT_TIMEOUT);
hashOperations.put(key, hashKey, cacheWrapperData);
stringRedisTemplate.boundHashOps(key).expire(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
return value;
}
}
/**
* 移除缓存
*
* @param keys
*/
public void delete(String... keys) {
stringRedisTemplate.delete(Lists.newArrayList(keys));
}
}

View File

@ -0,0 +1,276 @@
package com.alibaba.easytools.spring.cache;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.annotation.Resource;
import com.alibaba.easytools.base.excption.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.support.NullValue;
/**
* 缓存服务v2
*
* @author 是仪
*/
@Slf4j
public class EasyCacheV2 {
/**
* 超时时间
*/
private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(2L);
/**
* 同步锁的前缀
*/
private static final String SYNCHRONIZED_PREFIX = "_EasyCacheV2:";
@Resource
private RedissonClient redissonClient;
/**
* 去缓存里面获取一个值 并放入缓存
*
* @param key 缓存的key
* @param queryData 查询数据
* @return 缓存的值
*/
public <T> T computeIfAbsent(String key, Supplier<T> queryData) {
return computeIfAbsent(key, queryData, DEFAULT_TIMEOUT);
}
/**
* 去缓存里面获取一个值 并放入缓存
*
* @param key 缓存的key
* @param queryData 查询数据
* @param duration 超时时长
* @return 缓存的值
*/
public <T> T computeIfAbsent(String key, Supplier<T> queryData, Duration duration) {
if (key == null) {
return null;
}
// 先去缓存获取
T data = get(key);
if (data != null) {
return data;
}
// 没有则锁住 然后第一个去获取
String lockKey = SYNCHRONIZED_PREFIX + key;
synchronized (lockKey.intern()) {
// 重新获取
data = get(key);
if (data != null) {
return data;
}
// 真正的去查询数据
T value = queryData.get();
// 构建缓存
set(key, value, duration);
return value;
}
}
/**
* 移除缓存
*
* @param keys
*/
public void delete(String... keys) {
redissonClient.getKeys().delete(keys);
}
/**
* 同步执行任务,等待时间为一分钟执行释放锁为一分钟推荐mq回调执行简单任务使用
* 在等待超时后抛出异常
*
* @param lockKey 全局唯一
* @param runnable 同步执行的任务
*/
public void synchronousExecuteOneMinuteAndThrowException(String lockKey, Runnable runnable) {
synchronousExecute(lockKey, runnable, () -> {
log.warn("经过60秒没有抢到锁");
throw BusinessException.of("经过60秒没有抢到锁");
}, Duration.ofMinutes(1L), Duration.ofMinutes(1L));
}
/**
* 同步执行任务,等待时间为一分钟执行释放锁为一分钟推荐mq回调执行简单任务使用
* 在等待超时后抛出异常
*
* @param lockKey 全局唯一
* @param runnable 同步执行的任务
*/
public void synchronousExecuteThrowException(String lockKey, Runnable runnable, Duration waitDuration,
Duration leaseDuration) {
synchronousExecute(lockKey, runnable, () -> {
log.warn("经过{}秒没有抢到锁", waitDuration.getSeconds());
throw BusinessException.of("经过" + waitDuration.getSeconds() + "秒没有抢到锁");
}, waitDuration, leaseDuration);
}
/**
* 同步执行任务,等待时间为一分钟执行释放锁为一分钟推荐mq回调执行简单任务使用
* 在等待超时后抛出异常
*
* @param lockKey 全局唯一
* @param supplier 同步执行的任务
*/
public <T> T synchronousExecuteOneMinuteAndThrowException(String lockKey, Supplier<T> supplier) {
return synchronousExecute(lockKey, supplier, () -> {
log.warn("经过60秒没有抢到锁");
throw BusinessException.of("经过60秒没有抢到锁");
}, Duration.ofMinutes(1L), Duration.ofMinutes(1L));
}
/**
* 同步执行任务,等待时间为一分钟执行释放锁为一分钟推荐mq回调执行简单任务使用
* 在等待超时后抛出异常
*
* @param lockKey 全局唯一
* @param supplier 同步执行的任务
* @param waitDuration 等待超时时间
* @param leaseDuration 执行任务多少时间后释放锁
*/
public <T> T synchronousExecuteThrowException(String lockKey, Supplier<T> supplier,
Duration waitDuration, Duration leaseDuration) {
return synchronousExecute(lockKey, supplier, () -> {
log.warn("经过{}秒没有抢到锁", waitDuration.getSeconds());
throw BusinessException.of("经过" + waitDuration.getSeconds() + "秒没有抢到锁");
}, waitDuration, leaseDuration);
}
/**
* 同步执行任务,等待0秒后立即执行waitTimeOutExecutor
*
* @param lockKey 全局唯一
* @param runnable 同步执行的任务
* @param waitTimeOutRunnable 等待超时以后的处理逻辑
* @param leaseDuration 执行任务多少时间后释放锁
*/
public void synchronousExecuteFailFast(String lockKey, Runnable runnable, Runnable waitTimeOutRunnable,
Duration leaseDuration) {
synchronousExecute(lockKey, runnable, waitTimeOutRunnable, Duration.ZERO, leaseDuration);
}
/**
* 同步执行任务
*
* @param lockKey 全局唯一
* @param runnable 同步执行的任务
* @param waitTimeOutRunnable 等待超时以后的处理逻辑
* @param waitDuration 等待超时时间
* @param leaseDuration 执行任务多少时间后释放锁
*/
public void synchronousExecute(String lockKey, Runnable runnable, Runnable waitTimeOutRunnable,
Duration waitDuration, Duration leaseDuration) {
//同一对象 不能并发
RLock rLock = redissonClient.getLock(lockKey);
try {
// 尝试加锁 最多等待 waitTime , 并且在leaseTime 之后释放锁
if (rLock.tryLock(waitDuration.getSeconds(), leaseDuration.getSeconds(), TimeUnit.SECONDS)) {
runnable.run();
} else {
if (waitTimeOutRunnable != null) {
waitTimeOutRunnable.run();
}
}
} catch (InterruptedException e) {
log.error("执行任务被打断:{}", lockKey, e);
if (waitTimeOutRunnable != null) {
waitTimeOutRunnable.run();
}
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
}
/**
* 同步执行任务
*
* @param lockKey 全局唯一
* @param supplier 同步执行的任务
* @param waitTimeOutRunnable 等待超时以后的处理逻辑
* @param waitDuration 等待超时时间
* @param leaseDuration 执行任务多少时间后释放锁
*/
public <T> T synchronousExecute(String lockKey, Supplier<T> supplier, Runnable waitTimeOutRunnable,
Duration waitDuration, Duration leaseDuration) {
//同一对象 不能并发
RLock rLock = redissonClient.getLock(lockKey);
try {
// 尝试加锁 最多等待 waitTime , 并且在leaseTime 之后释放锁
if (rLock.tryLock(waitDuration.getSeconds(), leaseDuration.getSeconds(), TimeUnit.SECONDS)) {
return supplier.get();
} else {
if (waitTimeOutRunnable != null) {
waitTimeOutRunnable.run();
}
return null;
}
} catch (InterruptedException e) {
log.error("执行任务被打断:{}", lockKey, e);
if (waitTimeOutRunnable != null) {
waitTimeOutRunnable.run();
}
return null;
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
}
/**
* 查询操作
*
* @param key 缓存key
* @param <T>
* @return
*/
public <T> T get(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
RBucket<T> rBucket = redissonClient.getBucket(key);
T data = rBucket.get();
// 在存储空的数据的时候 存储了一个空的对象
if (data instanceof NullValue) {
return null;
}
return data;
}
/**
* 设置一个值
*
* @param key
* @param value
* @param duration
*/
public void set(String key, Object value, Duration duration) {
Object cacheValue = value;
if (cacheValue == null) {
cacheValue = NullValue.INSTANCE;
}
if (duration == null) {
duration = DEFAULT_TIMEOUT;
}
redissonClient.getBucket(key)
// 为了兼容老版本
.set(cacheValue, duration.getSeconds(), TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,25 @@
package com.alibaba.easytools.spring.cache.wrapper;
import java.io.Serializable;
import lombok.Data;
/**
* 缓存包装
*
* @author qiuyuyu
* @date 2022/03/08
*/
@Data
public class CacheWrapper<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 超时时间戳
*/
private Long expireTimeMillis;
/**
* 数据
*/
private T data;
}

View File

@ -0,0 +1,95 @@
package com.alibaba.easytools.spring.exception;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import com.alibaba.easytools.base.excption.BusinessException;
import com.alibaba.easytools.base.excption.SystemException;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
import com.alibaba.easytools.spring.exception.convertor.ExceptionConvertorUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
/**
* 拦截Controller异常
*
* @author 是仪
*/
@ControllerAdvice
@Slf4j
public class EasyControllerExceptionHandler {
/**
* 业务异常
*
* @param request request
* @param exception exception
* @return return
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, IllegalArgumentException.class,
MissingServletRequestParameterException.class, MethodArgumentTypeMismatchException.class,
BusinessException.class, MaxUploadSizeExceededException.class, ClientAbortException.class,
HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotAcceptableException.class,
MultipartException.class, MissingRequestHeaderException.class, HttpMediaTypeNotSupportedException.class,
ConstraintViolationException.class, HttpMessageNotReadableException.class})
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public ActionResult handleBusinessException(HttpServletRequest request, Exception exception) {
ActionResult result = ExceptionConvertorUtils.convert(exception);
log.info("发生业务异常{}:{}", request.getRequestURI(), result, exception);
return result;
}
/**
* 系统异常
*
* @param request request
* @param exception exception
* @return return
*/
@ExceptionHandler({SystemException.class})
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public ActionResult handleSystemException(HttpServletRequest request, Exception exception) {
ActionResult result = ExceptionConvertorUtils.convert(exception);
log.error("发生业务异常{}:{}", request.getRequestURI(), result, exception);
return result;
}
/**
* 未知异常 需要人工介入查看日志
*
* @param request request
* @param exception exception
* @return return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public ActionResult handledException(HttpServletRequest request, Exception exception) {
ActionResult result = ExceptionConvertorUtils.convert(exception);
log.error("发生未知异常{}:{}:{},请求参数:{}", request.getRequestURI(),
ExceptionConvertorUtils.buildHeaderString(request), result,
JSON.toJSONString(request.getParameterMap()), exception);
return result;
}
}

View File

@ -0,0 +1,20 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
import org.springframework.validation.BindException;
/**
* BindException
*
* @author 是仪
*/
public class BindExceptionConvertor implements ExceptionConvertor<BindException> {
@Override
public ActionResult convert(BindException exception) {
String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult());
return ActionResult.fail(CommonErrorEnum.PARAM_ERROR, message);
}
}

View File

@ -0,0 +1,17 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.BusinessException;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
/**
* BusinessException
*
* @author 是仪
*/
public class BusinessExceptionConvertor implements ExceptionConvertor<BusinessException> {
@Override
public ActionResult convert(BusinessException exception) {
return ActionResult.fail(exception.getCode(), exception.getMessage());
}
}

View File

@ -0,0 +1,20 @@
package com.alibaba.easytools.spring.exception.convertor;
import javax.validation.ConstraintViolationException;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
/**
* ConstraintViolationException
*
* @author 是仪
*/
public class ConstraintViolationExceptionConvertor implements ExceptionConvertor<ConstraintViolationException> {
@Override
public ActionResult convert(ConstraintViolationException exception) {
String message = ExceptionConvertorUtils.buildMessage(exception);
return ActionResult.fail(CommonErrorEnum.PARAM_ERROR, message);
}
}

View File

@ -0,0 +1,23 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.BusinessException;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
/**
* 默认的异常处理
* 直接抛出系统异常
*
* @author 是仪
*/
public class DefaultExceptionConvertor implements ExceptionConvertor<Throwable> {
@Override
public ActionResult convert(Throwable exception) {
if (exception instanceof BusinessException) {
BusinessException businessException = (BusinessException)exception;
return ActionResult.fail(businessException.getCode(), businessException.getMessage());
}
return ActionResult.fail(CommonErrorEnum.COMMON_SYSTEM_ERROR);
}
}

View File

@ -0,0 +1,19 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
/**
* 异常转换器
*
* @author 是仪
*/
public interface ExceptionConvertor<T extends Throwable> {
/**
* 转换异常
*
* @param exception
* @return
*/
ActionResult convert(T exception);
}

View File

@ -0,0 +1,164 @@
package com.alibaba.easytools.spring.exception.convertor;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import com.alibaba.easytools.base.constant.SymbolConstant;
import com.alibaba.easytools.base.excption.BusinessException;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
import com.google.common.collect.Maps;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
/**
* 转换工具类
*
* @author 是仪
*/
public class ExceptionConvertorUtils {
/**
* 所有的异常处理转换器
*/
public static final Map<Class<?>, ExceptionConvertor> EXCEPTION_CONVERTOR_MAP = Maps.newHashMap();
static {
EXCEPTION_CONVERTOR_MAP.put(MethodArgumentNotValidException.class,
new MethodArgumentNotValidExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(BindException.class, new BindExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(BusinessException.class, new BusinessExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(MissingServletRequestParameterException.class, new ParamExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(IllegalArgumentException.class, new ParamExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(MethodArgumentTypeMismatchException.class,
new MethodArgumentTypeMismatchExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(MaxUploadSizeExceededException.class,
new MaxUploadSizeExceededExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(HttpRequestMethodNotSupportedException.class, new BusinessExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(ConstraintViolationException.class, new ConstraintViolationExceptionConvertor());
EXCEPTION_CONVERTOR_MAP.put(HttpMessageNotReadableException.class,
new ParamExceptionConvertor());
}
/**
* 默认转换器
*/
public static ExceptionConvertor DEFAULT_EXCEPTION_CONVERTOR = new DefaultExceptionConvertor();
/**
* 提取ConstraintViolationException中的错误消息
*
* @param e
* @return
*/
public static String buildMessage(ConstraintViolationException e) {
if (e == null || CollectionUtils.isEmpty(e.getConstraintViolations())) {
return null;
}
int index = 1;
StringBuilder msg = new StringBuilder();
msg.append("请检查以下信息:");
for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
msg.append(index++);
// 得到错误消息
msg.append(SymbolConstant.DOT);
msg.append(" 字段(");
msg.append(constraintViolation.getPropertyPath());
msg.append(")传入的值为:\"");
msg.append(constraintViolation.getInvalidValue());
msg.append("\",校验失败,原因是:");
msg.append(constraintViolation.getMessage());
msg.append(SymbolConstant.SEMICOLON);
}
return msg.toString();
}
/**
* 提取BindingResult中的错误消息
*
* @param result
* @return
*/
public static String buildMessage(BindingResult result) {
List<ObjectError> errors = result.getAllErrors();
if (CollectionUtils.isEmpty(errors)) {
return null;
}
int index = 1;
StringBuilder msg = new StringBuilder();
msg.append("请检查以下信息:");
for (ObjectError e : errors) {
msg.append(index++);
// 得到错误消息
msg.append(SymbolConstant.DOT);
msg.append(" ");
msg.append("字段(");
msg.append(e.getObjectName());
if (e instanceof FieldError) {
FieldError fieldError = (FieldError)e;
msg.append(SymbolConstant.DOT);
msg.append(fieldError.getField());
}
msg.append(")");
if (e instanceof FieldError) {
FieldError fieldError = (FieldError)e;
msg.append("传入的值为:\"");
msg.append(fieldError.getRejectedValue());
msg.append("\",");
}
msg.append("校验失败,原因是:");
msg.append(e.getDefaultMessage());
msg.append(SymbolConstant.SEMICOLON);
}
return msg.toString();
}
/**
* 拼接头的日志信息
*
* @param request
* @return
*/
public static String buildHeaderString(HttpServletRequest request) {
StringBuilder stringBuilder = new StringBuilder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headName = headerNames.nextElement();
stringBuilder.append(headName);
stringBuilder.append(SymbolConstant.COLON);
stringBuilder.append(request.getHeader(headName));
stringBuilder.append(SymbolConstant.COMMA);
}
return stringBuilder.toString();
}
/**
* 转换结果
*
* @param exception
* @return
*/
public static ActionResult convert(Throwable exception) {
ExceptionConvertor exceptionConvertor = EXCEPTION_CONVERTOR_MAP.get(exception.getClass());
if (exceptionConvertor == null) {
exceptionConvertor = DEFAULT_EXCEPTION_CONVERTOR;
}
return exceptionConvertor.convert(exception);
}
}

View File

@ -0,0 +1,19 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
/**
* MaxUploadSizeExceededException
*
* @author 是仪
*/
public class MaxUploadSizeExceededExceptionConvertor implements ExceptionConvertor<MaxUploadSizeExceededException> {
@Override
public ActionResult convert(MaxUploadSizeExceededException exception) {
return ActionResult.fail(CommonErrorEnum.MAX_UPLOAD_SIZE);
}
}

View File

@ -0,0 +1,20 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
/**
* MethodArgumentNotValidException
*
* @author 是仪
*/
public class MethodArgumentNotValidExceptionConvertor implements ExceptionConvertor<MethodArgumentNotValidException> {
@Override
public ActionResult convert(MethodArgumentNotValidException exception) {
String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult());
return ActionResult.fail(CommonErrorEnum.PARAM_ERROR, message);
}
}

View File

@ -0,0 +1,20 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
/**
* MethodArgumentTypeMismatchException
*
* @author 是仪
*/
public class MethodArgumentTypeMismatchExceptionConvertor
implements ExceptionConvertor<MethodArgumentTypeMismatchException> {
@Override
public ActionResult convert(MethodArgumentTypeMismatchException exception) {
return ActionResult.fail(CommonErrorEnum.PARAM_ERROR, "请输入正确的数据格式");
}
}

View File

@ -0,0 +1,21 @@
package com.alibaba.easytools.spring.exception.convertor;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.wrapper.result.ActionResult;
/**
* 参数异常 目前包括
* ConstraintViolationException
* MissingServletRequestParameterException
* IllegalArgumentException
* HttpMessageNotReadableException
*
* @author 是仪
*/
public class ParamExceptionConvertor implements ExceptionConvertor<Throwable> {
@Override
public ActionResult convert(Throwable exception) {
return ActionResult.fail(CommonErrorEnum.PARAM_ERROR, exception.getMessage());
}
}

View File

@ -0,0 +1,56 @@
package com.alibaba.easytools.spring.oss;
import com.aliyun.oss.model.CannedAccessControlList;
import lombok.Data;
/**
* 计算请求签名对象
*
* @author 是仪
*/
@Data
public class CalculatePostSignature {
/**
* 请求的host
*/
private String host;
/**
* 请求的策略
*/
private String policy;
/**
* 授权id
*/
private String accessId;
/**
* 授权签名
*/
private String signature;
/**
* 过期时间 时间戳
*/
private Long expire;
/**
* 文件上传的key
*/
private String key;
/**
* 文件连接
*/
private String url;
/**
* 权限控制
*
* @see CannedAccessControlList
*/
private String objectAcl;
}

View File

@ -0,0 +1,45 @@
package com.alibaba.easytools.spring.oss;
import com.aliyun.oss.model.CannedAccessControlList;
import lombok.Getter;
/**
* 默认的oss枚举
*
* @author 是仪
*/
@Getter
public enum DefaultOssKindEnum implements OssKindEnum {
/**
* 默认
*/
DEFAULT("default", CannedAccessControlList.Private, null),
// 分号
;
/**
* 样式的格式
*/
final String code;
/**
* 这里只支持 私有每次访问都要授权 比如身份证和共有读获得连接了可以永久访问
*/
final CannedAccessControlList objectAcl;
/**
* 默认的一些处理 目前主要是样式处理
*/
final String process;
DefaultOssKindEnum(String code, CannedAccessControlList objectAcl, String process) {
this.code = code;
this.objectAcl = objectAcl;
this.process = process;
}
@Override
public String getDescription() {
return this.code;
}
}

View File

@ -0,0 +1,826 @@
package com.alibaba.easytools.spring.oss;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import com.alibaba.easytools.base.constant.SymbolConstant;
import com.alibaba.easytools.base.enums.oss.BaseOssKindEnum;
import com.alibaba.easytools.base.enums.oss.OssObjectAclEnum;
import com.alibaba.easytools.base.excption.BusinessException;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.excption.SystemException;
import com.alibaba.easytools.base.wrapper.ObjectWrapper;
import com.alibaba.easytools.common.util.EasyEnumUtils;
import com.alibaba.easytools.common.util.EasyOptionalUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import cn.hutool.core.date.DateUtil;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSErrorCode;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.common.utils.HttpUtil;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.internal.OSSUtils;
import com.aliyun.oss.internal.RequestParameters;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PolicyConditions;
import com.dtflys.forest.Forest;
import com.dtflys.forest.backend.ContentType;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import static com.aliyun.oss.internal.OSSConstants.DEFAULT_CHARSET_NAME;
/**
* 自定义OSS的客户端
*
* @author 是仪
**/
@Slf4j
@Data
public class EasyOssClient {
/**
* 最大为10G
*/
public static long maxContentLength = 10 * 1024 * 1024 * 1024L;
/**
* 最小为0
*/
public static long minContentLength = 0L;
/**
* 超时时间
*/
public static Duration defaultTimeout = Duration.ofDays(1);
public static Duration downloadConnectTimeout = Duration.ofMinutes(1);
public static Duration downloadReadTimeout = Duration.ofHours(1);
/**
* 有些图片不规范 流也可以下载
*/
private static final String STREAM = "application/octet-stream";
/**
* html类型
*/
private static final String HTML = "text/html";
/**
* https前缀
*/
private static final String HTTPS_PREFIX = "https:";
/**
* 如果不传kind 全部放到这里
*/
private static final String DEFAULT_KIND = "default";
/**
* oss枚举的类
*/
private Class<? extends BaseOssKindEnum> ossKindEnumClass;
/**
* 读文件的oss
*/
private OSSClient ossClient;
private String bucketName;
private String endpoint;
private String baseUrl;
/**
* bucketName+endpoint
*/
private String ossDomain;
/**
* 绑定的域名 类似于 oss.alibaba.com 不要带任何前缀
* 可以为空
* 如果为空 则会生成 bucketName+endpoint 的连接
* 如果不为空 则会生成 domain 的连接
*/
private String domain;
/**
* 多媒体项目
*/
private String immProject;
/**
* 是否需要替换域名
* 生成私有的连接的时候 返回的是 bucketName+endpoint
* 这个时候 我们要把替换成 domain ,当然只有domain!=bucketName+endpoint 的情况
*/
private Boolean needReplaceDomain;
/**
* accessKeyId
* 目前只有生成签名的时候需要
*/
private String accessKeyId;
public EasyOssClient(OSSClient ossClient, String bucketName, String endpoint,
Class<? extends BaseOssKindEnum> ossKindEnumClass, String domain) {
this(ossClient, bucketName, endpoint, ossKindEnumClass, domain, null);
}
public EasyOssClient(OSSClient ossClient, String bucketName, String endpoint,
Class<? extends BaseOssKindEnum> ossKindEnumClass, String domain, String accessKeyId) {
this.bucketName = bucketName;
this.endpoint = endpoint;
this.ossKindEnumClass = ossKindEnumClass;
this.ossDomain = bucketName + "." + endpoint;
this.domain = domain;
this.needReplaceDomain = this.domain != null;
// 为空 则拼接domain
if (this.domain == null) {
this.domain = ossDomain;
}
this.baseUrl = "https://" + this.domain + "/";
this.ossClient = ossClient;
this.accessKeyId = accessKeyId;
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file) {
return put(file, null, null, null, null, null, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file, String kind) {
return put(file, kind, null, null, null, null, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则根据file的名字下载 如果还为空 则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 为空则根据file的名字的后缀区分 如果还为空 则默认流application/octet-stream
* @param key 服务器的唯一key
* @param objectAcl 对象权限控制 默认私有的
* @param contentDisposition 是否需要把文件名设置到contentDisposition里面
* 这里默认不设置
* 原因是chrome 在设置了contentDisposition的情况下 有可能是导致图片无法展示特定的文件名这个就很离谱但是实际真的遇到了
* 所以图片的情况下慎用 contentDisposition除非自己指定名字纯中文或者英文问题应该不大
* 其他情况下不需要展示图片的可以直接设置为true
* @return 返回文件的唯一key 可以获取文件url
* @see CannedAccessControlList
*/
public String put(File file, String kind, String fileName, String contentType, String key,
CannedAccessControlList objectAcl, Boolean contentDisposition) {
if (file == null) {
throw new BusinessException("文件不能为空!");
}
if (!file.isFile()) {
throw new BusinessException("不能上传目录!");
}
// 构建用来存储在服务器是上面的唯一目录值
if (StringUtils.isBlank(key)) {
key = buildKey(kind, fileName);
}
// 构建用户下载的时候显示的文件名
String fileNameDownload = buildFileName(file, fileName);
ossClient.putObject(bucketName, key, file,
buildObjectMetadata(fileNameDownload, contentType, objectAcl, contentDisposition, kind));
return key;
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(InputStream inputStream) {
return put(inputStream, null, null, null, null, null, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(InputStream inputStream, String kind) {
return put(inputStream, kind, null, null, null, null, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 默认流application/octet-stream
* @param key 生成服务器文件存储的key 空的场景需要生成
* @param objectAcl 对象权限控制 默认私有的
* @param contentDisposition 是否需要把文件名设置到contentDisposition里面
* 这里默认不设置
* 原因是chrome 在设置了contentDisposition的情况下 有可能是导致图片无法展示特定的文件名这个就很离谱但是实际真的遇到了
* 所以图片的情况下慎用 contentDisposition除非自己指定名字纯中文或者英文问题应该不大
* 其他情况下不需要展示图片的可以直接设置为true
* @return 返回文件的唯一key 可以获取文件url
* @see CannedAccessControlList
*/
public String put(InputStream inputStream, String kind, String fileName, String contentType, String key,
CannedAccessControlList objectAcl, Boolean contentDisposition) {
if (inputStream == null) {
throw new BusinessException("文件流不能为空!");
}
// 构建用来存储在服务器是上面的唯一目录值
if (StringUtils.isBlank(key)) {
key = buildKey(kind, fileName);
}
// 构建用户下载的时候显示的文件名
String fileNameDownload = buildFileName(fileName);
// 上传文件
ossClient.putObject(bucketName, key, inputStream,
buildObjectMetadata(fileNameDownload, contentType, objectAcl, contentDisposition, kind));
return key;
}
/**
* 根据url上传文件
*
* @param url 文件地址 不能为空
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(String url) {
return put(url, null, null, null, null, null, null);
}
/**
* 根据url上传文件
*
* @param url 文件地址 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(String url, String kind) {
return put(url, kind, null, null, null, null, null);
}
/**
* 根据url上传文件
*
* @param url 文件地址 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 默认流application/octet-stream
* @param key 生成服务器文件存储的key 空的场景需要生成
* @param objectAcl 对象权限控制 默认私有的
* @param contentDisposition 是否需要把文件名设置到contentDisposition里面
* 这里默认不设置
* 原因是chrome 在设置了contentDisposition的情况下 有可能是导致图片无法展示特定的文件名这个就很离谱但是实际真的遇到了
* 所以图片的情况下慎用 contentDisposition除非自己指定名字纯中文或者英文问题应该不大
* 其他情况下不需要展示图片的可以直接设置为true
* @return 返回文件的唯一key 可以获取文件url
* @see CannedAccessControlList
*/
public String put(String url, String kind, String fileName, String contentType, String key,
CannedAccessControlList objectAcl, Boolean contentDisposition) {
if (url == null) {
throw new BusinessException("url不能为空");
}
BaseOssKindEnum kindEnum = EasyEnumUtils.getEnum(ossKindEnumClass, kind);
ObjectWrapper<String> finalKey = ObjectWrapper.ofNull();
// 获取图片信息
Forest.get(url)
.setDownloadFile(true)
.connectTimeout(downloadConnectTimeout)
.readTimeout(downloadReadTimeout)
.onSuccess((data, request, response) -> {
ContentType responseContentType = response.getContentType();
String finalContentType = contentType;
if (finalContentType == null && responseContentType != null) {
finalContentType = responseContentType.toString();
}
// 在html & 为空的情况下 默认使用文件流
// 攻击者可以上传恶意的HTMLJS等文件受害者访问之后可能会绕过浏览器的同源限制或者是安全侧的一些域名白名单限制最终导致CSRFXSS等安全漏洞
if (StringUtils.isBlank(finalContentType) || HTML.equalsIgnoreCase(finalContentType)) {
finalContentType = STREAM;
}
// 文件名称
String finalFileName = fileName;
if (finalFileName == null) {
// 获取文件名
finalFileName = response.getFilename();
if (StringUtils.isBlank(finalFileName)) {
//最后的后缀
String lastUrl = url.substring(url.lastIndexOf('/') + 1);
int index = lastUrl.indexOf('?');
if (index > 0) {
finalFileName = lastUrl.substring(0, index);
} else {
finalFileName = lastUrl;
}
}
// 没有后缀
if (!finalFileName.contains(".") && responseContentType != null
&& responseContentType.getSubType() != null) {
finalFileName += "." + responseContentType.getSubType();
}
}
// 构建用户下载的时候显示的文件名
String fileNameDownload = buildFileName(fileName);
// 构建用来存储在服务器是上面的唯一目录值
finalKey.set(buildKey(EasyOptionalUtils.mapTo(kindEnum, BaseOssKindEnum::getCode),
finalFileName));
// 上传文件
try {
// 上传文件
ossClient.putObject(bucketName, finalKey.get(), new ByteArrayInputStream(response.getByteArray()),
buildObjectMetadata(fileNameDownload, finalContentType, objectAcl, contentDisposition, kind));
} catch (Exception e) {
throw BusinessException.of(CommonErrorEnum.FAILED_TO_UPLOAD_FILE, "上传到文件服务器失败:" + url, e);
}
})
.onError((exception, request, response) -> {
throw BusinessException.of(CommonErrorEnum.FAILED_TO_UPLOAD_FILE, "下载文件失败:" + url, exception);
})
.executeAsByteArray();
return finalKey.get();
}
/**
* 根据 key获取文件流
*
* @param key 文件key 在上传的时候获得
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public InputStream get(String key) {
return get(key, null);
}
/**
* 根据 key获取文件流
*
* @param key 文件key 在上传的时候获得
* @param process 格式转换
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public InputStream get(String key, String process) {
return getOssObject(key, process).getObjectContent();
}
/**
* 根据 key获取OSS文件 如果需要获取文件名之类的请用此方法
*
* @param key 文件key 在上传的时候获得
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public OssObject getOssObject(String key) {
return getOssObject(key, null);
}
/**
* 根据 key获取OSS文件 如果需要获取文件名之类的请用此方法
*
* @param key 文件key 在上传的时候获得
* @param process 格式转换
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public OssObject getOssObject(String key, String process) {
if (StringUtils.isBlank(key)) {
throw new BusinessException("获取文件key 不能为空");
}
try {
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
if (StringUtils.isNotBlank(process)) {
getObjectRequest.setProcess(process);
}
return OssObject.buildWithAliyunOssObject(ossClient.getObject(getObjectRequest));
} catch (OSSException e) {
String errorCode = e.getErrorCode();
if (OSSErrorCode.NO_SUCH_KEY.equals(errorCode)) {
throw new BusinessException("找不到指定文件");
}
throw new BusinessException(e.getMessage(), e);
}
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @return 一个可以访问的url
*/
public String getUrl(String key) {
return getUrl(key, null, null);
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @param process 格式转换
* @return 一个可以访问的url
*/
public String getUrl(String key, String process) {
return getUrl(key, null, process);
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @param duration 超时时间 可以为空 共有读的不管传不传 都是永久的 私有的不传默认1天
* @return 一个可以访问的url
*/
public String getUrl(String key, Duration duration) {
return getUrl(key, duration, null);
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @param duration 超时时间 可以为空 共有读的不管传不传 都是永久的 私有的不传默认1天
* @param process 格式转换
* @return 一个可以访问的url
*/
public String getUrl(String key, Duration duration, String process) {
if (StringUtils.isBlank(key)) {
return null;
}
String kind = StringUtils.split(key, SymbolConstant.SLASH)[0];
BaseOssKindEnum kindEnum = EasyEnumUtils.getEnum(ossKindEnumClass, kind);
// 使用默认的样式处理
if (process == null) {
process = kindEnum.getProcess();
}
// 代表是公有读 直接拼接连接即可
if (kindEnum != null && kindEnum.getOssObjectAcl() == OssObjectAclEnum.PUBLIC_READ) {
Map<String, String> params = new LinkedHashMap<>();
if (StringUtils.isNotBlank(process)) {
params.put(RequestParameters.SUBRESOURCE_PROCESS, process);
}
if (MapUtils.isEmpty(params)) {
return baseUrl + OSSUtils.determineResourcePath(bucketName, key,
ossClient.getClientConfiguration().isSLDEnabled());
} else {
String queryString = HttpUtil.paramToQueryString(params, DEFAULT_CHARSET_NAME);
return baseUrl + OSSUtils.determineResourcePath(bucketName, key,
ossClient.getClientConfiguration().isSLDEnabled()) + "?" + queryString;
}
}
// 设置默认超时时间
if (duration == null) {
duration = defaultTimeout;
}
Date expirationDate = new Date(System.currentTimeMillis() + duration.getSeconds() * 1000);
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, key);
generatePresignedUrlRequest.setExpiration(expirationDate);
if (StringUtils.isNotBlank(process)) {
generatePresignedUrlRequest.setProcess(process);
}
URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);
String urlString = url.toString();
if (needReplaceDomain) {
urlString = urlString.replaceFirst(ossDomain, domain);
}
return urlString;
}
/**
* 根据url生成key
*
* @param url 一个可以访问的url
* @return 唯一key
*/
public String urlToKey(String url) {
if (StringUtils.isBlank(url)) {
throw new BusinessException("url转key url不能为空");
}
try {
URL urlObject = new URL(url);
String path = urlObject.getPath();
return path.substring(1);
} catch (Exception e) {
throw new BusinessException("请传入正确的url");
}
}
/**
* 根据key 移除文件
*
* @param key
*/
public void delete(String key) {
ossClient.deleteObject(bucketName, key);
}
/**
* 构建 请求参数
*
* @param fileNameDownload
* @param contentType
* @return
*/
private ObjectMetadata buildObjectMetadata(String fileNameDownload, String contentType,
CannedAccessControlList objectAcl, Boolean contentDisposition, String kind) {
if (objectAcl == null) {
BaseOssKindEnum kindEnum = EasyEnumUtils.getEnum(ossKindEnumClass, kind);
if (kindEnum != null && kindEnum.getOssObjectAcl() == OssObjectAclEnum.PUBLIC_READ) {
objectAcl = CannedAccessControlList.PublicRead;
} else {
objectAcl = CannedAccessControlList.Private;
}
}
if (objectAcl != CannedAccessControlList.Private && objectAcl != CannedAccessControlList.PublicRead) {
throw new SystemException("目前仅仅支持私有和共有读类型。");
}
ObjectMetadata objectMetadata = new ObjectMetadata();
if (StringUtils.isBlank(contentType)) {
contentType = Mimetypes.getInstance().getMimetype(fileNameDownload);
}
objectMetadata.setContentType(contentType);
// 判断是否要设置 contentDisposition
if (BooleanUtils.isTrue(contentDisposition)) {
try {
// 防止中文乱码
fileNameDownload = URLEncoder.encode(fileNameDownload, StandardCharsets.UTF_8.name()).replaceAll("\\+",
"%20");
} catch (UnsupportedEncodingException e) {
throw new SystemException(CommonErrorEnum.COMMON_SYSTEM_ERROR, "不支持的字符编码", e);
}
objectMetadata.setContentDisposition("filename*=utf-8''" + fileNameDownload);
}
// 默认私有
objectMetadata.setObjectAcl(objectAcl);
return objectMetadata;
}
/**
* 用来 下载时 显示给用户的名称
*
* @param fileName
* @return
*/
private String buildFileName(String fileName) {
if (StringUtils.isNotBlank(fileName)) {
return fileName;
}
return UUID.randomUUID().toString();
}
/**
* 用来 下载时 显示给用户的名称
*
* @param file
* @param fileName
* @return
*/
private String buildFileName(File file, String fileName) {
if (StringUtils.isNotBlank(fileName)) {
return fileName;
}
fileName = file.getName();
if (StringUtils.isNotBlank(fileName)) {
return fileName;
}
return UUID.randomUUID().toString();
}
/**
* 生成服务器文件存储的key 统一看不到名字 看不到后缀
* 有些场景需要提前生成唯一存储key
*
* @param kind
* @param fileName
* @return
*/
public String buildKey(String kind, String fileName) {
if (StringUtils.isBlank(kind)) {
kind = DEFAULT_KIND;
}
StringBuilder key = new StringBuilder();
key.append(kind);
key.append("/");
Calendar calendar = Calendar.getInstance();
key.append(calendar.get(Calendar.YEAR));
key.append("/");
key.append(StringUtils.leftPad(Integer.toString(calendar.get(Calendar.MONTH) + 1), 2, "0"));
key.append("/");
key.append(UUID.randomUUID());
Optional.ofNullable(fileName).map(fileNameData -> {
int lastDotIndex = fileNameData.lastIndexOf(SymbolConstant.DOT);
if (lastDotIndex >= 0) {
return fileNameData.substring(lastDotIndex);
}
return null;
}).ifPresent(key::append);
return key.toString();
}
/**
* 将一个html中需要授权的资源替换成授权完成
*
* @param html html文本
* @param ossKindEnum 需要处理的对象的oss 枚举
* @return 授权完成的html
*/
public String htmlSourceAuthorization(String html, OssKindEnum ossKindEnum) {
if (StringUtils.isBlank(html)) {
return html;
}
Document doc = Jsoup.parse(html);
// 查找所有image 标签
Elements imgs = doc.getElementsByTag("img");
for (Element img : imgs) {
img.attr("src",
getUrlFromUrl(img.attr("src"), EasyOptionalUtils.mapTo(ossKindEnum, OssKindEnum::getProcess)));
}
doc.outputSettings().prettyPrint(false);
return doc.body().html();
}
/**
* 将一个json中需要授权的资源替换成授权完成
*
* 这里注意 这个json 文本很奇怪类似于["root",{},["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"测试"],
* ["span",{"italic":true,"data-type":"leaf"},"文本"]]]]
*
* @param json json文本
* @param ossKindEnum 需要处理的对象的oss 枚举
* @return 授权完成的html
*/
public String jsonSourceAuthorization(String json, OssKindEnum ossKindEnum) {
if (StringUtils.isBlank(json)) {
return json;
}
JSONArray jsonArray = JSON.parseArray(json);
// 给资源授权
jsonSourceAuthorization(jsonArray, ossKindEnum);
return jsonArray.toString();
}
private void jsonSourceAuthorization(JSONArray jsonArray, OssKindEnum ossKindEnum) {
// 如果小于2级的标签 需要忽略
if (jsonArray.size() < 2) {
return;
}
// 递归 第三个及其以后的节点
for (int i = 2; i < jsonArray.size(); i++) {
Object data = jsonArray.get(i);
// 可能是 字符串 也是可能是数组
// 这里只需要考虑是数组的情况
if (data instanceof JSONArray) {
jsonSourceAuthorization((JSONArray)data, ossKindEnum);
}
}
// 处理本节点的数据
// 第0个数组代表标签名字
String tagName = jsonArray.getString(0);
//代表不是图片
if (!"img".equals(tagName)) {
return;
}
// 第1个数组代表 样式
JSONObject style = jsonArray.getJSONObject(1);
if (style == null) {
return;
}
style.put("src",
getUrlFromUrl(style.getString("src"), EasyOptionalUtils.mapTo(ossKindEnum, OssKindEnum::getProcess)));
}
/**
* 给一个路径资源授权
*
* @param url 一个url
* @param process process处理
* @return
*/
public String getUrlFromUrl(String url, String process) {
if (StringUtils.isBlank(url)) {
return url;
}
try {
// 有空可能没有http 协议 默认https
if (url.startsWith("//")) {
url = HTTPS_PREFIX + url;
}
URL sourceUrl = new URL(url);
// 代表不是当前 环境的资源
if (!domain.equals(sourceUrl.getHost()) && !ossDomain.equals(sourceUrl.getHost())) {
return url;
}
return getUrl(sourceUrl.getPath().substring(1), process);
} catch (Exception e) {
log.error("错误的路径:{}", url, e);
return url;
}
}
/**
* 生成一个指定类型的文件上传签名
*
* @param kind 文件类型
* @param name 文件名
* @return
*/
public CalculatePostSignature calculatePostSignature(String kind, String name) {
BaseOssKindEnum kindEnum = EasyEnumUtils.getEnum(ossKindEnumClass, kind);
if (kindEnum == null) {
throw new BusinessException("不支持当前文件的上传类型:" + kind);
}
CalculatePostSignature calculatePostSignature = new CalculatePostSignature();
// 放入cal
calculatePostSignature.setObjectAcl(kindEnum.getOssObjectAcl().getOssAcl());
// 获取请求的策略
// 默认1小时有效期
Date expiration = DateUtil.offsetHour(DateUtil.date(), 1);
calculatePostSignature.setExpire(expiration.getTime());
// 计算文件的key
calculatePostSignature.setKey(buildKey(kind, name));
// 设置文件大小
PolicyConditions policyConditions = new PolicyConditions();
policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, minContentLength,
maxContentLength);
policyConditions.addConditionItem(MatchMode.Exact, PolicyConditions.COND_KEY,
calculatePostSignature.getKey());
String postPolicy = ossClient.generatePostPolicy(expiration, policyConditions);
// 签名
calculatePostSignature.setSignature(ossClient.calculatePostSignature(postPolicy));
// 设置编码后的Policy
byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
String encodedPostPolicy = BinaryUtil.toBase64String(binaryData);
calculatePostSignature.setPolicy(encodedPostPolicy);
// host的格式为 bucketname.endpoint
calculatePostSignature.setHost("https://" + bucketName + "." + endpoint);
// 获取accessId
calculatePostSignature.setAccessId(accessKeyId);
calculatePostSignature.setUrl(getUrl(calculatePostSignature.getKey()));
return calculatePostSignature;
}
}

View File

@ -0,0 +1,759 @@
package com.alibaba.easytools.spring.oss;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import com.alibaba.easytools.base.constant.SymbolConstant;
import com.alibaba.easytools.base.excption.BusinessException;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.excption.SystemException;
import com.alibaba.easytools.common.util.EasyEnumUtils;
import com.alibaba.easytools.common.util.EasyOptionalUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSErrorCode;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.HttpUtil;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.internal.OSSUtils;
import com.aliyun.oss.internal.RequestParameters;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.imm.model.v20170906.CreateOfficeConversionTaskRequest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import static com.aliyun.oss.internal.OSSConstants.DEFAULT_CHARSET_NAME;
/**
* 自定义OSS的客户端
*
* @author 是仪
* @deprecated 使用 EasyOssClient
**/
@Slf4j
@Data
@Deprecated
public class OssClient {
/**
* https前缀
*/
private static final String HTTPS_PREFIX = "https:";
/**
* 如果不传kind 全部放到这里
*/
private static final String DEFAULT_KIND = "default";
/**
* 预览文件的后缀
*/
public static final String PREVIEW_SRC_SUFFIX = "_preview";
/**
* oss枚举的类
*/
private Class<? extends OssKindEnum> ossKindEnumClass;
/**
* 读文件的oss
*/
private OSSClient readOss;
/**
* 写文件的oss
* 可能会使用 internalEndpoint
*/
private OSSClient writeOss;
private String projectName;
private String bucketName;
private String endpoint;
private String baseUrl;
/**
* bucketName+endpoint
*/
private String ossDomain;
/**
* 绑定的域名 类似于 oss.alibaba.com 不要带任何前缀
* 可以为空
* 如果为空 则会生成 bucketName+endpoint 的连接
* 如果不为空 则会生成 domain 的连接
*/
private String domain;
/**
* 处理预览的时候有用
*/
private IAcsClient iAcsClient;
/**
* 多媒体项目
*/
private String immProject;
/**
* 是否需要替换域名
* 生成私有的连接的时候 返回的是 bucketName+endpoint
* 这个时候 我们要把替换成 domain ,当然只有domain!=bucketName+endpoint 的情况
*/
private Boolean needReplaceDomain;
public OssClient(OSSClient oss, String projectName, String bucketName, String endpoint,
Class<? extends OssKindEnum> ossKindEnumClass) {
this(projectName, bucketName, endpoint, ossKindEnumClass, null, null, null);
this.readOss = oss;
this.writeOss = oss;
}
/**
* 构建oss
*
* @param projectName 项目名 多个项目公用一个oss 可以传值
* @param bucketName
* @param endpoint
* @param ossKindEnumClass
* @param iAcsClient
* @param immProject
* @param domain
*/
public OssClient(String projectName, String bucketName, String endpoint,
Class<? extends OssKindEnum> ossKindEnumClass, IAcsClient iAcsClient, String immProject, String domain) {
this.projectName = projectName;
this.iAcsClient = iAcsClient;
this.bucketName = bucketName;
this.endpoint = endpoint;
if (ossKindEnumClass != null) {
this.ossKindEnumClass = ossKindEnumClass;
} else {
this.ossKindEnumClass = DefaultOssKindEnum.class;
}
this.immProject = immProject;
this.ossDomain = bucketName + "." + endpoint;
this.domain = domain;
this.needReplaceDomain = this.domain != null;
// 为空 则拼接domain
if (this.domain == null) {
this.domain = ossDomain;
}
this.baseUrl = "https://" + this.domain + "/";
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file) {
return put(file, null, null, null, null, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file, String kind) {
return put(file, kind, null, null, null, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param ossKindEnum 文件种类
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file, OssKindEnum ossKindEnum) {
return put(file, ossKindEnum.getCode(), null, null, null, ossKindEnum.getObjectAcl());
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则根据file的名字下载 如果还为空 则自动生成uuid
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file, String kind, String fileName) {
return put(file, kind, fileName, null, null, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则根据file的名字下载 如果还为空 则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 为空则根据file的名字的后缀区分 如果还为空 则默认流application/octet-stream
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(File file, String kind, String fileName, String contentType) {
return put(file, kind, fileName, contentType, null, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param fileName 用户下载时候的文件名 可以为空 为空则根据file的名字下载 如果还为空 则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 为空则根据file的名字的后缀区分 如果还为空 则默认流application/octet-stream
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(String key, File file, String fileName, String contentType) {
return put(file, null, fileName, contentType, key, null);
}
/**
* 上传文件
*
* @param file 文件 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则根据file的名字下载 如果还为空 则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 为空则根据file的名字的后缀区分 如果还为空 则默认流application/octet-stream
* @param key 服务器的唯一key
* @param objectAcl 对象权限控制 默认私有的
* @return 返回文件的唯一key 可以获取文件url
* @see CannedAccessControlList
*/
public String put(File file, String kind, String fileName, String contentType, String key,
CannedAccessControlList objectAcl) {
if (file == null) {
throw new BusinessException("文件不能为空!");
}
if (!file.isFile()) {
throw new BusinessException("不能上传目录!");
}
// 构建用来存储在服务器是上面的唯一目录值
if (StringUtils.isBlank(key)) {
key = buildKey(kind, fileName);
}
// 构建用户下载的时候显示的文件名
String fileNameDownload = buildFileName(file, fileName);
writeOss.putObject(bucketName, key, file, buildObjectMetadata(fileNameDownload, contentType, objectAcl));
return key;
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(InputStream inputStream) {
return put(inputStream, null, null, null, null, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(InputStream inputStream, String kind) {
return put(inputStream, kind, null, null, null, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则自动生成uuid
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(InputStream inputStream, String kind, String fileName) {
return put(inputStream, kind, fileName, null, null, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 默认流application/octet-stream
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(InputStream inputStream, String kind, String fileName, String contentType) {
return put(inputStream, kind, fileName, contentType, null, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param fileName 用户下载时候的文件名 可以为空 为空则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 默认流application/octet-stream
* @return 返回文件的唯一key 可以获取文件url
*/
public String put(String key, InputStream inputStream, String fileName, String contentType) {
return put(inputStream, null, fileName, contentType, key, null);
}
/**
* 根据文件流上传文件
*
* @param inputStream 文件流 不能为空
* @param kind 文件种类 可以为空 在每个项目创建OSSKindConstants 尽量不用重复 为空默认放在default下面
* @param fileName 用户下载时候的文件名 可以为空 为空则自动生成uuid
* @param contentType 文件类型 可以为空 用户打开时候浏览器的contentType 为空优先根据传入的fileName的后缀区分 如果fileName没有传入则
* 默认流application/octet-stream
* @param key 生成服务器文件存储的key 空的场景需要生成
* @param objectAcl 对象权限控制 默认私有的
* @return 返回文件的唯一key 可以获取文件url
* @see CannedAccessControlList
*/
public String put(InputStream inputStream, String kind, String fileName, String contentType, String key,
CannedAccessControlList objectAcl) {
if (inputStream == null) {
throw new BusinessException("文件流不能为空!");
}
// 构建用来存储在服务器是上面的唯一目录值
if (StringUtils.isBlank(key)) {
key = buildKey(kind, fileName);
}
// 构建用户下载的时候显示的文件名
String fileNameDownload = buildFileName(fileName);
// 上传文件
writeOss.putObject(bucketName, key, inputStream, buildObjectMetadata(fileNameDownload, contentType, objectAcl));
return key;
}
/**
* 根据 key获取文件流
*
* @param key 文件key 在上传的时候获得
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public InputStream get(String key) {
return get(key, null);
}
/**
* 根据 key获取文件流
*
* @param key 文件key 在上传的时候获得
* @param process 格式转换
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public InputStream get(String key, String process) {
return getOssObject(key, process).getObjectContent();
}
/**
* 根据 key获取OSS文件 如果需要获取文件名之类的请用此方法
*
* @param key 文件key 在上传的时候获得
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public OssObject getOssObject(String key) {
return getOssObject(key, null);
}
/**
* 根据 key获取OSS文件 如果需要获取文件名之类的请用此方法
*
* @param key 文件key 在上传的时候获得
* @param process 格式转换
* @return 返回文件流 如果不存在 则会抛出RuntimeException
*/
public OssObject getOssObject(String key, String process) {
if (StringUtils.isBlank(key)) {
throw new BusinessException("获取文件key 不能为空");
}
try {
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
if (StringUtils.isNotBlank(process)) {
getObjectRequest.setProcess(process);
}
return OssObject.buildWithAliyunOssObject(readOss.getObject(getObjectRequest));
} catch (OSSException e) {
String errorCode = e.getErrorCode();
if (OSSErrorCode.NO_SUCH_KEY.equals(errorCode)) {
throw new BusinessException("找不到指定文件");
}
throw new BusinessException(e.getMessage(), e);
}
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @return 一个可以访问的url
*/
public String getUrl(String key) {
return getUrl(key, null, null);
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @param process 格式转换
* @return 一个可以访问的url
*/
public String getUrl(String key, String process) {
return getUrl(key, null, null, process);
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @param expiration 超时时间 可以为空 共有读的不管传不传 都是永久的 私有的不传默认1小时
* @param timeUnit 超时时间单位 和超时时间一起使用
* @return 一个可以访问的url
*/
public String getUrl(String key, Long expiration, TimeUnit timeUnit) {
return getUrl(key, expiration, timeUnit, null);
}
/**
* 根据Key 获取url
*
* @param key 文件key 在上传的时候获得
* @param expiration 超时时间 可以为空 共有读的不管传不传 都是永久的 私有的不传默认1小时
* @param timeUnit 超时时间单位 和超时时间一起使用
* @param process 格式转换
* @return 一个可以访问的url
*/
public String getUrl(String key, Long expiration, TimeUnit timeUnit, String process) {
if (StringUtils.isBlank(key)) {
return null;
}
String kind = StringUtils.split(key, SymbolConstant.SLASH)[0];
OssKindEnum kindEnum = EasyEnumUtils.getEnum(ossKindEnumClass, kind);
// 使用默认的样式处理
if (process == null) {
process = kindEnum.getProcess();
}
// 代表是公有读 直接拼接连接即可
if (kindEnum != null && kindEnum.getObjectAcl() == CannedAccessControlList.PublicRead) {
Map<String, String> params = new LinkedHashMap<>();
if (StringUtils.isNotBlank(process)) {
params.put(RequestParameters.SUBRESOURCE_PROCESS, process);
}
if (MapUtils.isEmpty(params)) {
return baseUrl + OSSUtils.determineResourcePath(bucketName, key,
readOss.getClientConfiguration().isSLDEnabled());
} else {
String queryString = HttpUtil.paramToQueryString(params, DEFAULT_CHARSET_NAME);
return baseUrl + OSSUtils.determineResourcePath(bucketName, key,
readOss.getClientConfiguration().isSLDEnabled()) + "?" + queryString;
}
}
// 设置默认超时时间
if (expiration == null) {
expiration = 5L;
timeUnit = TimeUnit.MINUTES;
} else {
if (timeUnit == null) {
throw new BusinessException("超时时间和超时时间单位必须同时为空或者同时不为空");
}
}
Date expirationDate = new Date(System.currentTimeMillis() + timeUnit.toMillis(expiration));
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, key);
generatePresignedUrlRequest.setExpiration(expirationDate);
if (StringUtils.isNotBlank(process)) {
generatePresignedUrlRequest.setProcess(process);
}
URL url = readOss.generatePresignedUrl(generatePresignedUrlRequest);
String urlString = url.toString();
if (needReplaceDomain) {
urlString = urlString.replaceFirst(ossDomain, domain);
}
return urlString;
}
/**
* 创建一个转换office 的任务过一段时间会自动完成
*
* @param key
*/
public void createOfficeConversionTask(String key) {
String srcUri = "oss://" + bucketName + "/" + key;
CreateOfficeConversionTaskRequest createOfficeConversionTaskRequest = new CreateOfficeConversionTaskRequest();
createOfficeConversionTaskRequest.setProject(immProject);
createOfficeConversionTaskRequest.setSrcUri(srcUri);
createOfficeConversionTaskRequest.setTgtType("vector");
createOfficeConversionTaskRequest.setTgtUri(srcUri + PREVIEW_SRC_SUFFIX);
try {
iAcsClient.getAcsResponse(createOfficeConversionTaskRequest);
} catch (ClientException e) {
throw new SystemException("创建转换office任务失败.", e);
}
}
/**
* 根据url生成key
*
* @param url 一个可以访问的url
* @return 唯一key
*/
public String urlToKey(String url) {
if (StringUtils.isBlank(url)) {
throw new BusinessException("url转key url不能为空");
}
try {
URL urlObject = new URL(url);
String path = urlObject.getPath();
return path.substring(1);
} catch (Exception e) {
throw new BusinessException("请传入正确的url");
}
}
/**
* 根据key 移除文件
*
* @param key
*/
public void delete(String key) {
writeOss.deleteObject(bucketName, key);
}
/**
* 构建 请求参数
*
* @param fileNameDownload
* @param contentType
* @return
*/
private ObjectMetadata buildObjectMetadata(String fileNameDownload, String contentType,
CannedAccessControlList objectAcl) {
if (objectAcl == null) {
objectAcl = CannedAccessControlList.Private;
}
if (objectAcl != CannedAccessControlList.Private && objectAcl != CannedAccessControlList.PublicRead) {
throw new SystemException("目前仅仅支持私有和共有读类型。");
}
ObjectMetadata objectMetadata = new ObjectMetadata();
if (StringUtils.isBlank(contentType)) {
contentType = Mimetypes.getInstance().getMimetype(fileNameDownload);
}
objectMetadata.setContentType(contentType);
try {
// 防止中文乱码
fileNameDownload = URLEncoder.encode(fileNameDownload, StandardCharsets.UTF_8.name()).replaceAll("\\+",
"%20");
} catch (UnsupportedEncodingException e) {
throw new SystemException(CommonErrorEnum.COMMON_SYSTEM_ERROR, "不支持的字符编码", e);
}
objectMetadata.setContentDisposition("filename*=utf-8''" + fileNameDownload);
// 默认私有
objectMetadata.setObjectAcl(objectAcl);
return objectMetadata;
}
/**
* 用来 下载时 显示给用户的名称
*
* @param fileName
* @return
*/
private String buildFileName(String fileName) {
if (StringUtils.isNotBlank(fileName)) {
return fileName;
}
return UUID.randomUUID().toString();
}
/**
* 用来 下载时 显示给用户的名称
*
* @param file
* @param fileName
* @return
*/
private String buildFileName(File file, String fileName) {
if (StringUtils.isNotBlank(fileName)) {
return fileName;
}
fileName = file.getName();
if (StringUtils.isNotBlank(fileName)) {
return fileName;
}
return UUID.randomUUID().toString();
}
/**
* 生成服务器文件存储的key 统一看不到名字 看不到后缀
* 有些场景需要提前生成唯一存储key
*
* @param kind
* @param fileName
* @return
*/
public String buildKey(String kind, String fileName) {
if (StringUtils.isBlank(kind)) {
kind = DEFAULT_KIND;
}
StringBuilder key = new StringBuilder();
if (StringUtils.isNotBlank(projectName)) {
key.append(projectName);
key.append("/");
}
key.append(kind);
key.append("/");
Calendar calendar = Calendar.getInstance();
key.append(calendar.get(Calendar.YEAR));
key.append("/");
key.append(StringUtils.leftPad(Integer.toString(calendar.get(Calendar.MONTH) + 1), 2, "0"));
key.append("/");
key.append(UUID.randomUUID());
Optional.ofNullable(fileName).map(fileNameData -> {
int lastDotIndex = fileNameData.lastIndexOf(SymbolConstant.DOT);
if (lastDotIndex >= 0) {
return fileNameData.substring(lastDotIndex);
}
return null;
}).ifPresent(key::append);
return key.toString();
}
/**
* 将一个html中需要授权的资源替换成授权完成
*
* @param html html文本
* @param ossKindEnum 需要处理的对象的oss 枚举
* @return 授权完成的html
*/
public String htmlSourceAuthorization(String html, OssKindEnum ossKindEnum) {
if (StringUtils.isBlank(html)) {
return html;
}
Document doc = Jsoup.parse(html);
// 查找所有image 标签
Elements imgs = doc.getElementsByTag("img");
for (Element img : imgs) {
img.attr("src",
getUrlFromUrl(img.attr("src"), EasyOptionalUtils.mapTo(ossKindEnum, OssKindEnum::getProcess)));
}
doc.outputSettings().prettyPrint(false);
return doc.body().html();
}
/**
* 将一个json中需要授权的资源替换成授权完成
*
* 这里注意 这个json 文本很奇怪类似于["root",{},["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"测试"],
* ["span",{"italic":true,"data-type":"leaf"},"文本"]]]]
*
* @param json json文本
* @param ossKindEnum 需要处理的对象的oss 枚举
* @return 授权完成的html
*/
public String jsonSourceAuthorization(String json, OssKindEnum ossKindEnum) {
if (StringUtils.isBlank(json)) {
return json;
}
JSONArray jsonArray = JSON.parseArray(json);
// 给资源授权
jsonSourceAuthorization(jsonArray, ossKindEnum);
return jsonArray.toString();
}
private void jsonSourceAuthorization(JSONArray jsonArray, OssKindEnum ossKindEnum) {
// 如果小于2级的标签 需要忽略
if (jsonArray.size() < 2) {
return;
}
// 递归 第三个及其以后的节点
for (int i = 2; i < jsonArray.size(); i++) {
Object data = jsonArray.get(i);
// 可能是 字符串 也是可能是数组
// 这里只需要考虑是数组的情况
if (data instanceof JSONArray) {
jsonSourceAuthorization((JSONArray)data, ossKindEnum);
}
}
// 处理本节点的数据
// 第0个数组代表标签名字
String tagName = jsonArray.getString(0);
//代表不是图片
if (!"img".equals(tagName)) {
return;
}
// 第1个数组代表 样式
JSONObject style = jsonArray.getJSONObject(1);
if (style == null) {
return;
}
style.put("src",
getUrlFromUrl(style.getString("src"), EasyOptionalUtils.mapTo(ossKindEnum, OssKindEnum::getProcess)));
}
/**
* 给一个路径资源授权
*
* @param url 一个url
* @param process process处理
* @return
*/
public String getUrlFromUrl(String url, String process) {
if (StringUtils.isBlank(url)) {
return url;
}
try {
// 有空可能没有http 协议 默认https
if (url.startsWith("//")) {
url = HTTPS_PREFIX + url;
}
URL sourceUrl = new URL(url);
// 代表不是当前 环境的资源
if (!domain.equals(sourceUrl.getHost()) && !ossDomain.equals(sourceUrl.getHost())) {
return url;
}
return getUrl(sourceUrl.getPath().substring(1), process);
} catch (Exception e) {
log.error("错误的路径:{}", url, e);
return url;
}
}
}

View File

@ -0,0 +1,27 @@
package com.alibaba.easytools.spring.oss;
import com.alibaba.easytools.base.enums.BaseEnum;
import com.aliyun.oss.model.CannedAccessControlList;
/**
* oss枚举
*
* @author 是仪
*/
public interface OssKindEnum extends BaseEnum<String> {
/**
* 获取权限控制
*
* @return
*/
CannedAccessControlList getObjectAcl();
/**
* 样式处理
*
* @return
*/
String getProcess();
}

View File

@ -0,0 +1,157 @@
package com.alibaba.easytools.spring.oss;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import com.alibaba.easytools.base.excption.CommonErrorEnum;
import com.alibaba.easytools.base.excption.SystemException;
import com.aliyun.oss.model.OSSObject;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
/**
* oss返回对象
*
* @author 是仪
**/
@Data
public class OssObject implements Closeable {
/**
* 文件描述的文件名
*/
private static final String NORMAL_FILENAME = "filename=";
/**
* "RFC 5987"文件描述的文件名
*/
private static final String FILENAME = "filename*=";
/**
* utf8的前缀
*/
public static final String UTF8_PREFIX = "utf-8''";
/**
* 默认文价类型
*/
private static final String DEFAULT_SUFFIX = "data";
/**
* 件key
*/
private String key;
/**
* 原文件名 上传文件时候的文件名 如果为空 返回uuid
*/
private String originalFileName;
/**
* 原文件类型的后缀 如果没有指定 默认.data
*/
private String originalFileSuffix;
/**
* 对象实体
*/
private InputStream objectContent;
/**
* 对象实体长度
*/
private Long objectContentLength;
/**
* 对象实体类型
*/
private String objectContentType;
/**
* 对象展示类型
*/
private String objectContentDisposition;
public static OssObject buildWithAliyunOssObject(OSSObject aliyunOssObject) {
OssObject ossObject = new OssObject();
if (aliyunOssObject == null) {
return ossObject;
}
ossObject.setKey(aliyunOssObject.getKey());
ossObject.setObjectContent(aliyunOssObject.getObjectContent());
ossObject.setObjectContentLength(aliyunOssObject.getObjectMetadata().getContentLength());
ossObject.setObjectContentType(aliyunOssObject.getObjectMetadata().getContentType());
ossObject.setObjectContentDisposition(aliyunOssObject.getObjectMetadata().getContentDisposition());
String contentDisposition = aliyunOssObject.getObjectMetadata().getContentDisposition();
// 文件名
String originalFileName = getFilename(contentDisposition);
if (originalFileName != null) {
ossObject.setOriginalFileName(originalFileName);
// 从文件名解析文件后缀
int fileSuffixIndex = StringUtils.lastIndexOf(originalFileName, ".");
if (fileSuffixIndex != -1) {
// 文件的后缀
String serverFileSuffix = originalFileName.substring(fileSuffixIndex + 1);
ossObject.setOriginalFileSuffix(serverFileSuffix);
}
}
return ossObject;
}
/**
* 从contentDisposition获取文件名
*
* @param contentDisposition
* @return
*/
private static String getFilename(String contentDisposition) {
int originalFileNameIndex = StringUtils.indexOf(contentDisposition, FILENAME);
if (originalFileNameIndex != -1) {
// 获取的文件名全称
String originalFileName = contentDisposition.substring(originalFileNameIndex + FILENAME.length());
if (originalFileName.startsWith(OssObject.UTF8_PREFIX)) {
originalFileName = originalFileName.substring(OssObject.UTF8_PREFIX.length());
try {
originalFileName = URLDecoder.decode(originalFileName, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new SystemException(CommonErrorEnum.COMMON_SYSTEM_ERROR, "不支持的字符编码", e);
}
}
return originalFileName;
}
originalFileNameIndex = StringUtils.indexOf(contentDisposition, NORMAL_FILENAME);
if (originalFileNameIndex != -1) {
String originalFileName = contentDisposition.substring(originalFileNameIndex + NORMAL_FILENAME.length());
try {
originalFileName = URLDecoder.decode(originalFileName, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new SystemException(CommonErrorEnum.COMMON_SYSTEM_ERROR, "不支持的字符编码", e);
}
return originalFileName;
}
return null;
}
public String getOriginalFileName(String defaultFileName) {
if (StringUtils.isEmpty(originalFileName)) {
return defaultFileName;
}
return originalFileName;
}
public String getOriginalFileName() {
return getOriginalFileName(getKey());
}
public String getOriginalFileSuffix() {
return originalFileSuffix;
}
@Override
public void close() throws IOException {
if (objectContent != null) {
objectContent.close();
}
}
}

View File

@ -0,0 +1,65 @@
<?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>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>easytools-common</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-base</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,171 @@
package com.alibaba.atasuper.api.demo;
import java.util.List;
import javax.annotation.Nullable;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.param.DemoComprehensivePageQueryParam;
import com.alibaba.atasuper.api.demo.param.DemoCreateParam;
import com.alibaba.atasuper.api.demo.param.DemoPageQueryParam;
import com.alibaba.atasuper.api.demo.param.DemoQueryParam;
import com.alibaba.atasuper.api.demo.param.DemoSelector;
import com.alibaba.atasuper.api.demo.param.DemoUpdateParam;
import com.alibaba.atasuper.tools.base.exception.DataNotExistsBusinessException;
import com.alibaba.atasuper.tools.base.exception.NoPermissionBusinessException;
import com.alibaba.atasuper.tools.base.wrapper.result.AtaPageResult;
/**
* TODO 模板的service 方法用到才复制 别全复制 参考<a href="https://aliyuque.antfin.com/ctoo-pic/rd/ixq0lu">...</a>
*
* 思考
* 关于 create vs createWithPermission 修改类似
* 如果仅仅只是获取用户登陆信息 可以直接用create
* 如果需要获取用户登陆信息还需要获取用户的权限信息必须用createWithPermission
*
* @author 是仪
*/
public interface DemoCoreService {
/**
* 创建一条数据
*
* @param param 创建参数
* @return id
*/
Long create(@Valid @NotNull DemoCreateParam param);
/**
* 创建一条数据没有权限会抛出异常
*
* @param param 创建参数
* @return id
* @throws NoPermissionBusinessException 没有查询这条数据的权限
*/
Long createWithPermission(@Valid @NotNull DemoCreateParam param);
/**
* 根据主键修改一条数据
*
* @param param 修改参数
* @return id
*/
Long update(@Valid @NotNull DemoUpdateParam param);
/**
* 根据主键修改一条数据没有权限会抛出异常
*
* @param param 修改参数
* @return id
* @throws NoPermissionBusinessException 没有修改这条数据的权限
*/
Long updateWithPermission(@Valid @NotNull DemoUpdateParam param);
/**
* 根据主键删除一条数据
*
* @param id 主键id
* @return id
*/
Long delete(@Valid @NotNull Long id);
/**
* 根据主键删除一条数据没有权限会抛出异常
*
* @param id 主键id
* @return id
* @throws NoPermissionBusinessException 没有查询这条数据的权限
*/
Long deleteWithPermission(@Valid @NotNull Long id);
/**
* 查询一条数据不存在会抛出异常
*
* @param id 主键id
* @param selector 选择器
* @return 必定返回一条数据
* @throws DataNotExistsBusinessException 找不到数据的异常
*/
DemoDTO queryExistent(@Valid @NotNull Long id, @Nullable DemoSelector selector);
/**
* 查询一条数据不存在或者没有权限会抛出异常
*
* @param id 主键id
* @param selector 选择器
* @return 必定返回一条数据
* @throws DataNotExistsBusinessException 找不到数据的异常
* @throws NoPermissionBusinessException 没有查询这条数据的权限
*/
DemoDTO queryExistentWithPermission(@Valid @NotNull Long id, @Nullable DemoSelector selector);
/**
* 查询一条数据
*
* @param id 主键id
* @param selector 选择器
* @return 返回一条数据可能为空
*/
DemoDTO query(@Nullable Long id, @Nullable DemoSelector selector);
/**
* 查询一条数据,没有权限会抛出异常
*
* @param id 主键id
* @param selector 选择器
* @return 返回一条数据可能为空
* @throws NoPermissionBusinessException 没有查询这条数据的权限
*/
DemoDTO queryWithPermission(@Nullable Long id, @Nullable DemoSelector selector);
/**
* 根据id列表查询列表数据
*
* @param idList 主键id列表
* @param selector 选择器
* @return 返回全部查询到的数据
*/
List<DemoDTO> listQuery(@Nullable List<Long> idList, @Nullable DemoSelector selector);
/**
* 根据参数查询列表数据
*
* @param param 查询参数
* @param selector 选择器
* @return 返回查询数据最多500条
*/
List<DemoDTO> listQuery(@Valid @NotNull DemoQueryParam param, @Nullable DemoSelector selector);
/**
* 根据参数分页查询列表数据
*
* @param param 查询参数
* @param selector 选择器
* @return 返回分页查询数据
*/
AtaPageResult<DemoDTO> pageQuery(@Valid @NotNull DemoPageQueryParam param, @Nullable DemoSelector selector);
/**
* 根据参数分页查询列表数据只会查询有权限的数据
*
* @param param 查询参数
* @param selector 选择器
* @return 返回分页查询数据
*/
AtaPageResult<DemoDTO> pageQueryWithPermission(@Valid @NotNull DemoPageQueryParam param,
@Nullable DemoSelector selector);
/**
* 综合搜索
* 慎用一般涉及到join
*
* @param param 查询参数
* @param selector 选择器
* @return 返回分页查询数据
*/
AtaPageResult<DemoDTO> comprehensivePageQuery(@Valid @NotNull DemoComprehensivePageQueryParam param,
@Nullable DemoSelector selector);
}

View File

@ -0,0 +1,141 @@
package com.alibaba.atasuper.api.demo;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.enums.DemoStatusEnum;
import com.alibaba.atasuper.api.demo.param.DemoSelector;
import com.alibaba.atasuper.api.image.dto.ImageDTO;
import com.alibaba.atasuper.api.user.dto.DomainUserDTO;
import com.alibaba.atasuper.api.user.param.DomainUserSelector;
import com.alibaba.easytools.base.constant.EasyToolsConstant;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* TODO 示例对象 仅放需要使用的字段
*
* @author 是仪
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class DemoDTO implements Serializable {
@Serial
private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID;
/**
* 主键
*/
private Long id;
/**
* 用户id
*/
@NotNull
private Long userId;
/**
* 用户
*
* @see DemoSelector#setUser(Boolean)
* @see DemoSelector#setUserDomainUserSelector(DomainUserSelector)
*/
@NotNull
private DomainUserDTO user;
/**
* 状态一般非空还要有默认值
*
* @see DemoStatusEnum
*/
private String status;
/**
* 图片的key, 图片存储一般存储key别存储连接
*/
private String imageKey;
/**
* 图片
*
* @see DemoSelector#setImage(Boolean)
*/
private ImageDTO image;
/**
* 姓名字符串字段一般默认空字符串不建议使用非空
*/
private String name;
/**
* 加入时间必须gmt开头日期字段没办法默认值直接默认空
*/
private Date gmtJoin;
/**
* 被访问次数统计型的数据一般默认0建议使用bigint代替int设置非空
*/
private Long viewedCount;
/**
* 角色id, 这种外键没有的情况下用空 别用0
*/
private Long roleId;
/**
* 是否同意协议 布尔值
*/
private Boolean agreeProtocol;
/**
* 创建时间
*/
private Date gmtCreate;
/**
* 创建人用户id
*/
private Long createUserId;
/**
* 创建人
*
* @see DemoSelector#setCreateUser(Boolean)
*/
private DomainUserDTO createUser;
/**
* 修改人用户id
*/
private Long modifiedUserId;
/**
* 修改人
*
* @see DemoSelector#setModifiedUser(Boolean)
*/
private DomainUserDTO modifiedUser;
/**
* 备注字段一般设置可以为空
*
* @see DemoSelector#setWithBlobs(Boolean)
*/
private String remark;
/**
* 是否已经被删除
* 这个只有在 {@code fill} 时才不会为空 其他任何情况都会为空
*/
private Boolean deleted;
}

View File

@ -0,0 +1,36 @@
package com.alibaba.atasuper.api.demo.enums;
import com.alibaba.easytools.base.enums.BaseEnum;
import lombok.Getter;
/**
* TODO 示例状态枚举
*
* @author 是仪
*/
@Getter
public enum DemoStatusEnum implements BaseEnum<String> {
/**
* 草稿
*/
DRAFT("草稿"),
/**
* 线上
*/
RELEASE("线上"),
;
final String description;
DemoStatusEnum(String description) {
this.description = description;
}
@Override
public String getCode() {
return this.name();
}
}

View File

@ -0,0 +1,32 @@
package com.alibaba.atasuper.api.demo.param;
import java.util.List;
import com.alibaba.atasuper.tools.base.wrapper.param.CorePageQueryParam;
import lombok.Data;
import lombok.NonNull;
/**
* 综合搜索
* 慎用一般涉及到join
*
* @author 是仪
*/
@Data
public class DemoComprehensivePageQueryParam extends CorePageQueryParam {
/**
* 用户id
* 传null会报错
*/
@NonNull
private Long userId;
/**
* 用户id
* 传null会报错
*/
@NonNull
private List<Long> userIdList;
}

View File

@ -0,0 +1,47 @@
package com.alibaba.atasuper.api.demo.param;
import java.util.Date;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.enums.DemoStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 创建对象
*
* @author 是仪
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DemoCreateParam {
/**
* 用户id
*/
@NotNull
private Long userId;
/**
* 状态一般非空还要有默认值
*
* @see DemoStatusEnum
*/
private String status;
/**
* 图片的key, 图片存储一般存储key别存储连接
*/
private String imageKey;
/**
* 加入时间必须gmt开头日期字段没办法默认值直接默认空
*/
private Date gmtJoin;
}

View File

@ -0,0 +1,68 @@
package com.alibaba.atasuper.api.demo.param;
import java.util.List;
import com.alibaba.atasuper.tools.base.wrapper.param.CorePageQueryParam;
import com.alibaba.easytools.base.wrapper.param.OrderBy;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
/**
* 单表分页查询
*
* @author 是仪
*/
@Data
@NoArgsConstructor
public class DemoPageQueryParam extends CorePageQueryParam {
/**
* id列表
* 传null会报错
*/
@NonNull
private List<Long> idList;
/**
* 用户id
* 传null会报错
*/
@NonNull
private Long userId;
/**
* 用户id
* 传null不查询
*/
private Long userIdWhenPresent;
/**
* 用户id列表
* 传null不查询
*/
@NonNull
private List<Long> userIdList;
/**
* 关键字搜索
*/
private String keywordWhenPresent;
@Getter
public enum OrderCondition implements com.alibaba.easytools.base.wrapper.param.OrderCondition {
/**
* 修改时间降序
*/
GMT_MODIFIED_DESC(OrderBy.asc("GMTMODIFIED")),
;
final OrderBy orderBy;
OrderCondition(OrderBy orderBy) {
this.orderBy = orderBy;
}
}
}

View File

@ -0,0 +1,48 @@
package com.alibaba.atasuper.api.demo.param;
import java.util.List;
import com.alibaba.atasuper.tools.base.wrapper.param.CoreQueryParam;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
/**
* 单表普通查询
* 最多返回500条
*
* @author 是仪
*/
@Data
@NoArgsConstructor
public class DemoQueryParam extends CoreQueryParam {
/**
* id列表
* 传null会报错
*/
@NonNull
private List<Long> idList;
/**
* 用户id
* 传null会报错
*/
@NonNull
private Long userId;
/**
* 用户id
* 传null不查询
*/
private Long userIdWhenPresent;
/**
* 用户id列表
* 传null不查询
*/
@NonNull
private List<Long> userIdList;
}

View File

@ -0,0 +1,50 @@
package com.alibaba.atasuper.api.demo.param;
import com.alibaba.atasuper.api.user.param.DomainUserSelector;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 选择器
*
* @author 是仪
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DemoSelector {
/**
* 用户
*/
private Boolean user;
/**
* 用户
*/
private DomainUserSelector userDomainUserSelector;
/**
* 图片
*/
private Boolean image;
/**
* 创建人
*/
private Boolean createUser;
/**
* 修改人
*/
private Boolean modifiedUser;
/**
* 查询withBlobs
*/
private Boolean withBlobs;
}

View File

@ -0,0 +1,59 @@
package com.alibaba.atasuper.api.demo.param;
import java.util.Date;
import javax.validation.constraints.NotNull;
import com.alibaba.atasuper.api.demo.enums.DemoStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 创建对象
*
* @author 是仪
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DemoUpdateParam {
/**
* 主键
*/
@NotNull
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 状态一般非空还要有默认值
*
* @see DemoStatusEnum
*/
private String status;
/**
* 图片的key, 图片存储一般存储key别存储连接
*/
private String imageKey;
/**
* 加入时间必须gmt开头日期字段没办法默认值直接默认空
*/
private Date gmtJoin;
/**
* 当加入时间为空时删除加入时间
* 默认为空false
*/
private Boolean deleteGmtJoinWhenNull;
}

View File

@ -0,0 +1,88 @@
package com.alibaba.easytools.common.model;
import java.io.Serializable;
/**
* 整形的封装类
*
* @author 是仪
*/
public class IntegerWrapper extends Number implements Serializable {
private static final long serialVersionUID = 1L;
private int value;
public IntegerWrapper(int initialValue) {
value = initialValue;
}
public IntegerWrapper() {
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final int getAndIncrement() {
return getAndAdd(1);
}
public final int getAndDecrement() {
return getAndAdd(-1);
}
public final int getAndAdd(int delta) {
int oldValue = value;
value += delta;
return oldValue;
}
public final int incrementAndGet() {
return addAndGet(1);
}
public final int decrementAndGet() {
return addAndGet(-1);
}
public final int addAndGet(int delta) {
value += delta;
return value;
}
public final void increment() {
add(1);
}
public final void decrement() {
add(-1);
}
public final void add(int delta) {
value += delta;
}
@Override
public int intValue() {
return get();
}
@Override
public long longValue() {
return get();
}
@Override
public float floatValue() {
return get();
}
@Override
public double doubleValue() {
return get();
}
}

View File

@ -0,0 +1,235 @@
package com.alibaba.easytools.common.util;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
/**
* 集合工具类
*
* @author Jiaju Zhuang
*/
public class EasyCollectionUtils {
/**
* 集合stream
*
* @param collection 集合
* @param <T> 返回的类型
* @return 集合的stream
*/
public static <T> Stream<T> stream(Collection<T> collection) {
return collection != null ? collection.stream() : Stream.empty();
}
/**
* for 循环一个集合
*
* @param collection
* @param action
* @param <T>
*/
public static <T> void forEach(Collection<T> collection, Consumer<? super T> action) {
if (collection == null || collection.isEmpty()) {
return;
}
collection.forEach(action);
}
/**
* 将一个集合 转换成一个list
* <p>
* 会过滤集合中转换前后的空数据所以会入参出参数量不一致
*
* @param collection 集合
* @param function 转换function
* @param <T> 转换前的数据类型
* @param <R> 转换后数据类型
* @return list 如果入参为空 会返回一个空数组且无法修改
*/
public static <T, R> List<R> toList(Collection<T> collection, Function<T, R> function) {
return stream(collection)
.filter(Objects::nonNull)
.map(function)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* 将一个集合 转换成一个list
* <p>
* 会过滤集合中转换前后的空数据所以会入参出参数量不一致
*
* @param collection 集合
* @param function 转换function
* @param <T> 转换前的数据类型
* @param <R> 转换后数据类型
* @return list 如果入参为空 会返回一个空数组且无法修改
*/
public static <T, R> List<R> toList(Collection<T> collection, Function<T, R> function, Predicate<T> filter) {
return stream(collection)
.filter(Objects::nonNull)
.filter(filter)
.map(function)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* 将一个集合 转换成一个list 并去重
* <p>
* 会过滤集合中转换前后的空数据所以会入参出参数量不一致
*
* @param collection 集合
* @param function 转换function
* @param <T> 转换前的数据类型
* @param <R> 转换后数据类型
* @return list 如果入参为空 会返回一个空数组且无法修改
*/
public static <T, R> List<R> toDistinctList(Collection<T> collection, Function<T, R> function) {
return stream(collection)
.filter(Objects::nonNull)
.map(function)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
/**
* 将一个集合 里面的List属性的字段全部铺开重新转换成新的list
* <p>
* 会过滤集合中转换前后的空数据所以会入参出参数量不一致
*
* @param collection 集合
* @param function 转换function
* @param <T> 转换前的数据类型
* @param <R> 转换后数据类型
* @return list 如果入参为空 会返回一个空数组且无法修改
*/
public static <T, R> List<R> toFlatList(Collection<T> collection, Function<T, Collection<R>> function) {
return stream(collection)
.map(function)
.filter(CollectionUtils::isNotEmpty)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* 将一个集合 转换成一个set
* <p>
* 会过滤集合中转换前后的空数据
*
* @param collection 集合
* @param function 转换function
* @param <T> 转换前的数据类型
* @param <R> 转换后数据类型
* @return list 如果入参为空 会返回一个空数组且无法修改
*/
public static <T, R> Set<R> toSet(Collection<T> collection, Function<T, R> function) {
return stream(collection)
.filter(Objects::nonNull)
.map(function)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
/**
* 将一个集合转成map遇到key冲突以第二个为准
*
* @param collection 集合
* @param keyFunction keyFunction
* @param valueFunction valueFunction
* @param <K> key数据类型
* @param <V> value数据类型
* @param <T> 转换前的数据类型
* @return 转成以后的map
*/
public static <K, V, T> Map<K, V> toMap(Collection<T> collection, Function<? super T, K> keyFunction,
Function<? super T, V> valueFunction) {
return stream(collection)
.filter(Objects::nonNull)
.collect(Collectors.toMap(keyFunction, valueFunction, (oldValue, newValue) -> newValue));
}
/**
* 将一个集合转成map,map的value就是集合的值遇到key冲突以第二个为准
*
* @param collection 集合
* @param keyFunction keyFunction
* @param <K> key数据类型
* @param <T> 转换前的数据类型
* @return 转成以后的map
*/
public static <K, T> Map<K, T> toIdentityMap(Collection<T> collection, Function<? super T, K> keyFunction) {
return toMap(collection, keyFunction, Function.identity());
}
/**
* 将一个集合转成map 转换成一个 map 数据包含list
*
* @param collection 集合
* @param keyFunction keyFunction
* @param <K> key数据类型
* @param <T> 转换前的数据类型
* @return 转成以后的map
*/
public static <K, T> Map<K, List<T>> toGroupingMap(Collection<T> collection, Function<? super T, K> keyFunction) {
return stream(collection)
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(keyFunction));
}
/**
* 往一个集合里面加入另一个一个集合
*
* @param collection 原始的集合
* @param collectionAdd 需要被加入的集合
* @param <C>
* @return 是否加入了数据
*/
public static <C> boolean addAll(final Collection<C> collection, final Collection<C> collectionAdd) {
if (collectionAdd == null) {
return false;
}
return collection.addAll(collectionAdd);
}
/**
* 判断一个集合的长度为0 但是不为null
*
* @param collection 集合
* @return
*/
public static boolean isEmptyButNotNull(final Collection<?> collection) {
return collection != null && collection.isEmpty();
}
/**
* 判断 一堆集合 是否存在一个 长度为0 但是不为null的数组
*
* @param collections 为空则返回false
* @return
*/
public static boolean isAnyEmptyButNotNull(final Collection<?>... collections) {
if (ArrayUtils.isEmpty(collections)) {
return false;
}
for (final Collection<?> collection : collections) {
if (isEmptyButNotNull(collection)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,97 @@
package com.alibaba.easytools.common.util;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.alibaba.easytools.base.enums.BaseEnum;
/**
* enum工具类
* <p>
* 主要为了解决每个枚举都需要写一个根据code 获取value 的函数看起来不太友好
*
* @author Jiaju Zhuang
*/
public class EasyEnumUtils {
/**
* 枚举缓存 不用每次都去循环读取枚举
*/
private static final Map<Object, Map<?, BaseEnum<?>>> ENUM_CACHE = new ConcurrentHashMap<>();
/**
* 根据一个枚举类型获取 枚举的描述
*
* @param clazz 枚举的class
* @param code 枚举的编码
* @param <T> 枚举的类型
* @return 找不到code 则返回为空
*/
public static <T extends BaseEnum<?>> String getDescription(final Class<T> clazz, final Object code) {
BaseEnum<?> baseEnum = getEnum(clazz, code);
if (baseEnum == null) {
return null;
}
return baseEnum.getDescription();
}
/**
* 根据一个枚举类型获取 枚举的描述
*
* @param clazz 枚举的class
* @param code 枚举的编码
* @param <T> 枚举的类型
* @return 找不到code 则返回为空
*/
public static <T extends BaseEnum<?>> T getEnum(final Class<T> clazz, final Object code) {
return getEnumMap(clazz).get(code);
}
/**
* 校验是否是一个有效的枚举
*
* @param clazz 枚举的class
* @param code 枚举的编码 , null 也认为是一个有效的枚举
* @param <T> 枚举的类型
* @return 是否有效
*/
public static <T extends BaseEnum<?>> boolean isValidEnum(final Class<T> clazz, final Object code) {
return isValidEnum(clazz, code, true);
}
/**
* 校验是否是一个有效的枚举
*
* @param clazz 枚举的class
* @param code 枚举的编码,为空认为是一个无效的枚举
* @param ignoreNull 是否忽略空的code
* @param <T> 枚举的类型
* @return 是否有效
*/
public static <T extends BaseEnum<?>> boolean isValidEnum(final Class<T> clazz, final Object code,
final boolean ignoreNull) {
if (code == null) {
return ignoreNull;
}
return getEnumMap(clazz).containsKey(code);
}
/**
* 获取一个枚举的code Enum的map
*
* @param clazz 枚举的class
* @param <T> 枚举的类型
* @return Map<code, Enum>
*/
public static <T extends BaseEnum<?>> Map<Object, T> getEnumMap(final Class<T> clazz) {
String className = clazz.getName();
Map<?, BaseEnum<?>> result = ENUM_CACHE.computeIfAbsent(className, value -> {
T[] baseEnums = clazz.getEnumConstants();
return Arrays.stream(baseEnums)
.collect(Collectors.toMap(BaseEnum::getCode, Function.identity()));
});
return (Map)result;
}
}

View File

@ -0,0 +1,39 @@
package com.alibaba.easytools.common.util;
import java.util.Optional;
import java.util.function.Function;
/**
* Optional的工具类
*
* @author Jiaju Zhuang
*/
public class EasyOptionalUtils {
/**
* 将一个可能未null 的对象 获取其值
*
* @param source 原始对象
* @param function 转换方法
* @param <T>
* @param <R>
* @return 返回值 为空则返回nulll
*/
public static <T, R> R mapTo(T source, Function<T, R> function) {
return mapTo(source, function, null);
}
/**
* 将一个可能未null 的对象 获取其值
*
* @param source 原始对象
* @param function 转换方法
* @param defaultValue 默认值
* @param <T>
* @param <R>
* @return 返回值
*/
public static <T, R> R mapTo(T source, Function<T, R> function, R defaultValue) {
return Optional.ofNullable(source).map(function).orElse(defaultValue);
}
}

View File

@ -0,0 +1,38 @@
package com.alibaba.easytools.common.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import lombok.extern.slf4j.Slf4j;
/**
* 反射工具类
*
* @author Jiaju Zhuang
*/
@Slf4j
public class EasyReflectUtils {
/**
* 获取一个类的父类的泛型类型如果没有 则获取父类的父类
*
* @return
*/
public static Class<?> superParameterizedType(Class<?> clazz) {
Type type = clazz.getGenericSuperclass();
// 获取父类的
if (type instanceof Class) {
type = ((Class<?>)type).getGenericSuperclass();
}
// 获取父类的父类
if (type instanceof Class) {
type = ((Class<?>)type).getGenericSuperclass();
}
// 不管父类的父类是不是 强制转 如果不是这里会报错
ParameterizedType parameterizedType = (ParameterizedType)type;
// 直接拿第一个强制转换
return (Class<?>)parameterizedType.getActualTypeArguments()[0];
}
}

View File

@ -0,0 +1,154 @@
package com.alibaba.easytools.common.util;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
/**
* String工具类
*
* @author Jiaju Zhuang
*/
public class EasyStringUtils {
/**
* 0的字符
*/
private static final char ZERO_CHAR = '0';
/**
* 去掉工号前面的0
*
* @param userId 工号
* @return 修改后的工号
*/
public static String cutUserId(String userId) {
if (!org.apache.commons.lang3.StringUtils.isNumeric(userId)) {
return userId;
}
int startIndex = 0;
for (int i = 0; i < userId.length(); i++) {
char c = userId.charAt(i);
// 查询第一个不是0的位置
if (ZERO_CHAR == c) {
startIndex = i + 1;
} else {
break;
}
}
// 可能整个账号都是0
if (startIndex == userId.length()) {
return "0";
}
return userId.substring(startIndex);
}
/**
* 去除花名后面的工号
*
* @param name 姓名或者花名
* @return 去除工号后的姓名或者花名
*/
public static String cutName(String name, String workNo) {
if (StringUtils.isBlank(workNo) || StringUtils.isBlank(name)) {
return name;
}
// 这里可能会出现 0结的情况
String cutName = RegExUtils.removeFirst(name, workNo);
int lastIndex = cutName.length();
for (int i = cutName.length() - 1; i >= 0; i--) {
char c = cutName.charAt(i);
// 查询第最后一个不是0的位置
if (ZERO_CHAR == c) {
lastIndex = i;
} else {
break;
}
}
return cutName.substring(0, lastIndex);
}
/**
* 增加工号前面的0
*
* @param userId 工号
* @return 修改后的工号
*/
public static String padUserId(String userId) {
if (!StringUtils.isNumeric(userId)) {
return userId;
}
return StringUtils.leftPad(userId, 6, '0');
}
/**
* 构建展示的名称
*
* @param name 姓名
* @param nickName 花名
* @return 展示名称 姓名花名
*/
public static String buildShowName(String name, String nickName) {
StringBuilder showName = new StringBuilder();
if (StringUtils.isNotBlank(name)) {
showName.append(name);
}
if (StringUtils.isNotBlank(nickName)) {
showName.append("(");
showName.append(nickName);
showName.append(")");
}
return showName.toString();
}
/**
* 将多个字符串 拼接在一起
*
* @param delimiter 分隔符 不能为空
* @param elements 字符串 可以为空 会忽略空的字符串
* @return
*/
public static String join(CharSequence delimiter, CharSequence... elements) {
if (elements == null) {
return null;
}
List<CharSequence> charSequenceList = Arrays.stream(elements).filter(
org.apache.commons.lang3.StringUtils::isNotBlank).collect(Collectors.toList());
if (charSequenceList.isEmpty()) {
return null;
}
return String.join(delimiter, charSequenceList);
}
/**
* 限制一个string字符串的长度 超过长度 会用... 替换
*
* @param str 字符串
* @param length 限制长度
* @return
*/
public static String limitString(String str, int length) {
if (Objects.isNull(str)) {
return null;
}
String limitString = StringUtils.substring(str, 0, length);
if (limitString.length() == length) {
limitString += "...";
}
return limitString;
}
/**
* 根据冒号拼接在一起
*
* @param objs 对象
* @return 拼接完成的数据
*/
public static String joinWithColon(Object... objs) {
return StringUtils.join(objs, ":");
}
}

View File

@ -0,0 +1,13 @@
package com.alibaba.easytools.common.util.function;
/**
* 没有入参 出参的function
*/
@FunctionalInterface
public interface Executor {
/**
* 执行
*/
void execute();
}

View File

@ -0,0 +1,23 @@
<?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>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>easytools</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,4 @@
# 转换toString 调用上一层
lombok.toString.callSuper = CALL
# 全局配置 equalsAndHashCode 的 callSuper 属性为true
lombok.equalsAndHashCode.callSuper=call

View File

@ -1,82 +1,427 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<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/maven-v4_0_0.xsd">
<parent>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
<groupId>com.taobao</groupId>
<artifactId>parent</artifactId>
<version>2.0.0</version>
</parent>
<url>https://github.com/alibaba/easyexcel</url>
<packaging>jar</packaging>
<artifactId>easyexcel-test</artifactId>
<name>easyexcel-test</name>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-parent</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
<name>easytools-parent</name>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<!-- 当前项目的版本 用了flatten 仅修改这一个地方即可-->
<revision>1.1.11-SNAPSHOT</revision>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.11</spring-boot.version>
<pandora-boot.version>2023-03-release</pandora-boot.version>
<lombok.version>1.18.22</lombok.version>
</properties>
<dependencies>
<modules>
<module>easytools</module>
<module>easytools-spring-boot-autoconfigure</module>
<module>easytools-spring-boot-starter</module>
<module>easytools-base</module>
<module>easytools-common</module>
<module>easytools-log</module>
<module>easytools-spring-demo</module>
<module>easytools-spring</module>
<module>easytools-test</module>
</modules>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.18</version>
</dependency>
<dependencyManagement>
<dependencies>
<!-- 自己项目的包 -->
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-base</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-log</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-log-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-log-spring-boot-autoconfigure</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>eagleeye-core-sdk</artifactId>
<version>1.9.5--2021-11-release</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-log-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-spring-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-spring-spring-boot-autoconfigure</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-spring-boot-autoconfigure</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easytools-spring-demo</artifactId>
<version>${revision}</version>
</dependency>
<!-- log -->
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>3.7.2</version>
</dependency>
<!-- springboot2 需要加这个配置 -->
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-servlet</artifactId>
<version>3.7.2</version>
<classifier>javax</classifier>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.taobao.pandora</groupId>
<artifactId>pandora-boot-starter-bom</artifactId>
<version>${pandora-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.schedulerx</groupId>
<artifactId>schedulerx2-spring-boot-starter</artifactId>
<version>1.2.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- tddl -->
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>tddl-client-sdk</artifactId>
<version>5.2.12--2021-07-release</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<!-- oss -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-imm</artifactId>
<version>1.23.4</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.25</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
<!-- hsf -->
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>hsf-sdk</artifactId>
<version>3.1.1.5-SINK--2022-02-release</version>
</dependency>
<!-- aspectj -->
<dependency>
<artifactId>aspectjweaver</artifactId>
<groupId>org.aspectj</groupId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastvalidator</groupId>
<artifactId>fastvalidator-constraints</artifactId>
<version>2.6.2.5</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastvalidator</groupId>
<artifactId>fastvalidator-generator</artifactId>
<version>2.6.2.5</version>
</dependency>
<!-- fastvalidator不支持 2.0.1.Final版本 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.2.Final</version>
</dependency>
<!-- open search -->
<dependency>
<groupId>com.aliyun.opensearch</groupId>
<artifactId>aliyun-sdk-opensearch</artifactId>
<version>3.3.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.taobao.aliauto</groupId>
<artifactId>open-search-spring-starter</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.alibaba.grow</groupId>
<artifactId>grow-client-api</artifactId>
<version>1.0.2</version>
<exclusions>
<exclusion>
<groupId>com.alibaba.easytools</groupId>
<artifactId>easy-base</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.2</version>
</dependency>
<!--测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- 用于http请求支持sse请求 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>4.12.0</version>
</dependency>
<!-- http -->
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.5.36</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- logback -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打包源码 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 支持maven revision 来配置统版本-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>1.2.7</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>oss</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 运行测试案例 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<includes>
<include>/com/alibaba/easyexcel/test/core/**/*.java</include>
<include>/com/alibaba/easytools/test/**/*.java</include>
</includes>
<testFailureIgnore>false</testFailureIgnore>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
<!-- 清理mapstruct生成的转换器 -->
<!-- 考虑需要清理converter下面所有的class -->
<!-- 确认clean 插件能否搞2个or 再额外引入一个 -->
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-clean-plugin</artifactId>-->
<!-- <version>3.2.0</version>-->
<!-- <configuration>-->
<!-- &lt;!&ndash;当配置true时,只清理filesets里的文件,构建目录中得文件不被清理.默认是flase.&ndash;&gt;-->
<!-- <excludeDefaultDirectories>true</excludeDefaultDirectories>-->
<!-- <filesets>-->
<!-- <fileset>-->
<!-- &lt;!&ndash;要清理的目录位置&ndash;&gt;-->
<!-- <directory>${basedir}/target/generated-sources</directory>-->
<!-- </fileset>-->
<!-- </filesets>-->
<!-- </configuration>-->
<!-- </plugin>-->
</plugins>
</build>
</project>