feat: initial commit
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>agileboot</artifactId>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>agileboot-common</artifactId>
|
||||
|
||||
<description>
|
||||
common通用工具
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring框架基本的核心工具 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringWeb模块 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- spring security 安全认证 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 自定义验证注解 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--常用工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON工具类 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- io常用工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 文件上传工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- excel工具 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- yml解析器 -->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Token生成与解析-->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jaxb -->
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- redis 缓存操作 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- pool 对象池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 解析客户端操作系统、浏览器等 -->
|
||||
<dependency>
|
||||
<groupId>eu.bitwalker</groupId>
|
||||
<artifactId>UserAgentUtils</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- servlet包 -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-parameter-names</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>it.ozimov</groupId>
|
||||
<artifactId>embedded-redis</artifactId>
|
||||
<!-- 不排除掉slf4j的话 会冲突-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
<!-- 排除掉guava依赖,以本项目的guava依赖为准 -->
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- 多数据源 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- swagger注解 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.agileboot.common.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 自定义导出Excel数据注解
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface ExcelColumn {
|
||||
|
||||
String name() default "";
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.agileboot.common.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ExcelSheet {
|
||||
|
||||
/**
|
||||
* sheet名称
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.agileboot.common.config;
|
||||
|
||||
import com.agileboot.common.constant.Constants;
|
||||
import java.io.File;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 读取项目相关配置
|
||||
* TODO 移走 不合适放在这里common包底下
|
||||
* @author valarchie
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "agileboot")
|
||||
@Data
|
||||
public class AgileBootConfig {
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 版权年份
|
||||
*/
|
||||
private String copyrightYear;
|
||||
|
||||
/**
|
||||
* 实例演示开关
|
||||
*/
|
||||
private static boolean demoEnabled;
|
||||
|
||||
/**
|
||||
* 上传路径
|
||||
*/
|
||||
private static String fileBaseDir;
|
||||
|
||||
/**
|
||||
* 获取地址开关
|
||||
*/
|
||||
private static boolean addressEnabled;
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
*/
|
||||
private static String captchaType;
|
||||
|
||||
/**
|
||||
* rsa private key 静态属性的注入!! set方法一定不能是static 方法
|
||||
*/
|
||||
private static String rsaPrivateKey;
|
||||
|
||||
private static String apiPrefix;
|
||||
|
||||
public static String getFileBaseDir() {
|
||||
return fileBaseDir;
|
||||
}
|
||||
|
||||
public void setFileBaseDir(String fileBaseDir) {
|
||||
AgileBootConfig.fileBaseDir = fileBaseDir + File.separator + Constants.RESOURCE_PREFIX;
|
||||
}
|
||||
|
||||
public static String getApiPrefix() {
|
||||
return apiPrefix;
|
||||
}
|
||||
|
||||
public void setApiPrefix(String apiDocsPathPrefix) {
|
||||
AgileBootConfig.apiPrefix = apiDocsPathPrefix;
|
||||
}
|
||||
|
||||
public static boolean isAddressEnabled() {
|
||||
return addressEnabled;
|
||||
}
|
||||
|
||||
public void setAddressEnabled(boolean addressEnabled) {
|
||||
AgileBootConfig.addressEnabled = addressEnabled;
|
||||
}
|
||||
|
||||
public static String getCaptchaType() {
|
||||
return captchaType;
|
||||
}
|
||||
|
||||
public void setCaptchaType(String captchaType) {
|
||||
AgileBootConfig.captchaType = captchaType;
|
||||
}
|
||||
|
||||
public static String getRsaPrivateKey() {
|
||||
return rsaPrivateKey;
|
||||
}
|
||||
|
||||
public void setRsaPrivateKey(String rsaPrivateKey) {
|
||||
AgileBootConfig.rsaPrivateKey = rsaPrivateKey;
|
||||
}
|
||||
|
||||
public static boolean isDemoEnabled() {
|
||||
return demoEnabled;
|
||||
}
|
||||
|
||||
public void setDemoEnabled(boolean demoEnabled) {
|
||||
AgileBootConfig.demoEnabled = demoEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.agileboot.common.constant;
|
||||
|
||||
|
||||
/**
|
||||
* 通用常量信息
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
public class Constants {
|
||||
private Constants() {
|
||||
}
|
||||
|
||||
public static final int KB = 1024;
|
||||
|
||||
public static final int MB = KB * 1024;
|
||||
|
||||
public static final int GB = MB * 1024;
|
||||
|
||||
/**
|
||||
* http请求
|
||||
*/
|
||||
public static final String HTTP = "http://";
|
||||
|
||||
/**
|
||||
* https请求
|
||||
*/
|
||||
public static final String HTTPS = "https://";
|
||||
|
||||
|
||||
public static class Token {
|
||||
|
||||
private Token() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String LOGIN_USER_KEY = "login_user_key";
|
||||
|
||||
}
|
||||
|
||||
public static class Captcha {
|
||||
|
||||
private Captcha() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 令牌
|
||||
*/
|
||||
public static final String MATH_TYPE = "math";
|
||||
|
||||
/**
|
||||
* 令牌前缀
|
||||
*/
|
||||
public static final String CHAR_TYPE = "char";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源映射路径 前缀
|
||||
*/
|
||||
public static final String RESOURCE_PREFIX = "profile";
|
||||
|
||||
public static class UploadSubDir {
|
||||
|
||||
private UploadSubDir() {
|
||||
}
|
||||
|
||||
public static final String IMPORT_PATH = "import";
|
||||
|
||||
public static final String AVATAR_PATH = "avatar";
|
||||
|
||||
public static final String DOWNLOAD_PATH = "download";
|
||||
|
||||
public static final String UPLOAD_PATH = "upload";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.agileboot.common.core.base;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.util.Date;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class BaseController {
|
||||
|
||||
/**
|
||||
*
|
||||
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
|
||||
*/
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
// Date 类型转换
|
||||
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
|
||||
@Override
|
||||
public void setAsText(String text) {
|
||||
setValue(DateUtil.parseDate(text));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面跳转
|
||||
*/
|
||||
public String redirect(String url) {
|
||||
return StrUtil.format("redirect:{}", url);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.agileboot.common.core.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Entity基类
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class BaseEntity<T extends Model<?>> extends Model<T> {
|
||||
|
||||
@ApiModelProperty("创建者ID")
|
||||
@TableField(value = "creator_id", fill = FieldFill.INSERT)
|
||||
private Long creatorId;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
@ApiModelProperty("更新者ID")
|
||||
@TableField(value = "updater_id", fill = FieldFill.UPDATE, updateStrategy = FieldStrategy.NOT_NULL)
|
||||
private Long updaterId;
|
||||
|
||||
@ApiModelProperty("更新时间")
|
||||
@TableField(value = "update_time", fill = FieldFill.UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* deleted字段请在数据库中 设置为tinyInt 并且非null 默认值为0
|
||||
*/
|
||||
@ApiModelProperty("删除标志(0代表存在 1代表删除)")
|
||||
@TableField("deleted")
|
||||
@TableLogic
|
||||
private Boolean deleted;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.agileboot.common.core.dto;
|
||||
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 响应信息主体
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ResponseDTO<T> {
|
||||
|
||||
private Integer code;
|
||||
|
||||
private String msg;
|
||||
|
||||
@JsonInclude
|
||||
private T data;
|
||||
|
||||
public static <T> ResponseDTO<T> ok() {
|
||||
return build(null, ErrorCode.SUCCESS.code(), ErrorCode.SUCCESS.message());
|
||||
}
|
||||
|
||||
public static <T> ResponseDTO<T> ok(T data) {
|
||||
return build(data, ErrorCode.SUCCESS.code(), ErrorCode.SUCCESS.message());
|
||||
}
|
||||
|
||||
public static <T> ResponseDTO<T> fail() {
|
||||
return build(null, ErrorCode.FAILED.code(), ErrorCode.FAILED.message());
|
||||
}
|
||||
|
||||
public static <T> ResponseDTO<T> fail(T data) {
|
||||
return build(data, ErrorCode.FAILED.code(), ErrorCode.FAILED.message());
|
||||
}
|
||||
|
||||
public static <T> ResponseDTO<T> fail(ApiException exception) {
|
||||
return build(null, exception.getErrorCode().code(), exception.getMessage());
|
||||
}
|
||||
|
||||
public static <T> ResponseDTO<T> fail(ApiException exception, T data) {
|
||||
return build(data, exception.getErrorCode().code(), exception.getMessage());
|
||||
}
|
||||
|
||||
public static <T> ResponseDTO<T> build(T data, Integer code, String msg) {
|
||||
return new ResponseDTO<>(code, msg, data);
|
||||
}
|
||||
|
||||
// 去掉直接填充错误码的方式, 这种方式不能拿到i18n的错误消息 统一通过ApiException来构造错误消息
|
||||
// public static <T> ResponseDTO<T> fail(ErrorCodeInterface code, Object... args) {
|
||||
// return build(null, code, args);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.agileboot.common.core.page;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import javax.validation.constraints.Max;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public abstract class AbstractPageQuery<T> extends AbstractQuery<T> {
|
||||
|
||||
/**
|
||||
* 最大分页页数
|
||||
*/
|
||||
public static final int MAX_PAGE_NUM = 200;
|
||||
/**
|
||||
* 单页最大大小
|
||||
*/
|
||||
public static final int MAX_PAGE_SIZE = 500;
|
||||
/**
|
||||
* 默认分页页数
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_NUM = 1;
|
||||
/**
|
||||
* 默认分页大小
|
||||
*/
|
||||
public static final int DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
@Max(MAX_PAGE_NUM)
|
||||
protected Integer pageNum;
|
||||
@Max(MAX_PAGE_SIZE)
|
||||
protected Integer pageSize;
|
||||
|
||||
public Page<T> toPage() {
|
||||
pageNum = ObjectUtil.defaultIfNull(pageNum, DEFAULT_PAGE_NUM);
|
||||
pageSize = ObjectUtil.defaultIfNull(pageSize, DEFAULT_PAGE_SIZE);
|
||||
return new Page<>(pageNum, pageSize);
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.agileboot.common.core.page;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.common.utils.time.DatePickUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 如果是简单的排序 和 时间范围筛选 可以使用内置的这几个字段
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
public abstract class AbstractQuery<T> {
|
||||
|
||||
protected String orderColumn;
|
||||
|
||||
protected String orderDirection;
|
||||
|
||||
protected String timeRangeColumn;
|
||||
|
||||
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
|
||||
private Date beginTime;
|
||||
|
||||
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
|
||||
private Date endTime;
|
||||
|
||||
private static final String ASC = "ascending";
|
||||
private static final String DESC = "descending";
|
||||
|
||||
/**
|
||||
* 生成query conditions
|
||||
*
|
||||
* @return 添加条件后的QueryWrapper
|
||||
*/
|
||||
public QueryWrapper<T> toQueryWrapper() {
|
||||
QueryWrapper<T> queryWrapper = addQueryCondition();
|
||||
addSortCondition(queryWrapper);
|
||||
addTimeCondition(queryWrapper);
|
||||
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
public abstract QueryWrapper<T> addQueryCondition();
|
||||
|
||||
public void addSortCondition(QueryWrapper<T> queryWrapper) {
|
||||
if (queryWrapper == null || StrUtil.isEmpty(orderColumn)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Boolean sortDirection = convertSortDirection();
|
||||
if (sortDirection != null) {
|
||||
queryWrapper.orderBy(StrUtil.isNotEmpty(orderColumn), sortDirection,
|
||||
StrUtil.toUnderlineCase(orderColumn));
|
||||
}
|
||||
}
|
||||
|
||||
public void addTimeCondition(QueryWrapper<T> queryWrapper) {
|
||||
if (queryWrapper != null
|
||||
&& StrUtil.isNotEmpty(this.timeRangeColumn)) {
|
||||
queryWrapper
|
||||
.ge(beginTime != null, StrUtil.toUnderlineCase(timeRangeColumn),
|
||||
DatePickUtil.getBeginOfTheDay(beginTime))
|
||||
.le(endTime != null, StrUtil.toUnderlineCase(timeRangeColumn), DatePickUtil.getEndOfTheDay(endTime));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端传来的排序方向 转换成MyBatisPlus所需的排序参数 boolean=isAsc
|
||||
* @return 排序顺序, null为无排序
|
||||
*/
|
||||
public Boolean convertSortDirection() {
|
||||
Boolean isAsc = null;
|
||||
if (StrUtil.isEmpty(this.orderDirection)) {
|
||||
return isAsc;
|
||||
}
|
||||
|
||||
if (ASC.equals(this.orderDirection)) {
|
||||
isAsc = true;
|
||||
}
|
||||
if (DESC.equals(this.orderDirection)) {
|
||||
isAsc = false;
|
||||
}
|
||||
|
||||
return isAsc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.agileboot.common.core.page;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分页模型类
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
public class PageDTO<T> {
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
private List<T> rows;
|
||||
|
||||
public PageDTO(List<T> list) {
|
||||
this.rows = list;
|
||||
this.total = (long) list.size();
|
||||
}
|
||||
|
||||
public PageDTO(Page<T> page) {
|
||||
this.rows = page.getRecords();
|
||||
this.total = page.getTotal();
|
||||
}
|
||||
|
||||
public PageDTO(List<T> list, Long count) {
|
||||
this.rows = list;
|
||||
this.total = count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.agileboot.common.enums;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
* 普通的枚举 接口
|
||||
* @param <T>
|
||||
*/
|
||||
public interface BasicEnum<T>{
|
||||
|
||||
|
||||
/**
|
||||
* 获取枚举的值
|
||||
* @return 枚举值
|
||||
*/
|
||||
T getValue();
|
||||
|
||||
/**
|
||||
* 获取枚举的描述
|
||||
* @return 描述
|
||||
*/
|
||||
String description();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.agileboot.common.enums;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.common.enums.BasicEnum;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
public class BasicEnumUtil {
|
||||
|
||||
private BasicEnumUtil() {
|
||||
}
|
||||
|
||||
public static final String UNKNOWN = "未知";
|
||||
|
||||
public static <E extends Enum<E>> E fromValueSafely(Class<E> enumClass, Object value) {
|
||||
E target = null;
|
||||
|
||||
for (E enumConstant : enumClass.getEnumConstants()) {
|
||||
BasicEnum<?> basicEnum = (BasicEnum<?>) enumConstant;
|
||||
if (Objects.equals(basicEnum.getValue(), value)) {
|
||||
target = (E) basicEnum;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> E fromValue(Class<E> enumClass, Object value) {
|
||||
E target = null;
|
||||
|
||||
for (E enumConstant : enumClass.getEnumConstants()) {
|
||||
BasicEnum basicEnum = (BasicEnum) enumConstant;
|
||||
if (Objects.equals(basicEnum.getValue(), value)) {
|
||||
target = (E) basicEnum;
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
throw new ApiException(ErrorCode.Internal.GET_ENUM_FAILED, enumClass.getSimpleName());
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> String getDescriptionByBool(Class<E> enumClass, Boolean bool) {
|
||||
Integer value = Convert.toInt(bool, 0);
|
||||
return getDescriptionByValue(enumClass, value);
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> String getDescriptionByValue(Class<E> enumClass, Object value) {
|
||||
E basicEnum = fromValueSafely(enumClass, value);
|
||||
if (basicEnum != null) {
|
||||
return ((BasicEnum<?>) basicEnum).description();
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.agileboot.common.enums;
|
||||
|
||||
/**
|
||||
* 字典类型 接口
|
||||
* @author valarchie
|
||||
*/
|
||||
public interface DictionaryEnum<T> extends BasicEnum<T> {
|
||||
|
||||
/**
|
||||
* 获取css标签
|
||||
* @return css标签
|
||||
*/
|
||||
String cssTag();
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_operation_log的business_type
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysOperationLog.businessType")
|
||||
public enum BusinessTypeEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
*/
|
||||
OTHER(0, "其他操作", CssTag.INFO),
|
||||
ADD(1, "添加", CssTag.PRIMARY),
|
||||
MODIFY(2, "修改", CssTag.PRIMARY),
|
||||
DELETE(3, "删除", CssTag.DANGER),
|
||||
GRANT(4, "授权", CssTag.PRIMARY),
|
||||
EXPORT(5, "导出", CssTag.WARNING),
|
||||
IMPORT(6, "导入", CssTag.WARNING),
|
||||
FORCE_LOGOUT(7, "强退", CssTag.DANGER),
|
||||
CLEAN(8, "清空", CssTag.DANGER),
|
||||
;
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
BusinessTypeEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.BasicEnum;
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
* @author valarchie
|
||||
* 对应 sys_config表的config_key字段
|
||||
*/
|
||||
public enum ConfigKeyEnum implements BasicEnum<String> {
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
SKIN_THEME("sys.index.skinName", "系统皮肤主题"),
|
||||
INIT_PASSWORD("sys.user.initPassword", "初始密码"),
|
||||
SIDE_BAR_THEME("sys.index.sideTheme", "侧边栏开关"),
|
||||
CAPTCHA("sys.account.captchaOnOff", "验证码开关"),
|
||||
REGISTER("sys.account.registerUser", "注册开放功能");
|
||||
|
||||
private final String value;
|
||||
private final String description;
|
||||
|
||||
ConfigKeyEnum(String value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_user的sex字段
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysUser.sex")
|
||||
public enum GenderEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 用户性别
|
||||
*/
|
||||
MALE(1, "男", CssTag.PRIMARY),
|
||||
FEMALE(2, "女", CssTag.PRIMARY),
|
||||
UNKNOWN(0, "未知", CssTag.PRIMARY);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
GenderEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
* @author valarchie
|
||||
*/
|
||||
// TODO 表记得改成LoginLog
|
||||
@Dictionary(name = "sysLoginLog.status")
|
||||
public enum LoginStatusEnum implements DictionaryEnum<Integer> {
|
||||
/**
|
||||
* status of user
|
||||
*/
|
||||
LOGIN_SUCCESS(1, "登录成功", CssTag.SUCCESS),
|
||||
LOGOUT(2, "退出成功", CssTag.INFO),
|
||||
REGISTER(3, "注册", CssTag.PRIMARY),
|
||||
LOGIN_FAIL(0, "登录失败", CssTag.DANGER);
|
||||
|
||||
private final int value;
|
||||
private final String msg;
|
||||
private final String cssTag;
|
||||
|
||||
LoginStatusEnum(int status, String msg, String cssTag) {
|
||||
this.value = status;
|
||||
this.msg = msg;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.BasicEnum;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Deprecated
|
||||
public enum MenuComponentEnum implements BasicEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 菜单组件类型
|
||||
*/
|
||||
LAYOUT(1,"Layout"),
|
||||
PARENT_VIEW(2,"ParentView"),
|
||||
INNER_LINK(3,"InnerLink");
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
|
||||
MenuComponentEnum(int value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.BasicEnum;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
* 对应 sys_menu表的menu_type字段
|
||||
*/
|
||||
public enum MenuTypeEnum implements BasicEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
MENU(1, "页面"),
|
||||
CATALOG(2, "目录"),
|
||||
IFRAME(3, "内嵌Iframe"),
|
||||
OUTSIDE_LINK_REDIRECT(4, "外链跳转");
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
|
||||
MenuTypeEnum(int value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_notice的 status字段
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysNotice.status")
|
||||
public enum NoticeStatusEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 通知状态
|
||||
*/
|
||||
OPEN(1, "正常", CssTag.PRIMARY),
|
||||
CLOSE(0, "关闭", CssTag.DANGER);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
NoticeStatusEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_notice的 notice_type字段
|
||||
* 名称一般由对应的表名.字段构成
|
||||
* 全局的话使用common作为表名
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysNotice.noticeType")
|
||||
public enum NoticeTypeEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
NOTIFICATION(1, "通知", CssTag.WARNING),
|
||||
ANNOUNCEMENT(2, "公告", CssTag.SUCCESS);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
NoticeTypeEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_operation_log的status字段
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysOperationLog.status")
|
||||
public enum OperationStatusEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 操作状态
|
||||
*/
|
||||
SUCCESS(1, "成功", CssTag.PRIMARY),
|
||||
FAIL(0, "失败", CssTag.DANGER);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
OperationStatusEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.BasicEnum;
|
||||
|
||||
/**
|
||||
* 操作者类型
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysOperationLog.operatorType")
|
||||
public enum OperatorTypeEnum implements BasicEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
OTHER(1, "其他"),
|
||||
WEB(2, "Web用户"),
|
||||
MOBILE(3, "手机端用户");
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
|
||||
OperatorTypeEnum(int value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.BasicEnum;
|
||||
|
||||
/**
|
||||
* Http Method
|
||||
* @author valarchie
|
||||
*/
|
||||
public enum RequestMethodEnum implements BasicEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
GET(1, "GET"),
|
||||
POST(2, "POST"),
|
||||
PUT(3, "PUT"),
|
||||
DELETE(4, "DELETE"),
|
||||
UNKNOWN(-1, "UNKNOWN");
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
|
||||
RequestMethodEnum(int value, String description) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 除非表有特殊指明的话,一般用这个枚举代表 status字段
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "common.status")
|
||||
public enum StatusEnum implements DictionaryEnum<Integer> {
|
||||
/**
|
||||
* 开关状态
|
||||
*/
|
||||
ENABLE(1, "正常", CssTag.PRIMARY),
|
||||
DISABLE(0, "停用", CssTag.DANGER);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
StatusEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_user的status字段
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "sysUser.status")
|
||||
public enum UserStatusEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 用户账户状态
|
||||
*/
|
||||
NORMAL(1, "正常", CssTag.PRIMARY),
|
||||
DISABLED(2, "禁用", CssTag.DANGER),
|
||||
FROZEN(3, "冻结", CssTag.WARNING);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
|
||||
private final String cssTag;
|
||||
|
||||
UserStatusEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
|
||||
/**
|
||||
* 对应sys_menu表的is_visible字段
|
||||
* @author valarchie
|
||||
*/
|
||||
@Deprecated
|
||||
@Dictionary(name = "sysMenu.isVisible")
|
||||
public enum VisibleStatusEnum implements DictionaryEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 显示与否
|
||||
*/
|
||||
SHOW(1, "显示", CssTag.PRIMARY),
|
||||
HIDE(0, "隐藏", CssTag.DANGER);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
VisibleStatusEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.agileboot.common.enums.common;
|
||||
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
import com.agileboot.common.enums.dictionary.CssTag;
|
||||
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||
|
||||
/**
|
||||
* 系统内代表是与否的枚举
|
||||
* @author valarchie
|
||||
*/
|
||||
@Dictionary(name = "common.yesOrNo")
|
||||
public enum YesOrNoEnum implements DictionaryEnum<Integer> {
|
||||
/**
|
||||
* 是与否
|
||||
*/
|
||||
YES(1, "是", CssTag.PRIMARY),
|
||||
NO(0, "否", CssTag.DANGER);
|
||||
|
||||
private final int value;
|
||||
private final String description;
|
||||
private final String cssTag;
|
||||
|
||||
YesOrNoEnum(int value, String description, String cssTag) {
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
this.cssTag = cssTag;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String cssTag() {
|
||||
return cssTag;
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.agileboot.common.enums.dictionary;
|
||||
|
||||
/**
|
||||
* Css 样式
|
||||
* @author valarchie
|
||||
*/
|
||||
public class CssTag {
|
||||
|
||||
public static final String PRIMARY = "";
|
||||
public static final String DANGER = "danger";
|
||||
public static final String WARNING = "warning";
|
||||
public static final String SUCCESS = "success";
|
||||
public static final String INFO = "info";
|
||||
|
||||
private CssTag() {
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.agileboot.common.enums.dictionary;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 字典类型注解
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Dictionary {
|
||||
|
||||
/**
|
||||
* 字典类型名称
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.agileboot.common.enums.dictionary;
|
||||
|
||||
import com.agileboot.common.enums.DictionaryEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 字典模型类
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
public class DictionaryData {
|
||||
|
||||
private String label;
|
||||
private Integer value;
|
||||
private String cssTag;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public DictionaryData(DictionaryEnum enumType) {
|
||||
if (enumType != null) {
|
||||
this.label = enumType.description();
|
||||
this.value = (Integer) enumType.getValue();
|
||||
this.cssTag = enumType.cssTag();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.agileboot.common.exception;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.common.exception.error.ErrorCodeInterface;
|
||||
import com.agileboot.common.utils.i18n.MessageUtils;
|
||||
import java.util.HashMap;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 统一异常类
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
@Data
|
||||
public class ApiException extends RuntimeException {
|
||||
|
||||
protected ErrorCodeInterface errorCode;
|
||||
|
||||
protected String message;
|
||||
|
||||
protected String i18nMessage;
|
||||
|
||||
/**
|
||||
* 如果有一些特殊的数据 可以放在这个payload里面
|
||||
* 有时候错误的返回信息太少 不便前端处理的话 可以放在这个payload字段当中
|
||||
* 比如你做了一个大批量操作,操作ID为1~10的实体, 其中1~5成功 6~10失败
|
||||
* 你可以将这些相关信息放在这个payload中
|
||||
*/
|
||||
protected HashMap<String, Object> payload;
|
||||
|
||||
public ApiException(ErrorCodeInterface errorCode) {
|
||||
fillErrorCode(errorCode);
|
||||
}
|
||||
|
||||
public ApiException(ErrorCodeInterface errorCode, Object... args) {
|
||||
fillErrorCode(errorCode, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注意 如果是try catch的情况下捕获异常 并转为ApiException的话 一定要填入Throwable e
|
||||
* @param e 捕获到的原始异常
|
||||
* @param errorCode 错误码
|
||||
* @param args 错误详细信息参数
|
||||
*/
|
||||
public ApiException(Throwable e, ErrorCodeInterface errorCode, Object... args) {
|
||||
super(e);
|
||||
fillErrorCode(errorCode, args);
|
||||
}
|
||||
|
||||
private void fillErrorCode(ErrorCodeInterface errorCode, Object... args) {
|
||||
this.errorCode = errorCode;
|
||||
this.message = StrUtil.format(errorCode.message(), args);
|
||||
|
||||
try {
|
||||
this.i18nMessage = MessageUtils.message(errorCode.i18nKey(), args);
|
||||
} catch (Exception e) {
|
||||
log.error("could not found i18nMessage entry for key: " + errorCode.i18nKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return i18nMessage != null ? i18nMessage : message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalizedMessage() {
|
||||
return i18nMessage != null ? i18nMessage : message;
|
||||
}
|
||||
|
||||
}
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
package com.agileboot.common.exception.error;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 错误码集合
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
public enum ErrorCode implements ErrorCodeInterface {
|
||||
|
||||
/**
|
||||
* 错误码集合
|
||||
* ******以下是旧的设计****
|
||||
* 1~9999 为保留错误码 或者 常用错误码
|
||||
* 10000~19999 为内部错误码
|
||||
* 20000~29999 客户端错误码 (客户端异常调用之类的错误)
|
||||
* 30000~39999 为第三方错误码 (代码正常,但是第三方异常)
|
||||
* 40000~49999 为业务逻辑 错误码 (无异常,代码正常流转,并返回提示给用户)
|
||||
* 由于系统内的错误码都是独一无二的,所以错误码应该放在common包集中管理
|
||||
* ---------------------------
|
||||
* 旧的设计的缺陷,比如内部错误码其实并不会很多 但是占用了1~9999的序列,其实是不必要的。
|
||||
* 而且错误码不一定位数一定要相同。比如腾讯的微信接口错误码的位数就并不相同。按照常理错误码的数量大小应该是:
|
||||
* 内部错误码< 客户端错误码< 第三方错误码< 业务错误码
|
||||
* 所以我们应该尽可能的把错误码的数量留给业务错误码
|
||||
* ---------------------------
|
||||
* *******新的设计**********
|
||||
* 1~99 为内部错误码(框架本身的错误)
|
||||
* 100~999 客户端错误码 (客户端异常调用之类的错误)
|
||||
* 1000~9999为第三方错误码 (代码正常,但是第三方异常)
|
||||
* 10000~99999 为业务逻辑 错误码 (无异常,代码正常流转,并返回提示给用户)
|
||||
* 由于系统内的错误码都是独一无二的,所以错误码应该放在common包集中管理
|
||||
* ---------------------------
|
||||
* 总体设计就是值越小 错误严重性越高
|
||||
* 目前10000~19999是初始系统内嵌功能使用的错误码,后续开发者可以直接使用20000以上的错误码作为业务错误码
|
||||
*/
|
||||
|
||||
SUCCESS(0, "操作成功", "SUCCESS"),
|
||||
FAILED(99999, "操作失败", "FAILED");
|
||||
|
||||
private final int code;
|
||||
private final String msg;
|
||||
private final String i18nKey;
|
||||
|
||||
ErrorCode(int code, String msg, String i18nKey) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String i18nKey() {
|
||||
return this.i18nKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10000~99999 为业务逻辑 错误码 (无代码异常,代码正常流转,并返回提示给用户)
|
||||
* 1XX01 XX是代表模块的意思 比如10101 01是Permission模块
|
||||
* 错误码的命名最好以模块为开头 比如 NOT_ALLOWED_TO_OPERATE前面加上PERMISSION = PERMISSION_NOT_ALLOWED_TO_OPERATE
|
||||
*/
|
||||
public enum Business implements ErrorCodeInterface {
|
||||
|
||||
// ----------------------------- COMMON --------------------------------------
|
||||
|
||||
COMMON_OBJECT_NOT_FOUND(10001, "找不到ID为 {} 的 {}", "Business.OBJECT_NOT_FOUND"),
|
||||
|
||||
COMMON_UNSUPPORTED_OPERATION(10002, "不支持的操作", "Business.UNSUPPORTED_OPERATION"),
|
||||
|
||||
COMMON_BULK_DELETE_IDS_IS_INVALID(10003, "批量参数ID列表为空", "Business.BULK_DELETE_IDS_IS_INVALID"),
|
||||
|
||||
COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD(10004, "文件名称({})非法,不允许下载", "Business.FILE_NOT_ALLOWED_TO_DOWNLOAD"),
|
||||
|
||||
// ----------------------------- PERMISSION -----------------------------------
|
||||
|
||||
PERMISSION_FORBIDDEN_TO_MODIFY_ADMIN(10101, "不允许修改管理员的信息", "Business.FORBIDDEN_TO_MODIFY_ADMIN"),
|
||||
|
||||
PERMISSION_NOT_ALLOWED_TO_OPERATE(10202, "没有权限进行此操作,请联系管理员", "Business.NO_PERMISSION_TO_OPERATE"),
|
||||
|
||||
// ----------------------------- LOGIN -----------------------------------------
|
||||
|
||||
LOGIN_WRONG_USER_PASSWORD(10201, "用户密码错误,请重输", "Business.LOGIN_WRONG_USER_PASSWORD"),
|
||||
|
||||
LOGIN_ERROR(10202, "登录失败:{}", "Business.LOGIN_ERROR"),
|
||||
|
||||
LOGIN_CAPTCHA_CODE_WRONG(10203, "验证码错误", "Business.LOGIN_CAPTCHA_CODE_WRONG"),
|
||||
|
||||
LOGIN_CAPTCHA_CODE_EXPIRE(10204, "验证码过期", "Business.LOGIN_CAPTCHA_CODE_EXPIRE"),
|
||||
|
||||
LOGIN_CAPTCHA_CODE_NULL(10205, "验证码为空", "Business.LOGIN_CAPTCHA_CODE_NULL"),
|
||||
|
||||
// ----------------------------- UPLOAD -----------------------------------------
|
||||
|
||||
UPLOAD_FILE_TYPE_NOT_ALLOWED(10401, "不允许上传的文件类型,仅允许:{}", "Business.UPLOAD_FILE_TYPE_NOT_ALLOWED"),
|
||||
|
||||
UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH(10402, "文件名长度超过:{} ", "Business.UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH"),
|
||||
|
||||
UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE(10403, "文件名大小超过:{} MB", "Business.UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE"),
|
||||
|
||||
UPLOAD_IMPORT_EXCEL_FAILED(10404, "导入excel失败:{}", "Business.UPLOAD_IMPORT_EXCEL_FAILED"),
|
||||
|
||||
UPLOAD_FILE_IS_EMPTY(10405, "上传文件为空", "Business.UPLOAD_FILE_IS_EMPTY"),
|
||||
|
||||
UPLOAD_FILE_FAILED(10406, "上传文件失败:{}", "Business.UPLOAD_FILE_FAILED"),
|
||||
|
||||
// ----------------------------- CONFIG -----------------------------------------
|
||||
|
||||
CONFIG_VALUE_IS_NOT_ALLOW_TO_EMPTY(10601, "参数键值不允许为空", "Business.CONFIG_VALUE_IS_NOT_ALLOW_TO_EMPTY"),
|
||||
|
||||
CONFIG_VALUE_IS_NOT_IN_OPTIONS(10602, "参数键值不存在列表中", "Business.CONFIG_VALUE_IS_NOT_IN_OPTIONS"),
|
||||
|
||||
// ------------------------------- POST --------------------------------------------
|
||||
|
||||
POST_NAME_IS_NOT_UNIQUE(10701, "岗位名称:{}, 已存在", "Business.POST_NAME_IS_NOT_UNIQUE"),
|
||||
|
||||
POST_CODE_IS_NOT_UNIQUE(10702, "岗位编号:{}, 已存在", "Business.POST_CODE_IS_NOT_UNIQUE"),
|
||||
|
||||
POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED(10703, "职位已分配给用户,请先取消分配再删除", "Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED"),
|
||||
|
||||
// ------------------------------- DEPT ---------------------------------------------
|
||||
|
||||
DEPT_NAME_IS_NOT_UNIQUE(10801, "部门名称:{}, 已存在", "Business.DEPT_NAME_IS_NOT_UNIQUE"),
|
||||
|
||||
DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF(10802, "父级部门不能选择自己", "Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF"),
|
||||
|
||||
DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE(10803, "子部门还有正在启用的部门,暂时不能停用该部门", "Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE"),
|
||||
|
||||
DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE(10804, "该部门存在下级部门不允许删除", "Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE"),
|
||||
|
||||
DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE(10805, "该部门存在关联的用户不允许删除", "Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE"),
|
||||
|
||||
DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED(10806, "该父级部门不存在或已停用", "Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED"),
|
||||
|
||||
// ------------------------------- MENU -------------------------------------------------
|
||||
|
||||
MENU_NAME_IS_NOT_UNIQUE(10901, "新增菜单:{} 失败,菜单名称已存在", "Business.MENU_NAME_IS_NOT_UNIQUE"),
|
||||
|
||||
MENU_EXTERNAL_LINK_MUST_BE_HTTP(10902, "菜单外链必须以 http(s)://开头", "Business.MENU_EXTERNAL_LINK_MUST_BE_HTTP"),
|
||||
|
||||
MENU_PARENT_ID_NOT_ALLOW_SELF(10903, "父级菜单不能选择自身", "Business.MENU_PARENT_ID_NOT_ALLOW_SELF"),
|
||||
|
||||
MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE(10904, "存在子菜单不允许删除", "Business.MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE"),
|
||||
|
||||
MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE(10905, "菜单已分配给角色,不允许", "Business.MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE"),
|
||||
|
||||
MENU_NOT_ALLOWED_TO_CREATE_BUTTON_ON_IFRAME_OR_OUT_LINK(10906, "不允许在Iframe和外链跳转类型下创建按钮", "Business.MENU_ONLY_ALLOWED_TO_CREATE_BUTTON_ON_PAGE"),
|
||||
|
||||
MENU_ONLY_ALLOWED_TO_CREATE_SUB_MENU_IN_CATALOG(10907, "只允许在目录类型底下创建子菜单", "Business.MENU_ONLY_ALLOWED_TO_CREATE_SUB_MENU_IN_CATALOG"),
|
||||
|
||||
MENU_CAN_NOT_CHANGE_MENU_TYPE(10908, "不允许更改菜单的类型", "Business.MENU_CAN_NOT_CHANGE_MENU_TYPE"),
|
||||
|
||||
// -------------------------------- ROLE -------------------------------------------------
|
||||
|
||||
ROLE_NAME_IS_NOT_UNIQUE(11001, "角色名称:{}, 已存在", "Business.ROLE_NAME_IS_NOT_UNIQUE"),
|
||||
|
||||
ROLE_KEY_IS_NOT_UNIQUE(11002, "角色标识:{}, 已存在", "Business.ROLE_KEY_IS_NOT_UNIQUE"),
|
||||
|
||||
ROLE_DATA_SCOPE_DUPLICATED_DEPT(11003, "重复的部门id", "Business.ROLE_DATA_SCOPE_DUPLICATED_DEPT"),
|
||||
|
||||
ROLE_ALREADY_ASSIGN_TO_USER(11004, "角色已分配给用户,请先取消分配,再删除角色", "Business.ROLE_ALREADY_ASSIGN_TO_USER"),
|
||||
|
||||
ROLE_IS_NOT_AVAILABLE(11005, "角色:{} 已禁用,无法分配给用户", "Business.ROLE_IS_NOT_AVAILABLE"),
|
||||
|
||||
// ---------------------------------- USER -----------------------------------------------
|
||||
|
||||
USER_NON_EXIST(10501, "登录用户:{} 不存在", "Business.USER_NON_EXIST"),
|
||||
|
||||
USER_IS_DISABLE(10502, "对不起, 您的账号:{} 已停用", "Business.USER_IS_DISABLE"),
|
||||
|
||||
USER_CACHE_IS_EXPIRE(11003, "用户缓存信息已经过期", "Business.USER_CACHE_IS_EXPIRE"),
|
||||
|
||||
USER_FAIL_TO_GET_USER_ID(11004, "获取用户ID失败", "Business.USER_FAIL_TO_GET_USER_ID"),
|
||||
|
||||
USER_FAIL_TO_GET_DEPT_ID(10504, "获取用户部门ID失败", "Business.USER_FAIL_TO_GET_DEPT_ID"),
|
||||
|
||||
USER_FAIL_TO_GET_ACCOUNT(10505, "获取用户账户失败", "Business.USER_FAIL_TO_GET_ACCOUNT"),
|
||||
|
||||
USER_FAIL_TO_GET_USER_INFO(10506, "获取用户信息失败", "Business.USER_FAIL_TO_GET_USER_INFO"),
|
||||
|
||||
USER_IMPORT_DATA_IS_NULL(10507, "导入的用户为空", "Business.USER_IMPORT_DATA_IS_NULL"),
|
||||
|
||||
USER_PHONE_NUMBER_IS_NOT_UNIQUE(10508, "该电话号码已被其他用户占用", "Business.USER_PHONE_NUMBER_IS_NOT_UNIQUE"),
|
||||
|
||||
USER_EMAIL_IS_NOT_UNIQUE(10509, "该邮件地址已被其他用户占用", "Business.USER_EMAIL_IS_NOT_UNIQUE"),
|
||||
|
||||
USER_PASSWORD_IS_NOT_CORRECT(10510, "用户密码错误", "Business.USER_PASSWORD_IS_NOT_CORRECT"),
|
||||
|
||||
USER_NEW_PASSWORD_IS_THE_SAME_AS_OLD(10511, "用户新密码与旧密码相同", "Business.USER_NEW_PASSWORD_IS_THE_SAME_AS_OLD"),
|
||||
|
||||
USER_UPLOAD_FILE_FAILED(10512, "用户上传文件失败", "Business.USER_UPLOAD_FILE_FAILED"),
|
||||
|
||||
USER_NAME_IS_NOT_UNIQUE(10513, "用户名已被其他用户占用", "Business.USER_NAME_IS_NOT_UNIQUE"),
|
||||
|
||||
USER_CURRENT_USER_CAN_NOT_BE_DELETE(10514, "当前用户不允许被删除", "Business.USER_CURRENT_USER_CAN_NOT_BE_DELETE"),
|
||||
|
||||
USER_ADMIN_CAN_NOT_BE_MODIFY(10515, "管理员不允许做任何修改", "Business.USER_ADMIN_CAN_NOT_BE_MODIFY"),
|
||||
|
||||
;
|
||||
|
||||
|
||||
private final int code;
|
||||
private final String msg;
|
||||
|
||||
private final String i18nKey;
|
||||
|
||||
Business(int code, String msg, String i18nKey) {
|
||||
Assert.isTrue(code > 10000 && code < 99999,
|
||||
"错误码code值定义失败,Business错误码code值范围在10000~99099之间,请查看ErrorCode.Business类,当前错误码码为" + name());
|
||||
|
||||
String errorTypeName = this.getClass().getSimpleName();
|
||||
Assert.isTrue(i18nKey != null && i18nKey.startsWith(errorTypeName),
|
||||
String.format("错误码i18nKey值定义失败,%s错误码i18nKey值必须以%s开头,当前错误码为%s", errorTypeName, errorTypeName, name()));
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String i18nKey() {
|
||||
return i18nKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1000~9999是外部错误码 比如调用支付失败
|
||||
*/
|
||||
public enum External implements ErrorCodeInterface {
|
||||
|
||||
/**
|
||||
* 支付宝调用失败
|
||||
*/
|
||||
FAIL_TO_PAY_ON_ALIPAY(1001, "支付宝调用失败", "External.FAIL_TO_PAY_ON_ALIPAY");
|
||||
|
||||
|
||||
private final int code;
|
||||
private final String msg;
|
||||
|
||||
private final String i18nKey;
|
||||
|
||||
External(int code, String msg, String i18nKey) {
|
||||
Assert.isTrue(code > 1000 && code < 9999,
|
||||
"错误码code值定义失败,External错误码code值范围在1000~9999之间,请查看ErrorCode.External类,当前错误码码为" + name());
|
||||
|
||||
String errorTypeName = this.getClass().getSimpleName();
|
||||
Assert.isTrue(i18nKey != null && i18nKey.startsWith(errorTypeName),
|
||||
String.format("错误码i18nKey值定义失败,%s错误码i18nKey值必须以%s开头,当前错误码为%s", errorTypeName, errorTypeName, name()));
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String i18nKey() {
|
||||
return this.i18nKey;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 100~999是客户端错误码
|
||||
* 客户端如 Web+小程序+手机端 调用出错
|
||||
* 可能由于参数问题或者授权问题或者调用过去频繁
|
||||
*/
|
||||
public enum Client implements ErrorCodeInterface {
|
||||
|
||||
COMMON_FORBIDDEN_TO_CALL(101, "禁止调用", "Client.COMMON_FORBIDDEN_TO_CALL"),
|
||||
|
||||
COMMON_REQUEST_TOO_OFTEN(102, "调用太过频繁", "Client.COMMON_REQUEST_TOO_OFTEN"),
|
||||
|
||||
COMMON_REQUEST_PARAMETERS_INVALID(103, "请求参数异常,{}", "Client.COMMON_REQUEST_PARAMETERS_INVALID"),
|
||||
|
||||
COMMON_REQUEST_METHOD_INVALID(104, "请求方式: {} 不支持", "Client.COMMON_REQUEST_METHOD_INVALID"),
|
||||
|
||||
COMMON_REQUEST_RESUBMIT(105, "请求重复提交", "Client.COMMON_REQUEST_RESUBMIT"),
|
||||
|
||||
COMMON_NO_AUTHORIZATION(106, "请求接口:{} 失败,用户未授权", "Client.COMMON_NO_AUTHORIZATION"),
|
||||
|
||||
INVALID_TOKEN(107, "token异常", "Client.INVALID_TOKEN"),
|
||||
|
||||
TOKEN_PROCESS_FAILED(108, "token处理失败:{}", "Client.TOKEN_PROCESS_FAILED"),
|
||||
|
||||
;
|
||||
|
||||
private final int code;
|
||||
private final String msg;
|
||||
private final String i18nKey;
|
||||
|
||||
Client(int code, String msg, String i18nKey) {
|
||||
Assert.isTrue(code > 100 && code < 999,
|
||||
"错误码code值定义失败,Client错误码code值范围在100~999之间,请查看ErrorCode.Client类,当前错误码码为" + name());
|
||||
|
||||
String errorTypeName = this.getClass().getSimpleName();
|
||||
Assert.isTrue(i18nKey != null && i18nKey.startsWith(errorTypeName),
|
||||
String.format("错误码i18nKey值定义失败,%s错误码i18nKey值必须以%s开头,当前错误码为%s", errorTypeName, errorTypeName, name()));
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String i18nKey() {
|
||||
return this.i18nKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 0~99是内部错误码 例如 框架内部问题之类的
|
||||
*/
|
||||
public enum Internal implements ErrorCodeInterface {
|
||||
/**
|
||||
* 内部错误码
|
||||
*/
|
||||
INVALID_PARAMETER(1, "参数异常:{}", "Internal.INVALID_PARAMETER"),
|
||||
|
||||
/**
|
||||
* 该错误主要用于返回 未知的异常(大部分是RuntimeException) 程序未能捕获 未能预料的错误
|
||||
*/
|
||||
INTERNAL_ERROR(2, "系统内部错误:{}", "Internal.INTERNAL_ERROR"),
|
||||
|
||||
GET_ENUM_FAILED(3, "获取枚举类型失败, 枚举类:{}", "Internal.GET_ENUM_FAILED"),
|
||||
|
||||
GET_CACHE_FAILED(4, "获取缓存失败:{}", "Internal.GET_CACHE_FAILED"),
|
||||
|
||||
DB_INTERNAL_ERROR(5, "数据库异常", "Internal.DB_INTERNAL_ERROR"),
|
||||
|
||||
LOGIN_CAPTCHA_GENERATE_FAIL(7, "验证码生成失败", "Internal.LOGIN_CAPTCHA_GENERATE_FAIL"),
|
||||
|
||||
EXCEL_PROCESS_ERROR(8, "excel处理失败:{}", "Internal.EXCEL_PROCESS_ERROR"),
|
||||
|
||||
;
|
||||
|
||||
private final int code;
|
||||
private final String msg;
|
||||
|
||||
private final String i18nKey;
|
||||
|
||||
Internal(int code, String msg, String i18nKey) {
|
||||
Assert.isTrue(code < 100,
|
||||
"错误码code值定义失败,Internal错误码code值范围在100~999之间,请查看ErrorCode.Internal类,当前错误码码为" + name());
|
||||
|
||||
String errorTypeName = this.getClass().getSimpleName();
|
||||
Assert.isTrue(i18nKey != null && i18nKey.startsWith(errorTypeName),
|
||||
String.format("错误码i18nKey值定义失败,%s错误码i18nKey值必须以%s开头,当前错误码为%s", errorTypeName, errorTypeName, name()));
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.i18nKey = i18nKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int code() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String i18nKey() {
|
||||
return this.i18nKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.agileboot.common.exception.error;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
public interface ErrorCodeInterface {
|
||||
|
||||
/**
|
||||
* 返回错误码
|
||||
* @return 错误码
|
||||
*/
|
||||
int code();
|
||||
|
||||
/**
|
||||
* 返回具体的详细错误描述
|
||||
* @return 错误描述
|
||||
*/
|
||||
String message();
|
||||
|
||||
/**
|
||||
* i18n资源文件的key, 详见messages.properties文件
|
||||
* @return key
|
||||
*/
|
||||
String i18nKey();
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.agileboot.common.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 客户端工具类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServletHolderUtil {
|
||||
|
||||
private ServletHolderUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取request
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
return getRequestAttributes().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取response
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
return getRequestAttributes().getResponse();
|
||||
}
|
||||
|
||||
|
||||
public static ServletRequestAttributes getRequestAttributes() {
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
return (ServletRequestAttributes) attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串渲染到客户端
|
||||
*
|
||||
* @param response 渲染对象
|
||||
* @param string 待渲染的字符串
|
||||
*/
|
||||
public static void renderString(HttpServletResponse response, String string) {
|
||||
try {
|
||||
response.setStatus(200);
|
||||
response.setContentType("application/json");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.getWriter().print(string);
|
||||
} catch (IOException e) {
|
||||
log.error("返回response失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取仅含有项目根路径的url
|
||||
* 比如 localhost:8080/agileboot/user/list
|
||||
* 返回 localhost:8080/agileboot
|
||||
* @return localhost:8080/agileboot
|
||||
*/
|
||||
public static String getContextUrl() {
|
||||
HttpServletRequest request = getRequest();
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String contextPath = request.getServletContext().getContextPath();
|
||||
String strip = StrUtil.strip(url, null, request.getRequestURI());
|
||||
return strip + contextPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package com.agileboot.common.utils.file;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import com.agileboot.common.config.AgileBootConfig;
|
||||
import com.agileboot.common.constant.Constants;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||
import com.agileboot.common.exception.error.ErrorCode.Internal;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 文件上传工具类
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
public class FileUploadUtils {
|
||||
|
||||
/**
|
||||
* 默认大小 50M
|
||||
*/
|
||||
public static final long MAX_FILE_SIZE = 50 * Constants.MB;
|
||||
|
||||
/**
|
||||
* 默认的文件名最大长度 127
|
||||
*/
|
||||
public static final int MAX_FILE_NAME_LENGTH = 127;
|
||||
|
||||
/**
|
||||
* 允许上传和下载的文件类型
|
||||
*/
|
||||
private static final String[] ALLOWED_EXTENSIONS = {
|
||||
// 图片
|
||||
"bmp", "gif", "jpg", "jpeg", "png",
|
||||
// word excel powerpoint
|
||||
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
|
||||
// 压缩文件
|
||||
"rar", "zip", "gz", "bz2",
|
||||
// 视频格式
|
||||
"mp4", "avi", "rmvb",
|
||||
// pdf
|
||||
"pdf"};
|
||||
|
||||
private FileUploadUtils() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据文件路径上传
|
||||
*
|
||||
* @param subDir 相对应用的基目录
|
||||
* @param file 上传的文件
|
||||
* @return 文件名称
|
||||
*/
|
||||
public static String upload(String subDir, MultipartFile file) {
|
||||
try {
|
||||
return upload(subDir, file, ALLOWED_EXTENSIONS);
|
||||
} catch (Exception e) {
|
||||
throw new ApiException(Business.UPLOAD_FILE_FAILED, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
*
|
||||
* @param subDir 相对应用的子目录
|
||||
* @param file 上传的文件
|
||||
* @param allowedExtension 上传文件类型
|
||||
* @return 返回上传成功的文件名
|
||||
* @throws IOException 比如读写文件出错时
|
||||
*/
|
||||
public static String upload(String subDir, MultipartFile file, String[] allowedExtension)
|
||||
throws IOException {
|
||||
isAllowedUpload(file, allowedExtension);
|
||||
String fileName = generateFilename(file);
|
||||
saveFileToLocal(file, subDir, fileName);
|
||||
return getRelativeFileUrl(subDir, fileName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将文件保存到服务器
|
||||
*
|
||||
* @param file 文件
|
||||
* @param subDir 子目录
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
static void saveFileToLocal(MultipartFile file, String subDir, String fileName) throws IOException {
|
||||
if (StrUtil.isEmpty(subDir) || StrUtil.isEmpty(fileName)) {
|
||||
throw new ApiException(Internal.INVALID_PARAMETER, "subDir or fileName");
|
||||
}
|
||||
|
||||
File destination = new File(getFileAbsolutePath(subDir, fileName));
|
||||
if (!destination.exists()) {
|
||||
if (!destination.getParentFile().exists()) {
|
||||
destination.getParentFile().mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
file.transferTo(destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的相对地址
|
||||
*
|
||||
* @param subDir avatar
|
||||
* @param fileName test.jpg
|
||||
* @return /profile/avatar/test.jpg
|
||||
*/
|
||||
static String getRelativeFileUrl(String subDir, String fileName) {
|
||||
// 相对地址用于网络请求 所以不使用File.separate
|
||||
return StrUtil.format("/{}/{}/{}", Constants.RESOURCE_PREFIX, subDir, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测文件是否可以上传
|
||||
*
|
||||
* @param file 文件
|
||||
* @param allowedExtension 允许的文件类型列表
|
||||
*/
|
||||
static void isAllowedUpload(MultipartFile file, String[] allowedExtension) {
|
||||
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
|
||||
|
||||
if (fileNameLength > MAX_FILE_NAME_LENGTH) {
|
||||
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH, MAX_FILE_NAME_LENGTH);
|
||||
}
|
||||
|
||||
long size = file.getSize();
|
||||
if (size > MAX_FILE_SIZE) {
|
||||
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE, MAX_FILE_SIZE / Constants.MB);
|
||||
}
|
||||
|
||||
String extension = getFileExtension(file);
|
||||
if (!isExtensionAllowed(extension, allowedExtension)) {
|
||||
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_TYPE_NOT_ALLOWED,
|
||||
StrUtil.join(",", (Object[]) allowedExtension));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查文件是否可下载
|
||||
*
|
||||
* @param resource 需要下载的文件
|
||||
* @return true 正常 false 非法
|
||||
*/
|
||||
public static boolean isAllowDownload(String resource) {
|
||||
// 禁止目录上跳级别
|
||||
return !StrUtil.contains(resource, "..") &&
|
||||
// 检查允许下载的文件规则
|
||||
StrUtil.containsAnyIgnoreCase(FileNameUtil.getSuffix(resource), ALLOWED_EXTENSIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断MIME类型是否是允许的MIME类型
|
||||
*/
|
||||
static boolean isExtensionAllowed(String extension, String[] allowedExtension) {
|
||||
if (allowedExtension == null || allowedExtension.length == 0) {
|
||||
return true;
|
||||
}
|
||||
return StrUtil.containsAnyIgnoreCase(extension, allowedExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名的后缀
|
||||
*
|
||||
* @param file 表单文件
|
||||
* @return 后缀名
|
||||
*/
|
||||
static String getFileExtension(MultipartFile file) {
|
||||
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
|
||||
if (StrUtil.isEmpty(extension)) {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType(Objects.requireNonNull(file.getContentType()));
|
||||
extension = mimeType.getSubtype();
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码文件名
|
||||
*/
|
||||
static String generateFilename(MultipartFile file) {
|
||||
return StrUtil.format("{}_{}_{}.{}",
|
||||
DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN),
|
||||
FilenameUtils.getBaseName(file.getOriginalFilename()),
|
||||
IdUtil.simpleUUID(),
|
||||
getFileExtension(file));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 下载文件名重新编码
|
||||
*
|
||||
* @param fileName 真实文件名
|
||||
*/
|
||||
public static HttpHeaders getDownloadHeader(String fileName) {
|
||||
String randomFileName = System.currentTimeMillis() + "_" + fileName;
|
||||
String fileNameUrlEncoded = URLUtil.encode(randomFileName, CharsetUtil.CHARSET_UTF_8);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Content-Disposition", String.format("attachment;filename=%s", fileNameUrlEncoded));
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
public static String getFileAbsolutePath(String subDir, String fileName) {
|
||||
return AgileBootConfig.getFileBaseDir() + File.separator + subDir + File.separator + fileName;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.agileboot.common.utils.i18n;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
/**
|
||||
* 获取i18n资源文件
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
public class MessageUtils {
|
||||
|
||||
private MessageUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据消息键和参数 获取消息 委托给spring messageSource
|
||||
*
|
||||
* @param code 消息键
|
||||
* @param args 参数
|
||||
* @return 获取国际化翻译值
|
||||
*/
|
||||
public static String message(String code, Object... args) {
|
||||
MessageSource messageSource = SpringUtil.getBean(MessageSource.class);
|
||||
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class IpRegion {
|
||||
private static final String UNKNOWN = "未知";
|
||||
private String country;
|
||||
private String region;
|
||||
private String province;
|
||||
private String city;
|
||||
private String isp;
|
||||
|
||||
public IpRegion(String province, String city) {
|
||||
this.province = province;
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String briefLocation() {
|
||||
return String.format("%s %s",
|
||||
CharSequenceUtil.nullToDefault(province, UNKNOWN),
|
||||
CharSequenceUtil.nullToDefault(city, UNKNOWN)).trim();
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* IP地理位置工具类
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
public class IpRegionUtil {
|
||||
|
||||
private IpRegionUtil() {
|
||||
}
|
||||
|
||||
public static IpRegion getIpRegion(String ip) {
|
||||
if (StrUtil.isEmpty(ip)) {
|
||||
return new IpRegion();
|
||||
}
|
||||
|
||||
if (IpUtil.isInnerIp(ip)) {
|
||||
return new IpRegion("", "内网IP");
|
||||
}
|
||||
|
||||
IpRegion ipRegionOffline = OfflineIpRegionUtil.getIpRegion(ip);
|
||||
if (ipRegionOffline != null) {
|
||||
return ipRegionOffline;
|
||||
}
|
||||
|
||||
IpRegion ipRegionOnline = OnlineIpRegionUtil.getIpRegion(ip);
|
||||
if (ipRegionOnline != null) {
|
||||
return ipRegionOnline;
|
||||
}
|
||||
|
||||
return new IpRegion();
|
||||
}
|
||||
|
||||
public static String getBriefLocationByIp(String ip) {
|
||||
return getIpRegion(ip).briefLocation();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* ip校验器
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class IpUtil {
|
||||
|
||||
public static final String INNER_IP_REGEX = "^(127\\.0\\.0\\.\\d{1,3})|(localhost)|(10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|(172\\.((1[6-9])|(2\\d)|(3[01]))\\.\\d{1,3}\\.\\d{1,3})|(192\\.168\\.\\d{1,3}\\.\\d{1,3})$";
|
||||
public static final Pattern INNER_IP_PATTERN = Pattern.compile(INNER_IP_REGEX);
|
||||
|
||||
private IpUtil() {
|
||||
}
|
||||
|
||||
public static boolean isInnerIp(String ip) {
|
||||
return INNER_IP_PATTERN.matcher(ip).matches() || isLocalHost(ip);
|
||||
}
|
||||
|
||||
public static boolean isLocalHost(String ipAddress) {
|
||||
InetAddress ia = null;
|
||||
try {
|
||||
InetAddress ad = InetAddress.getByName(ipAddress);
|
||||
byte[] ip = ad.getAddress();
|
||||
ia = InetAddress.getByAddress(ip);
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("解析Ip失败", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (ia == null) {
|
||||
return false;
|
||||
}
|
||||
return ia.isSiteLocalAddress() || ia.isLoopbackAddress();
|
||||
}
|
||||
|
||||
|
||||
public static boolean isValidIpv4(String inetAddress) {
|
||||
return Validator.isIpv4(inetAddress);
|
||||
}
|
||||
|
||||
public static boolean isValidIpv6(String inetAddress) {
|
||||
return Validator.isIpv6(inetAddress);
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class OfflineIpRegionUtil {
|
||||
|
||||
private static Searcher searcher;
|
||||
|
||||
private OfflineIpRegionUtil() {
|
||||
}
|
||||
|
||||
static {
|
||||
InputStream resourceAsStream = OfflineIpRegionUtil.class.getResourceAsStream("/ip2region.xdb");
|
||||
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
bytes = new byte[resourceAsStream.available()];
|
||||
IOUtils.read(resourceAsStream, bytes);
|
||||
} catch (IOException e) {
|
||||
log.error("读取本地Ip文件失败", e);
|
||||
}
|
||||
|
||||
try {
|
||||
searcher = Searcher.newWithBuffer(bytes);
|
||||
} catch (Exception e) {
|
||||
log.error("构建本地Ip缓存失败", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static IpRegion getIpRegion(String ip) {
|
||||
try {
|
||||
if (StrUtil.isBlank(ip) || IpUtil.isValidIpv6(ip)
|
||||
|| !IpUtil.isValidIpv4(ip)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String rawRegion = searcher.search(ip);
|
||||
|
||||
if (StrUtil.isEmpty(rawRegion)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] split = rawRegion.split("\\|");
|
||||
return new IpRegion(split[0], split[1], split[2], split[3], split[4]);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取IP地理位置失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.agileboot.common.config.AgileBootConfig;
|
||||
import com.agileboot.common.utils.jackson.JacksonUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* query geography address from ip
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class OnlineIpRegionUtil {
|
||||
|
||||
private OnlineIpRegionUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* website for query geography address from ip
|
||||
*/
|
||||
public static final String ADDRESS_QUERY_SITE = "http://whois.pconline.com.cn/ipJson.jsp";
|
||||
|
||||
|
||||
public static IpRegion getIpRegion(String ip) {
|
||||
if (StrUtil.isBlank(ip) || IpUtil.isValidIpv6(ip) || !IpUtil.isValidIpv4(ip)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (AgileBootConfig.isAddressEnabled()) {
|
||||
try {
|
||||
String rspStr = HttpUtil.get(ADDRESS_QUERY_SITE + "?ip=" + ip + "&json=true",
|
||||
CharsetUtil.CHARSET_GBK);
|
||||
|
||||
if (StrUtil.isEmpty(rspStr)) {
|
||||
log.error("获取地理位置异常 {}", ip);
|
||||
return null;
|
||||
}
|
||||
|
||||
String province = JacksonUtil.getAsString(rspStr, "pro");
|
||||
String city = JacksonUtil.getAsString(rspStr, "city");
|
||||
return new IpRegion(province, city);
|
||||
} catch (Exception e) {
|
||||
log.error("获取地理位置异常 {}", ip);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.agileboot.common.utils.jackson;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
public class JacksonException extends RuntimeException {
|
||||
|
||||
public JacksonException(String message, Exception e) {
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
}
|
||||
+684
@@ -0,0 +1,684 @@
|
||||
package com.agileboot.common.utils.jackson;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.type.CollectionType;
|
||||
import com.fasterxml.jackson.databind.type.MapType;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Jackson工具类 优势: 数据量高于百万的时候,速度和FastJson相差极小 API和注解支持最完善,可定制性最强
|
||||
* 支持的数据源最广泛(字符串,对象,文件、流、URL)
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class JacksonUtil {
|
||||
|
||||
private static ObjectMapper mapper;
|
||||
|
||||
private static final Set<JsonReadFeature> JSON_READ_FEATURES_ENABLED = CollUtil.newHashSet(
|
||||
//允许在JSON中使用Java注释
|
||||
JsonReadFeature.ALLOW_JAVA_COMMENTS,
|
||||
//允许 json 存在没用双引号括起来的 field
|
||||
JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES,
|
||||
//允许 json 存在使用单引号括起来的 field
|
||||
JsonReadFeature.ALLOW_SINGLE_QUOTES,
|
||||
//允许 json 存在没用引号括起来的 ascii 控制字符
|
||||
JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS,
|
||||
//允许 json number 类型的数存在前导 0 (例: 0001)
|
||||
JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS,
|
||||
//允许 json 存在 NaN, INF, -INF 作为 number 类型
|
||||
JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS,
|
||||
//允许 只有Key没有Value的情况
|
||||
JsonReadFeature.ALLOW_MISSING_VALUES,
|
||||
//允许数组json的结尾多逗号
|
||||
JsonReadFeature.ALLOW_TRAILING_COMMA
|
||||
);
|
||||
|
||||
static {
|
||||
try {
|
||||
//初始化
|
||||
mapper = initMapper();
|
||||
} catch (Exception e) {
|
||||
log.error("jackson config error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private JacksonUtil() {
|
||||
throw new IllegalStateException("Utility class JacksonUtil can not be instantiated");
|
||||
}
|
||||
|
||||
public static ObjectMapper initMapper() {
|
||||
JsonMapper.Builder builder = JsonMapper.builder()
|
||||
.enable(JSON_READ_FEATURES_ENABLED.toArray(new JsonReadFeature[0]));
|
||||
return initMapperConfig(builder.build());
|
||||
}
|
||||
|
||||
public static ObjectMapper initMapperConfig(ObjectMapper objectMapper) {
|
||||
String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
objectMapper.setDateFormat(new SimpleDateFormat(dateTimeFormat));
|
||||
//配置序列化级别
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
//配置JSON缩进支持
|
||||
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, false);
|
||||
//允许单个数值当做数组处理
|
||||
objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
|
||||
//禁止重复键, 抛出异常
|
||||
objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
|
||||
//禁止使用int代表Enum的order()來反序列化Enum, 抛出异常
|
||||
objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
|
||||
//有属性不能映射的时候不报错
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
//对象为空时不抛异常
|
||||
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
//时间格式
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
//允许未知字段
|
||||
objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
|
||||
//序列化BigDecimal时之间输出原始数字还是科学计数, 默认false, 即是否以toPlainString()科学计数方式来输出
|
||||
objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
|
||||
//识别Java8时间
|
||||
objectMapper.registerModule(new ParameterNamesModule());
|
||||
objectMapper.registerModule(new Jdk8Module());
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDateTime.class,
|
||||
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)))
|
||||
.addDeserializer(LocalDateTime.class,
|
||||
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
// if we use guava, we can add this line of code: objectMapper.registerModule(new GuavaModule())
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
public static ObjectMapper getObjectMapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(URL url, Class<V> type) {
|
||||
try {
|
||||
return mapper.readValue(url, type);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(URL url, TypeReference<V> type) {
|
||||
try {
|
||||
return mapper.readValue(url, type);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化(List)
|
||||
*/
|
||||
public static <V> List<V> fromList(URL url, Class<V> type) {
|
||||
try {
|
||||
CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
|
||||
return mapper.readValue(url, collectionType);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, url: {}, type: {}", url.getPath(), type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(InputStream inputStream, Class<V> type) {
|
||||
try {
|
||||
return mapper.readValue(inputStream, type);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(InputStream inputStream, TypeReference<V> type) {
|
||||
try {
|
||||
return mapper.readValue(inputStream, type);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化(List)
|
||||
*/
|
||||
public static <V> List<V> fromList(InputStream inputStream, Class<V> type) {
|
||||
try {
|
||||
CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
|
||||
return mapper.readValue(inputStream, collectionType);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, type: {}", type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(File file, Class<V> type) {
|
||||
try {
|
||||
return mapper.readValue(file, type);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(File file, TypeReference<V> type) {
|
||||
try {
|
||||
return mapper.readValue(file, type);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化(List)
|
||||
*/
|
||||
public static <V> List<V> fromList(File file, Class<V> type) {
|
||||
try {
|
||||
CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
|
||||
return mapper.readValue(file, collectionType);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson from error, file path: {}, type: {}", file.getPath(), type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(String json, Class<V> type) {
|
||||
return from(json, (Type) type);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(String json, TypeReference<V> type) {
|
||||
return from(json, type.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化
|
||||
*/
|
||||
public static <V> V from(String json, Type type) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JavaType javaType = mapper.getTypeFactory().constructType(type);
|
||||
return mapper.readValue(json, javaType);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json, type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化(List)
|
||||
*/
|
||||
public static <V> List<V> fromList(String json, Class<V> type) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
|
||||
return mapper.readValue(json, collectionType);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json, type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化(Map)
|
||||
*/
|
||||
public static Map<String, Object> fromMap(String json) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
|
||||
return mapper.readValue(json, mapType);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson from error, json: {}, type: {}", json), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为JSON
|
||||
*/
|
||||
public static <V> String to(List<V> list) {
|
||||
try {
|
||||
return mapper.writeValueAsString(list);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson to error, data: {}", list), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为JSON
|
||||
*/
|
||||
public static <V> String to(V v) {
|
||||
try {
|
||||
return mapper.writeValueAsString(v);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson to error, data: {}", v), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为JSON
|
||||
*/
|
||||
public static <V> void toFile(String path, List<V> list) {
|
||||
try (Writer writer = new FileWriter(path, true)) {
|
||||
mapper.writer().writeValues(writer).writeAll(list);
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson to file error, path: {}, list: {}", path, list), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为JSON
|
||||
*/
|
||||
public static <V> void toFile(String path, V v) {
|
||||
try (Writer writer = new FileWriter(path, true)) {
|
||||
mapper.writer().writeValues(writer).write(v);
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson to file error, path: {}, data: {}", path, v), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return String,默认为 null
|
||||
*/
|
||||
public static String getAsString(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return null;
|
||||
}
|
||||
return getAsString(jsonNode);
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get string error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getAsString(JsonNode jsonNode) {
|
||||
return jsonNode.isTextual() ? jsonNode.textValue() : jsonNode.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return int,默认为 0
|
||||
*/
|
||||
public static int getAsInt(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return 0;
|
||||
}
|
||||
return jsonNode.isInt() ? jsonNode.intValue() : Integer.parseInt(getAsString(jsonNode));
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get int error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return long,默认为 0
|
||||
*/
|
||||
public static long getAsLong(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return 0L;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return 0L;
|
||||
}
|
||||
return jsonNode.isLong() ? jsonNode.longValue() : Long.parseLong(getAsString(jsonNode));
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get long error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return double,默认为 0.0
|
||||
*/
|
||||
public static double getAsDouble(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return 0.0;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return 0.0;
|
||||
}
|
||||
return jsonNode.isDouble() ? jsonNode.doubleValue() : Double.parseDouble(getAsString(jsonNode));
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get double error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return BigInteger,默认为 0.0
|
||||
*/
|
||||
public static BigInteger getAsBigInteger(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return new BigInteger(String.valueOf(0.00));
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return new BigInteger(String.valueOf(0.00));
|
||||
}
|
||||
return jsonNode.isBigInteger() ? jsonNode.bigIntegerValue() : new BigInteger(getAsString(jsonNode));
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get big integer error, json: {}, key: {}", json, key),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return BigDecimal,默认为 0.00
|
||||
*/
|
||||
public static BigDecimal getAsBigDecimal(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return new BigDecimal("0.00");
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return new BigDecimal("0.00");
|
||||
}
|
||||
return jsonNode.isBigDecimal() ? jsonNode.decimalValue() : new BigDecimal(getAsString(jsonNode));
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get big decimal error, json: {}, key: {}", json, key),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return boolean, 默认为false
|
||||
*/
|
||||
public static boolean getAsBoolean(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return false;
|
||||
}
|
||||
if (jsonNode.isBoolean()) {
|
||||
return jsonNode.booleanValue();
|
||||
} else {
|
||||
if (jsonNode.isTextual()) {
|
||||
String textValue = jsonNode.textValue();
|
||||
return Convert.toBool(textValue);
|
||||
} else {//number
|
||||
return BooleanUtils.toBoolean(jsonNode.intValue());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get boolean error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return byte[], 默认为 null
|
||||
*/
|
||||
public static byte[] getAsBytes(String json, String key) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return new byte[0];
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return new byte[0];
|
||||
}
|
||||
return jsonNode.isBinary() ? jsonNode.binaryValue() : getAsString(jsonNode).getBytes();
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(StrUtil.format("jackson get byte error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return object, 默认为 null
|
||||
*/
|
||||
public static <V> V getAsObject(String json, String key, Class<V> type) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return null;
|
||||
}
|
||||
JavaType javaType = mapper.getTypeFactory().constructType(type);
|
||||
return from(getAsString(jsonNode), javaType);
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return list, 默认为 null
|
||||
*/
|
||||
public static <V> List<V> getAsList(String json, String key, Class<V> type) {
|
||||
if (StringUtils.isEmpty(json)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = getAsJsonObject(json, key);
|
||||
if (null == jsonNode) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, type);
|
||||
return from(getAsString(jsonNode), collectionType);
|
||||
} catch (Exception e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson get list error, json: {}, key: {}, type: {}", json, key, type), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json串中获取某个字段
|
||||
*
|
||||
* @return JsonNode, 默认为 null
|
||||
*/
|
||||
public static JsonNode getAsJsonObject(String json, String key) {
|
||||
try {
|
||||
JsonNode node = mapper.readTree(json);
|
||||
if (null == node) {
|
||||
return null;
|
||||
}
|
||||
return node.get(key);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson get object from json error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向json中添加属性
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
public static <V> String add(String json, String key, V value) {
|
||||
try {
|
||||
JsonNode node = mapper.readTree(json);
|
||||
add(node, key, value);
|
||||
return node.toString();
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson add error, json: {}, key: {}, value: {}", json, key, value), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向json中添加属性
|
||||
*/
|
||||
private static <V> void add(JsonNode jsonNode, String key, V value) {
|
||||
if (value instanceof String) {
|
||||
((ObjectNode) jsonNode).put(key, (String) value);
|
||||
} else if (value instanceof Short) {
|
||||
((ObjectNode) jsonNode).put(key, (Short) value);
|
||||
} else if (value instanceof Integer) {
|
||||
((ObjectNode) jsonNode).put(key, (Integer) value);
|
||||
} else if (value instanceof Long) {
|
||||
((ObjectNode) jsonNode).put(key, (Long) value);
|
||||
} else if (value instanceof Float) {
|
||||
((ObjectNode) jsonNode).put(key, (Float) value);
|
||||
} else if (value instanceof Double) {
|
||||
((ObjectNode) jsonNode).put(key, (Double) value);
|
||||
} else if (value instanceof BigDecimal) {
|
||||
((ObjectNode) jsonNode).put(key, (BigDecimal) value);
|
||||
} else if (value instanceof BigInteger) {
|
||||
((ObjectNode) jsonNode).put(key, (BigInteger) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
((ObjectNode) jsonNode).put(key, (Boolean) value);
|
||||
} else if (value instanceof byte[]) {
|
||||
((ObjectNode) jsonNode).put(key, (byte[]) value);
|
||||
} else {
|
||||
((ObjectNode) jsonNode).put(key, to(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 除去json中的某个属性
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
public static String remove(String json, String key) {
|
||||
try {
|
||||
JsonNode node = mapper.readTree(json);
|
||||
((ObjectNode) node).remove(key);
|
||||
return node.toString();
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson remove error, json: {}, key: {}", json, key), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改json中的属性
|
||||
*/
|
||||
public static <V> String update(String json, String key, V value) {
|
||||
try {
|
||||
JsonNode node = mapper.readTree(json);
|
||||
((ObjectNode) node).remove(key);
|
||||
add(node, key, value);
|
||||
return node.toString();
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(
|
||||
StrUtil.format("jackson update error, json: {}, key: {}, value: {}", json, key, value), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化Json(美化)
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
public static String format(String json) {
|
||||
try {
|
||||
JsonNode node = mapper.readTree(json);
|
||||
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
|
||||
} catch (IOException e) {
|
||||
throw new JacksonException(StrUtil.format("jackson format json error, json: {}", json), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否是json
|
||||
*
|
||||
* @return json
|
||||
*/
|
||||
public static boolean isJson(String json) {
|
||||
try {
|
||||
mapper.readTree(json);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.agileboot.common.utils.poi;
|
||||
|
||||
import cn.hutool.poi.excel.ExcelReader;
|
||||
import cn.hutool.poi.excel.ExcelUtil;
|
||||
import cn.hutool.poi.excel.ExcelWriter;
|
||||
import com.agileboot.common.annotation.ExcelColumn;
|
||||
import com.agileboot.common.annotation.ExcelSheet;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode.Internal;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义Excel 导入导出工具
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class CustomExcelUtil {
|
||||
|
||||
private CustomExcelUtil() {
|
||||
}
|
||||
|
||||
public static <T> void writeToResponse(List<T> list, Class<T> clazz, HttpServletResponse response) {
|
||||
try {
|
||||
writeToOutputStream(list, clazz, response.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new ApiException(e, Internal.EXCEL_PROCESS_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> readFromRequest(Class<T> clazz, MultipartFile file) {
|
||||
try {
|
||||
return readFromInputStream(clazz, file.getInputStream());
|
||||
} catch (IOException e) {
|
||||
// 注意如果是捕获到的错误 一定要放进ApiException当中
|
||||
throw new ApiException(e, Internal.EXCEL_PROCESS_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void writeToOutputStream(List<T> list, Class<T> clazz, OutputStream outputStream) {
|
||||
|
||||
// 通过工具类创建writer
|
||||
ExcelWriter writer = ExcelUtil.getWriter(true);
|
||||
|
||||
ExcelSheet sheetAnno = clazz.getAnnotation(ExcelSheet.class);
|
||||
|
||||
if (sheetAnno != null) {
|
||||
// 默认的sheetName是 sheet1
|
||||
writer.renameSheet(sheetAnno.name());
|
||||
}
|
||||
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
|
||||
//自定义标题别名
|
||||
for (Field field : fields) {
|
||||
ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
|
||||
if (annotation != null) {
|
||||
writer.addHeaderAlias(field.getName(), annotation.name());
|
||||
}
|
||||
}
|
||||
|
||||
// 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
|
||||
writer.setOnlyAlias(true);
|
||||
|
||||
// 合并单元格后的标题行,使用默认标题样式
|
||||
// writer.merge(4, "一班成绩单"); 一次性写出内容,使用默认样式,强制输出标题
|
||||
writer.write(list, true);
|
||||
writer.flush(outputStream, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <T> List<T> readFromInputStream(Class<T> clazz, InputStream inputStream) {
|
||||
ExcelReader reader = ExcelUtil.getReader(inputStream);
|
||||
// 去除掉excel中的html标签语言 避免xss攻击
|
||||
reader.setCellEditor(new TrimXssEditor());
|
||||
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
|
||||
//自定义标题别名
|
||||
for (Field field : fields) {
|
||||
ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
|
||||
if (annotation != null) {
|
||||
reader.addHeaderAlias(annotation.name(), field.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return reader.read(0, 1, clazz);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.agileboot.common.utils.poi;
|
||||
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import cn.hutool.poi.excel.cell.CellEditor;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
* 读取excel的时候,去除掉html相关的标签 避免xss注入
|
||||
*/
|
||||
public class TrimXssEditor implements CellEditor {
|
||||
|
||||
@Override
|
||||
public Object edit(Cell cell, Object value) {
|
||||
if (value instanceof String) {
|
||||
return HtmlUtil.cleanHtmlTag(value.toString());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.agileboot.common.utils.time;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import java.util.Date;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@Slf4j
|
||||
public class DatePickUtil {
|
||||
|
||||
private DatePickUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地获取日期的一天开始时间, date为null 则返回null DateUtil.beginOfDay(date) 如果传null 会NPE
|
||||
*
|
||||
* @param date 当前日期
|
||||
* @return 日期的一天开始时间
|
||||
*/
|
||||
public static Date getBeginOfTheDay(Date date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return DateUtil.beginOfDay(date);
|
||||
} catch (Exception e) {
|
||||
log.error("pick begin of the day failed, due to: ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地获取日期的一天结束时间, date为null 则返回null。 避免NPE
|
||||
* DateUtil.endOfDay(date) 如果传null 会NPE
|
||||
* @param date 23:59:59
|
||||
* @return 日期的一天结束时间
|
||||
*/
|
||||
public static Date getEndOfTheDay(Date date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return DateUtil.endOfDay(date);
|
||||
} catch (Exception e) {
|
||||
log.error("pick end of the day failed, due to: ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Binary file not shown.
+29
@@ -0,0 +1,29 @@
|
||||
package com.agileboot.common.core.exception;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ApiExceptionTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testVarargsWithArrayArgs() {
|
||||
String errorMsg = "these parameters are null: %s, %s, %s.";
|
||||
Object[] array = new Object[] { "param1" , "param2" , "param3"};
|
||||
|
||||
String formatWithArray = String.format(errorMsg, array);
|
||||
String formatWithVarargs = String.format(errorMsg, "param1", "param2", "param3");
|
||||
|
||||
Assert.assertEquals(formatWithVarargs, formatWithArray);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarargsWithNullArgs() {
|
||||
String errorMsg = "these parameters are null: %s, %s, %s.";
|
||||
|
||||
String format = String.format(errorMsg, "param1", null, null);
|
||||
|
||||
Assert.assertEquals("these parameters are null: param1, null, null.", format);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.agileboot.common.enums;
|
||||
|
||||
|
||||
import com.agileboot.common.enums.common.YesOrNoEnum;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class BasicEnumUtilTest {
|
||||
|
||||
@Test
|
||||
public void testFromValue() {
|
||||
|
||||
YesOrNoEnum yes = BasicEnumUtil.fromValue(YesOrNoEnum.class, 1);
|
||||
YesOrNoEnum no = BasicEnumUtil.fromValue(YesOrNoEnum.class, 0);
|
||||
|
||||
Assert.assertEquals(yes.description(), "是");
|
||||
Assert.assertEquals(no.description(), "否");
|
||||
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.agileboot.common.exception.error;
|
||||
|
||||
import com.agileboot.common.exception.error.ErrorCode.Client;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ErrorCodeInterfaceTest {
|
||||
|
||||
@Test
|
||||
void testI18nKey() {
|
||||
String i18nKey = Client.COMMON_FORBIDDEN_TO_CALL.i18nKey();
|
||||
Assertions.assertEquals("20001_COMMON_FORBIDDEN_TO_CALL", i18nKey);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.agileboot.common.query;
|
||||
|
||||
|
||||
import com.agileboot.common.core.page.AbstractQuery;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import java.util.Date;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AbstractQueryTest {
|
||||
|
||||
private AbstractQuery query;
|
||||
|
||||
@BeforeEach
|
||||
public void getNewQuery() {
|
||||
query = new AbstractQuery<Object>() {
|
||||
@Override
|
||||
public QueryWrapper addQueryCondition() {
|
||||
return new QueryWrapper();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void addTimeConditionWithNull() {
|
||||
query.setTimeRangeColumn("loginTime");
|
||||
QueryWrapper<Object> queryWrapper = query.toQueryWrapper();
|
||||
|
||||
String targetSql = queryWrapper.getTargetSql();
|
||||
Assertions.assertEquals("", targetSql);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void addTimeConditionWithBothValue() {
|
||||
query.setBeginTime(new Date());
|
||||
query.setEndTime(new Date());
|
||||
query.setTimeRangeColumn("loginTime");
|
||||
|
||||
QueryWrapper<Object> queryWrapper = query.toQueryWrapper();
|
||||
|
||||
String targetSql = queryWrapper.getTargetSql();
|
||||
Assertions.assertEquals("(login_time >= ? AND login_time <= ?)", targetSql);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void addTimeConditionWithBeginValueOnly() {
|
||||
query.setBeginTime(new Date());
|
||||
query.setTimeRangeColumn("loginTime");
|
||||
|
||||
QueryWrapper<Object> queryWrapper = query.toQueryWrapper();
|
||||
|
||||
String targetSql = queryWrapper.getTargetSql();
|
||||
Assertions.assertEquals("(login_time >= ?)", targetSql);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void testConvertSortDirection() {
|
||||
query.setOrderDirection("ascending");
|
||||
Assertions.assertTrue(query.convertSortDirection());
|
||||
|
||||
query.setOrderDirection("descending");
|
||||
Assertions.assertFalse(query.convertSortDirection());
|
||||
|
||||
query.setOrderDirection("");
|
||||
Assertions.assertNull(query.convertSortDirection());
|
||||
|
||||
query.setOrderDirection(null);
|
||||
Assertions.assertNull(query.convertSortDirection());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.agileboot.common.utils.file;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.common.config.AgileBootConfig;
|
||||
import com.agileboot.common.constant.Constants.UploadSubDir;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||
import com.agileboot.common.exception.error.ErrorCode.Internal;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
class FileUploadUtilsTest {
|
||||
|
||||
@Test
|
||||
void testIsAllowedExtension() {
|
||||
String[] imageTypes = new String[]{"img", "gif"};
|
||||
|
||||
boolean isAllow = FileUploadUtils.isExtensionAllowed("img", imageTypes);
|
||||
boolean isNotAllow = FileUploadUtils.isExtensionAllowed("png", imageTypes);
|
||||
|
||||
Assertions.assertTrue(isAllow);
|
||||
Assertions.assertFalse(isNotAllow);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsAllowedExtensionWhenNull() {
|
||||
String[] imageTypes = null;
|
||||
|
||||
boolean isAllow = FileUploadUtils.isExtensionAllowed("img", imageTypes);
|
||||
|
||||
Assertions.assertTrue(isAllow);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetRelativeFileUrl() {
|
||||
String relativeFilePath = FileUploadUtils.getRelativeFileUrl(UploadSubDir.UPLOAD_PATH, "test.jpg");
|
||||
|
||||
Assertions.assertEquals("/profile/upload/test.jpg", relativeFilePath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveFileToLocal() {
|
||||
MultipartFile fileMock = Mockito.mock(MultipartFile.class);
|
||||
|
||||
ApiException exceptionWithNullSubDir = Assertions.assertThrows(ApiException.class,
|
||||
() -> FileUploadUtils.saveFileToLocal(fileMock, "", ""));
|
||||
ApiException exceptionWitEmptyFileName = Assertions.assertThrows(ApiException.class,
|
||||
() -> FileUploadUtils.saveFileToLocal(fileMock, "", ""));
|
||||
|
||||
Assertions.assertEquals(Internal.INVALID_PARAMETER, exceptionWithNullSubDir.getErrorCode());
|
||||
Assertions.assertEquals(Internal.INVALID_PARAMETER, exceptionWitEmptyFileName.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsAllowedUploadWhenFileNameTooLong() {
|
||||
MultipartFile fileMock = Mockito.mock(MultipartFile.class);
|
||||
String longFileName = "this is a very very long sentence, this is a very very long sentence, "
|
||||
+ "this is a very very long sentence, this is a very very long sentence, ";
|
||||
|
||||
Mockito.when(fileMock.getOriginalFilename()).thenReturn(longFileName);
|
||||
|
||||
ApiException exception = Assertions.assertThrows(ApiException.class,
|
||||
() -> FileUploadUtils.isAllowedUpload(fileMock, null));
|
||||
Assertions.assertEquals(Business.UPLOAD_FILE_NAME_EXCEED_MAX_LENGTH, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsAllowedUploadWhenFileTooBig() {
|
||||
MultipartFile fileMock = Mockito.mock(MultipartFile.class);
|
||||
Mockito.when(fileMock.getOriginalFilename()).thenReturn("test.jpg");
|
||||
Mockito.when(fileMock.getSize()).thenReturn(FileUploadUtils.MAX_FILE_SIZE + 1);
|
||||
|
||||
ApiException exception = Assertions.assertThrows(ApiException.class,
|
||||
() -> FileUploadUtils.isAllowedUpload(fileMock, null));
|
||||
Assertions.assertEquals(Business.UPLOAD_FILE_SIZE_EXCEED_MAX_SIZE, exception.getErrorCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testisAllowDownload() {
|
||||
Assertions.assertFalse(FileUploadUtils.isAllowDownload("../test.jpg"));
|
||||
Assertions.assertFalse(FileUploadUtils.isAllowDownload("../test.exe"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetFileExtension() {
|
||||
MultipartFile fileMock = Mockito.mock(MultipartFile.class);
|
||||
Mockito.when(fileMock.getOriginalFilename()).thenReturn("test.jpg");
|
||||
|
||||
String fileExtension = FileUploadUtils.getFileExtension(fileMock);
|
||||
|
||||
Assertions.assertEquals("jpg", fileExtension);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateFilename() {
|
||||
String fileName = "test.jpg";
|
||||
MultipartFile fileMock = Mockito.mock(MultipartFile.class);
|
||||
Mockito.when(fileMock.getOriginalFilename()).thenReturn(fileName);
|
||||
|
||||
String randomFileName = FileUploadUtils.generateFilename(fileMock);
|
||||
|
||||
String[] nameParts = randomFileName.split("_");
|
||||
Assertions.assertEquals("test", nameParts[1]);
|
||||
Assertions.assertTrue(StrUtil.endWith(nameParts[2], ".jpg"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getFileAbsolutePath() {
|
||||
AgileBootConfig agileBootConfig = new AgileBootConfig();
|
||||
agileBootConfig.setFileBaseDir("D:\\agileboot");
|
||||
|
||||
String fileAbsolutePath = FileUploadUtils.getFileAbsolutePath(UploadSubDir.AVATAR_PATH, "test.jpg");
|
||||
|
||||
Assertions.assertEquals("D:\\agileboot\\profile\\avatar\\test.jpg", fileAbsolutePath);
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class IpRegionUtilTest {
|
||||
|
||||
@Test
|
||||
void testGetIpRegion() {
|
||||
IpRegion ipRegion = IpRegionUtil.getIpRegion("110.81.189.80");
|
||||
|
||||
Assertions.assertEquals("中国", ipRegion.getCountry());
|
||||
Assertions.assertEquals("福建省", ipRegion.getProvince());
|
||||
Assertions.assertEquals("泉州市", ipRegion.getCity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWhenLocalHost() {
|
||||
IpRegion ipRegion = IpRegionUtil.getIpRegion("127.0.0.1");
|
||||
Assertions.assertEquals("内网IP", ipRegion.briefLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithIpv6() {
|
||||
IpRegion ipRegion = IpRegionUtil.getIpRegion("2001:0DB8:0000:0023:0008:0800:200C:417A");
|
||||
|
||||
Assertions.assertNotNull(ipRegion);
|
||||
Assertions.assertNull(ipRegion.getCountry());
|
||||
Assertions.assertEquals("未知 未知", ipRegion.briefLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithEmpty() {
|
||||
IpRegion ipRegion = IpRegionUtil.getIpRegion("");
|
||||
|
||||
Assertions.assertNotNull(ipRegion);
|
||||
Assertions.assertNull(ipRegion.getCountry());
|
||||
Assertions.assertEquals("未知 未知", ipRegion.briefLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithNull() {
|
||||
IpRegion ipRegion = IpRegionUtil.getIpRegion(null);
|
||||
|
||||
Assertions.assertNotNull(ipRegion);
|
||||
Assertions.assertNull(ipRegion.getCountry());
|
||||
Assertions.assertEquals("未知 未知", ipRegion.briefLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithWrongIpString() {
|
||||
IpRegion ipRegion = IpRegionUtil.getIpRegion("xsdfwefsfsd");
|
||||
|
||||
Assertions.assertNotNull(ipRegion);
|
||||
Assertions.assertNull(ipRegion.getCountry());
|
||||
Assertions.assertEquals("未知 未知", ipRegion.briefLocation());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBriefLocationByIp() {
|
||||
String briefLocationByIp = IpRegionUtil.getBriefLocationByIp("110.81.189.80");
|
||||
|
||||
Assertions.assertEquals("福建省 泉州市", briefLocationByIp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class IpUtilTest {
|
||||
|
||||
@Test
|
||||
void isInnerIp() {
|
||||
boolean innerIp1 = IpUtil.isLocalHost("127.0.0.1");
|
||||
boolean innerIp2 = IpUtil.isLocalHost("0:0:0:0:0:0:0:1");
|
||||
boolean innerIp3 = IpUtil.isLocalHost("localhost");
|
||||
boolean innerIp4 = IpUtil.isLocalHost("192.168.1.1");
|
||||
boolean innerIp5 = IpUtil.isLocalHost("10.32.1.1");
|
||||
boolean innerIp6 = IpUtil.isLocalHost("172.16.1.1");
|
||||
boolean notInnerIP = IpUtil.isLocalHost("110.81.189.80");
|
||||
|
||||
Assertions.assertTrue(innerIp1);
|
||||
Assertions.assertTrue(innerIp2);
|
||||
Assertions.assertTrue(innerIp3);
|
||||
Assertions.assertTrue(innerIp4);
|
||||
Assertions.assertTrue(innerIp5);
|
||||
Assertions.assertTrue(innerIp6);
|
||||
Assertions.assertFalse(notInnerIP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLocalHost() {
|
||||
boolean localHost1 = IpUtil.isLocalHost("127.0.0.1");
|
||||
boolean localHost2 = IpUtil.isLocalHost("0:0:0:0:0:0:0:1");
|
||||
boolean localHost4 = IpUtil.isLocalHost("localhost");
|
||||
boolean notLocalHost = IpUtil.isLocalHost("110.81.189.80");
|
||||
|
||||
Assertions.assertTrue(localHost1);
|
||||
Assertions.assertTrue(localHost2);
|
||||
Assertions.assertTrue(localHost4);
|
||||
Assertions.assertFalse(notLocalHost);
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class OfflineIpRegionUtilTest {
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWhenIpv4() {
|
||||
IpRegion ipRegion = OfflineIpRegionUtil.getIpRegion("110.81.189.80");
|
||||
|
||||
Assertions.assertEquals("中国", ipRegion.getCountry());
|
||||
Assertions.assertEquals("福建省", ipRegion.getProvince());
|
||||
Assertions.assertEquals("泉州市", ipRegion.getCity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithIpv6() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OfflineIpRegionUtil.getIpRegion("2001:0DB8:0000:0023:0008:0800:200C:417A")
|
||||
);
|
||||
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithEmpty() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OfflineIpRegionUtil.getIpRegion("")
|
||||
);
|
||||
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithNull() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OfflineIpRegionUtil.getIpRegion(null)
|
||||
);
|
||||
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetIpRegionWithWrongIpString() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OfflineIpRegionUtil.getIpRegion("asfdsfdsff")
|
||||
);
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.agileboot.common.utils.ip;
|
||||
|
||||
import com.agileboot.common.config.AgileBootConfig;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class OnlineIpRegionUtilTest {
|
||||
|
||||
@BeforeEach
|
||||
public void enableOnlineAddressQuery() {
|
||||
AgileBootConfig agileBootConfig = new AgileBootConfig();
|
||||
agileBootConfig.setAddressEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getIpRegionWithIpv6() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OnlineIpRegionUtil.getIpRegion("ABCD:EF01:2345:6789:ABCD:EF01:2345:6789")
|
||||
);
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getIpRegionWithIpv4() {
|
||||
IpRegion ipRegion = OnlineIpRegionUtil.getIpRegion("120.42.247.130");
|
||||
|
||||
Assertions.assertEquals("福建省", ipRegion.getProvince());
|
||||
Assertions.assertEquals("泉州市", ipRegion.getCity());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getIpRegionWithEmpty() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OnlineIpRegionUtil.getIpRegion("")
|
||||
);
|
||||
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getIpRegionWithNull() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OnlineIpRegionUtil.getIpRegion(null)
|
||||
);
|
||||
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getIpRegionWithWrongIpString() {
|
||||
IpRegion region = Assertions.assertDoesNotThrow(() ->
|
||||
OnlineIpRegionUtil.getIpRegion("seffsdfsdf")
|
||||
);
|
||||
|
||||
Assertions.assertNull(region);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.agileboot.common.utils.jackson;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author duanxinyuan 2019/1/21 18:17
|
||||
*/
|
||||
public class JacksonUtilTest {
|
||||
|
||||
@Test
|
||||
public void testObjectToJson() {
|
||||
Person person = Person.newPerson();
|
||||
|
||||
String jacksonStr = JacksonUtil.to(person);
|
||||
Assert.assertEquals(DateUtil.formatDateTime(person.getDate()), JacksonUtil.getAsString(jacksonStr, "date"));
|
||||
Assert.assertEquals(DateUtil.formatLocalDateTime(person.getLocalDateTime()),
|
||||
JacksonUtil.getAsString(jacksonStr, "localDateTime"));
|
||||
Assert.assertEquals(person.getName(), JacksonUtil.getAsString(jacksonStr, "name"));
|
||||
Assert.assertEquals(person.getAge(), JacksonUtil.getAsInt(jacksonStr, "age"));
|
||||
Assert.assertEquals(person.isMan(), JacksonUtil.getAsBoolean(jacksonStr, "man"));
|
||||
Assert.assertEquals(person.getMoney(), JacksonUtil.getAsBigDecimal(jacksonStr, "money"));
|
||||
Assert.assertEquals(person.getTrait(), JacksonUtil.getAsList(jacksonStr, "trait", String.class));
|
||||
|
||||
Assert.assertNotNull(JacksonUtil.getAsString(jacksonStr, "name"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试兼容情况
|
||||
*/
|
||||
@Test
|
||||
public void testAllPrimitiveTypeToJson() {
|
||||
String json = "{\n"
|
||||
+ "\"code\": \"200\",\n"
|
||||
+ "\"id\": \"2001215464647687987\",\n"
|
||||
+ "\"message\": \"success\",\n"
|
||||
+ "\"amount\": \"1.12345\",\n"
|
||||
+ "\"amount1\": \"0.12345\",\n"
|
||||
+ "\"isSuccess\": \"true\",\n"
|
||||
+ "\"isSuccess1\": \"1\",\n"
|
||||
+ "\"key\": \"8209167202090377654857374178856064487200234961995543450245362822537162918731039965956758726661669012305745755921310000297396309887550627402157318910581311\"\n"
|
||||
+ "}";
|
||||
Assert.assertEquals(200, JacksonUtil.getAsInt(json, "code"));
|
||||
Assert.assertEquals(2001215464647687987L,JacksonUtil.getAsLong(json, "id"));
|
||||
Assert.assertEquals("success", JacksonUtil.getAsString(json, "message"));
|
||||
Assert.assertEquals(new BigDecimal("1.12345"), JacksonUtil.getAsBigDecimal(json, "amount"));
|
||||
Assert.assertEquals(new BigDecimal("0.12345"), JacksonUtil.getAsBigDecimal(json, "amount1"));
|
||||
Assert.assertEquals(1.12345d, JacksonUtil.getAsDouble(json, "amount"), 0.00001);
|
||||
Assert.assertEquals(0.12345d, JacksonUtil.getAsDouble(json, "amount1"), 0.00001);
|
||||
Assert.assertTrue(JacksonUtil.getAsBoolean(json, "isSuccess"));
|
||||
Assert.assertTrue(JacksonUtil.getAsBoolean(json, "isSuccess1"));
|
||||
Assert.assertEquals(new BigInteger(
|
||||
"8209167202090377654857374178856064487200234961995543450245362822537162918731039965956758726661669012305745755921310000297396309887550627402157318910581311"),
|
||||
JacksonUtil.getAsBigInteger(json, "key"));
|
||||
Assert.assertEquals("1", JacksonUtil.getAsString(json, "isSuccess1"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.agileboot.common.utils.jackson;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author duanxinyuan
|
||||
* 2018/6/29 14:17
|
||||
*/
|
||||
@Data
|
||||
public class Person {
|
||||
public String name;
|
||||
public Date date;
|
||||
public LocalDateTime localDateTime;
|
||||
public int age;
|
||||
public BigDecimal money;
|
||||
public boolean man;
|
||||
public ArrayList<String> trait;
|
||||
public HashMap<String, String> cards;
|
||||
|
||||
public static Person newPerson() {
|
||||
Person person = new Person();
|
||||
person.name = "张三";
|
||||
person.date = new Date();
|
||||
person.localDateTime = LocalDateTime.now();
|
||||
person.age = 100;
|
||||
person.money = BigDecimal.valueOf(500.21);
|
||||
person.man = true;
|
||||
person.trait = new ArrayList<>();
|
||||
person.trait.add("淡然");
|
||||
person.trait.add("温和");
|
||||
person.cards = new HashMap<>();
|
||||
person.cards.put("身份证", "4a6d456as");
|
||||
person.cards.put("建行卡", "649874545");
|
||||
return person;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.agileboot.common.utils.poi;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CustomExcelUtilTest {
|
||||
|
||||
@Test
|
||||
void testImportAndExport() {
|
||||
PostDTO post1 = new PostDTO(1L, "admin1", "管理员1", "1", "无备注", "1", "正常");
|
||||
PostDTO post2 = new PostDTO(2L, "admin2", "管理员2<script>alert(1)</script>", "2", "无备注", "1", "正常");
|
||||
|
||||
List<PostDTO> postDTOList = new ArrayList<>();
|
||||
postDTOList.add(post1);
|
||||
postDTOList.add(post2);
|
||||
|
||||
File file = FileUtil.createTempFile();
|
||||
|
||||
CustomExcelUtil.writeToOutputStream(postDTOList, PostDTO.class, FileUtil.getOutputStream(file));
|
||||
|
||||
List<PostDTO> postListFromExcel = CustomExcelUtil.readFromInputStream(PostDTO.class, FileUtil.getInputStream(file));
|
||||
|
||||
PostDTO post1fromExcel = postListFromExcel.get(0);
|
||||
PostDTO post2fromExcel = postListFromExcel.get(1);
|
||||
|
||||
Assertions.assertEquals(post1.getPostId(), post1fromExcel.getPostId());
|
||||
Assertions.assertEquals(post1.getPostCode(), post1fromExcel.getPostCode());
|
||||
Assertions.assertEquals(post2.getPostId(), post2fromExcel.getPostId());
|
||||
Assertions.assertEquals(post2.getPostCode(), post2fromExcel.getPostCode());
|
||||
// 检查脚本注入的字符串是否被去除
|
||||
Assertions.assertNotEquals(post2.getPostName(), post2fromExcel.getPostName());
|
||||
Assertions.assertEquals(HtmlUtil.cleanHtmlTag(post2.getPostName()), post2fromExcel.getPostName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.agileboot.common.utils.poi;
|
||||
|
||||
import com.agileboot.common.annotation.ExcelColumn;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
public class PostDTO {
|
||||
|
||||
@ExcelColumn(name = "岗位ID")
|
||||
private Long postId;
|
||||
|
||||
|
||||
@ExcelColumn(name = "岗位编码")
|
||||
private String postCode;
|
||||
|
||||
@ExcelColumn(name = "岗位名称")
|
||||
private String postName;
|
||||
|
||||
|
||||
@ExcelColumn(name = "岗位排序")
|
||||
private String postSort;
|
||||
|
||||
@ExcelColumn(name = "备注")
|
||||
private String remark;
|
||||
|
||||
private String status;
|
||||
|
||||
@ExcelColumn(name = "状态")
|
||||
private String statusStr;
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.agileboot.common.utils.time;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class DatePickUtilTest {
|
||||
|
||||
@Test
|
||||
void testGetBeginOfTheDay() {
|
||||
Date beginOfTheDay = DatePickUtil.getBeginOfTheDay(new Date());
|
||||
|
||||
Calendar instance = Calendar.getInstance();
|
||||
instance.setTime(beginOfTheDay);
|
||||
|
||||
Assertions.assertEquals(0, instance.get(Calendar.HOUR));
|
||||
Assertions.assertEquals(0, instance.get(Calendar.MINUTE));
|
||||
Assertions.assertEquals(0, instance.get(Calendar.SECOND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBeginOfTheDayWhenNull() {
|
||||
Assertions.assertDoesNotThrow(() -> DatePickUtil.getBeginOfTheDay(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetEndOfTheDay() {
|
||||
Date endOfTheDay = DatePickUtil.getEndOfTheDay(new Date());
|
||||
|
||||
Calendar instance = Calendar.getInstance();
|
||||
instance.setTime(endOfTheDay);
|
||||
|
||||
Assertions.assertEquals(23, instance.get(Calendar.HOUR_OF_DAY));
|
||||
Assertions.assertEquals(59, instance.get(Calendar.MINUTE));
|
||||
Assertions.assertEquals(59, instance.get(Calendar.SECOND));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetEndOfTheDayWhenNull() {
|
||||
Assertions.assertDoesNotThrow(() -> DatePickUtil.getEndOfTheDay(null)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user