feat: initial commit

This commit is contained in:
gin
2026-05-07 18:39:00 +08:00
commit cdee21ee8e
653 changed files with 63946 additions and 0 deletions
@@ -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 "";
}
@@ -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 "";
}
@@ -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";
}
}
@@ -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);
// }
}
@@ -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);
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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() {
}
}
@@ -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 "";
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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(), "");
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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)
);
}
}