refactor: 目录结构优化
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* MP数据权限注解
|
||||
*
|
||||
* @author zc
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
public @interface DataPermission {
|
||||
|
||||
/**
|
||||
* 数据权限 {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor}
|
||||
*/
|
||||
String deptAlias() default "";
|
||||
|
||||
String deptIdColumnName() default "dept_id";
|
||||
|
||||
String userAlias() default "";
|
||||
|
||||
String userIdColumnName() default "create_by";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
import com.youlai.system.enums.LogModuleEnum;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 日志注解
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/6/25
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface LogAnnotation {
|
||||
|
||||
String value() default "";
|
||||
|
||||
LogModuleEnum module() ;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.youlai.boot.common.annotation;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 防止重复提交注解
|
||||
* <p>
|
||||
* 该注解用于方法上,防止在指定时间内的重复提交。
|
||||
* 默认时间为5秒。
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface NoRepeat {
|
||||
|
||||
/**
|
||||
* 锁过期时间(秒)
|
||||
* <p>
|
||||
* 默认5秒内不允许重复提交
|
||||
*/
|
||||
int expire() default 5;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.youlai.boot.common.base;
|
||||
|
||||
import com.alibaba.excel.event.AnalysisEventListener;
|
||||
|
||||
/**
|
||||
* 自定义解析结果监听器
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/03/01
|
||||
*/
|
||||
public abstract class BaseAnalysisEventListener<T> extends AnalysisEventListener<T> {
|
||||
|
||||
private String msg;
|
||||
public abstract String getMsg();
|
||||
}
|
||||
48
src/main/java/com/youlai/boot/common/base/BaseEntity.java
Normal file
48
src/main/java/com/youlai/boot/common/base/BaseEntity.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.youlai.boot.common.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体类
|
||||
*
|
||||
* <p>实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等</p>
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/6/23
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
22
src/main/java/com/youlai/boot/common/base/BasePageQuery.java
Normal file
22
src/main/java/com/youlai/boot/common/base/BasePageQuery.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.youlai.boot.common.base;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 基础分页请求对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2021/2/28
|
||||
*/
|
||||
@Data
|
||||
@Schema
|
||||
public class BasePageQuery {
|
||||
|
||||
@Schema(description = "页码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private int pageNum = 1;
|
||||
|
||||
@Schema(description = "每页记录数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private int pageSize = 10;
|
||||
}
|
||||
21
src/main/java/com/youlai/boot/common/base/BaseVO.java
Normal file
21
src/main/java/com/youlai/boot/common/base/BaseVO.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.youlai.boot.common.base;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 视图对象基类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/22
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
public class BaseVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
88
src/main/java/com/youlai/boot/common/base/IBaseEnum.java
Normal file
88
src/main/java/com/youlai/boot/common/base/IBaseEnum.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.youlai.boot.common.base;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 枚举通用接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/3/27 12:06
|
||||
*/
|
||||
public interface IBaseEnum<T> {
|
||||
|
||||
T getValue();
|
||||
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* 根据值获取枚举
|
||||
*
|
||||
* @param value
|
||||
* @param clazz
|
||||
* @param <E> 枚举
|
||||
* @return
|
||||
*/
|
||||
static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) {
|
||||
Objects.requireNonNull(value);
|
||||
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
|
||||
E matchEnum = allEnums.stream()
|
||||
.filter(e -> ObjectUtil.equal(e.getValue(), value))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return matchEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文本标签获取值
|
||||
*
|
||||
* @param value
|
||||
* @param clazz
|
||||
* @param <E>
|
||||
* @return
|
||||
*/
|
||||
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
|
||||
Objects.requireNonNull(value);
|
||||
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
|
||||
E matchEnum = allEnums.stream()
|
||||
.filter(e -> ObjectUtil.equal(e.getValue(), value))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
String label = null;
|
||||
if (matchEnum != null) {
|
||||
label = matchEnum.getLabel();
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据文本标签获取值
|
||||
*
|
||||
* @param label
|
||||
* @param clazz
|
||||
* @param <E>
|
||||
* @return
|
||||
*/
|
||||
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
|
||||
Objects.requireNonNull(label);
|
||||
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
|
||||
String finalLabel = label;
|
||||
E matchEnum = allEnums.stream()
|
||||
.filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
Object value = null;
|
||||
if (matchEnum != null) {
|
||||
value = matchEnum.getValue();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
|
||||
/**
|
||||
* JWT Claims声明常量
|
||||
* <p>
|
||||
* JWT Claims 属于 Payload 的一部分,包含了一些实体(通常指的用户)的状态和额外的元数据。
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/11/24
|
||||
*/
|
||||
public interface JwtClaimConstants {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
String USER_ID = "userId";
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
String DEPT_ID = "deptId";
|
||||
|
||||
/**
|
||||
* 数据权限
|
||||
*/
|
||||
String DATA_SCOPE = "dataScope";
|
||||
|
||||
/**
|
||||
* 权限(角色Code)集合
|
||||
*/
|
||||
String AUTHORITIES = "authorities";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
|
||||
/**
|
||||
* Redis Key常量
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024-7-29 11:46:08
|
||||
*/
|
||||
public interface RedisConstants {
|
||||
|
||||
/**
|
||||
* 系统配置Redis-key
|
||||
*/
|
||||
String SYSTEM_CONFIG_KEY = "system:config";
|
||||
|
||||
/**
|
||||
* IP限流Redis-key
|
||||
*/
|
||||
String IP_RATE_LIMITER_KEY = "ip:rate:limiter:";
|
||||
|
||||
/**
|
||||
* 防重复提交Redis-key
|
||||
*/
|
||||
String RESUBMIT_LOCK_PREFIX = "resubmit:lock:";
|
||||
|
||||
/**
|
||||
* 单个IP请求的最大每秒查询数(QPS)阈值Key
|
||||
*/
|
||||
String IP_QPS_THRESHOLD_LIMIT_KEY = "IP_QPS_THRESHOLD_LIMIT";
|
||||
|
||||
/**
|
||||
* 手机验证码缓存前缀
|
||||
*/
|
||||
|
||||
String MOBILE_VERIFICATION_CODE_PREFIX = "VERIFICATION_CODE:MOBILE:";
|
||||
|
||||
|
||||
/**
|
||||
* 邮箱验证码缓存前缀
|
||||
*/
|
||||
String EMAIL_VERIFICATION_CODE_PREFIX = "VERIFICATION_CODE:EMAIL:";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
|
||||
/**
|
||||
* 缓存常量
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/11/24
|
||||
*/
|
||||
public interface SecurityConstants {
|
||||
|
||||
/**
|
||||
* 验证码缓存前缀
|
||||
*/
|
||||
String CAPTCHA_CODE_PREFIX = "captcha_code:";
|
||||
|
||||
/**
|
||||
* 角色和权限缓存前缀
|
||||
*/
|
||||
String ROLE_PERMS_PREFIX = "role_perms:";
|
||||
|
||||
/**
|
||||
* 黑名单Token缓存前缀
|
||||
*/
|
||||
String BLACKLIST_TOKEN_PREFIX = "token:blacklist:";
|
||||
|
||||
|
||||
/**
|
||||
* 登录路径
|
||||
*/
|
||||
String LOGIN_PATH = "/api/v1/auth/login";
|
||||
|
||||
|
||||
/**
|
||||
* JWT Token 前缀
|
||||
*/
|
||||
String JWT_TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
|
||||
/**
|
||||
* 符号和特殊符号常用类
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024-7-29 11:46:08
|
||||
*/
|
||||
public interface SymbolConstant {
|
||||
|
||||
/**
|
||||
* 符号:点
|
||||
*/
|
||||
String SPOT = ".";
|
||||
|
||||
/**
|
||||
* 符号:双斜杠
|
||||
*/
|
||||
String DOUBLE_BACKSLASH = "\\";
|
||||
|
||||
/**
|
||||
* 符号:冒号
|
||||
*/
|
||||
String COLON = ":";
|
||||
|
||||
/**
|
||||
* 符号:逗号
|
||||
*/
|
||||
String COMMA = ",";
|
||||
|
||||
/**
|
||||
* 符号:左花括号 {
|
||||
*/
|
||||
String LEFT_CURLY_BRACKET = "{";
|
||||
|
||||
/**
|
||||
* 符号:右花括号 }
|
||||
*/
|
||||
String RIGHT_CURLY_BRACKET = "}";
|
||||
|
||||
/**
|
||||
* 符号:井号 #
|
||||
*/
|
||||
String WELL_NUMBER = "#";
|
||||
|
||||
/**
|
||||
* 符号:单斜杠
|
||||
*/
|
||||
String SINGLE_SLASH = "/";
|
||||
|
||||
/**
|
||||
* 符号:双斜杠
|
||||
*/
|
||||
String DOUBLE_SLASH = "//";
|
||||
|
||||
/**
|
||||
* 符号:感叹号
|
||||
*/
|
||||
String EXCLAMATORY_MARK = "!";
|
||||
|
||||
/**
|
||||
* 符号:下划线
|
||||
*/
|
||||
String UNDERLINE = "_";
|
||||
|
||||
/**
|
||||
* 符号:单引号
|
||||
*/
|
||||
String SINGLE_QUOTATION_MARK = "'";
|
||||
|
||||
/**
|
||||
* 符号:星号
|
||||
*/
|
||||
String ASTERISK = "*";
|
||||
|
||||
/**
|
||||
* 符号:百分号
|
||||
*/
|
||||
String PERCENT_SIGN = "%";
|
||||
|
||||
/**
|
||||
* 符号:美元 $
|
||||
*/
|
||||
String DOLLAR = "$";
|
||||
|
||||
/**
|
||||
* 符号:和 &
|
||||
*/
|
||||
String AND = "&";
|
||||
|
||||
/**
|
||||
* 符号:../
|
||||
*/
|
||||
String SPOT_SINGLE_SLASH = "../";
|
||||
|
||||
/**
|
||||
* 符号:..\\
|
||||
*/
|
||||
String SPOT_DOUBLE_BACKSLASH = "..\\";
|
||||
|
||||
/**
|
||||
* 系统变量前缀 #{
|
||||
*/
|
||||
String SYS_VAR_PREFIX = "#{";
|
||||
|
||||
/**
|
||||
* 符号 {{
|
||||
*/
|
||||
String DOUBLE_LEFT_CURLY_BRACKET = "{{";
|
||||
|
||||
/**
|
||||
* 符号:[
|
||||
*/
|
||||
String SQUARE_BRACKETS_LEFT = "[";
|
||||
|
||||
/**
|
||||
* 符号:]
|
||||
*/
|
||||
String SQUARE_BRACKETS_RIGHT = "]";
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.youlai.boot.common.constant;
|
||||
|
||||
/**
|
||||
* 系统常量
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface SystemConstants {
|
||||
|
||||
/**
|
||||
* 根节点ID
|
||||
*/
|
||||
Long ROOT_NODE_ID = 0L;
|
||||
|
||||
/**
|
||||
* 系统默认密码
|
||||
*/
|
||||
String DEFAULT_PASSWORD = "123456";
|
||||
|
||||
/**
|
||||
* 超级管理员角色编码
|
||||
*/
|
||||
String ROOT_ROLE_CODE = "ROOT";
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
/**
|
||||
* EasyCaptcha 验证码类型枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.5.1
|
||||
*/
|
||||
public enum CaptchaTypeEnum {
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*/
|
||||
CIRCLE,
|
||||
/**
|
||||
* GIF验证码
|
||||
*/
|
||||
GIF,
|
||||
/**
|
||||
* 干扰线验证码
|
||||
*/
|
||||
LINE,
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*/
|
||||
SHEAR
|
||||
}
|
||||
19
src/main/java/com/youlai/boot/common/enums/ContactType.java
Normal file
19
src/main/java/com/youlai/boot/common/enums/ContactType.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
/**
|
||||
* 联系方式类型
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
public enum ContactType {
|
||||
/**
|
||||
* 手机
|
||||
*/
|
||||
MOBILE,
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
EMAIL
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据权限枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Getter
|
||||
public enum DataScopeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
/**
|
||||
* value 越小,数据权限范围越大
|
||||
*/
|
||||
ALL(0, "所有数据"),
|
||||
DEPT_AND_SUB(1, "部门及子部门数据"),
|
||||
DEPT(2, "本部门数据"),
|
||||
SELF(3, "本人数据");
|
||||
|
||||
private final Integer value;
|
||||
|
||||
private final String label;
|
||||
|
||||
DataScopeEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
84
src/main/java/com/youlai/boot/common/enums/FormTypeEnum.java
Normal file
84
src/main/java/com/youlai/boot/common/enums/FormTypeEnum.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 表单类型枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum FormTypeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 输入框
|
||||
*/
|
||||
INPUT(1, "输入框"),
|
||||
|
||||
/**
|
||||
* 下拉框
|
||||
*/
|
||||
SELECT(2, "下拉框"),
|
||||
|
||||
/**
|
||||
* 单选框
|
||||
*/
|
||||
RADIO(3, "单选框"),
|
||||
|
||||
/**
|
||||
* 复选框
|
||||
*/
|
||||
CHECK_BOX(4, "复选框"),
|
||||
|
||||
/**
|
||||
* 数字输入框
|
||||
*/
|
||||
INPUT_NUMBER(5, "数字输入框"),
|
||||
|
||||
/**
|
||||
* 开关
|
||||
*/
|
||||
SWITCH(6, "开关"),
|
||||
|
||||
/**
|
||||
* 文本域
|
||||
*/
|
||||
TEXT_AREA(7, "文本域"),
|
||||
|
||||
/**
|
||||
* 日期时间框
|
||||
*/
|
||||
DATE(8, "日期框"),
|
||||
|
||||
/**
|
||||
* 日期框
|
||||
*/
|
||||
DATE_TIME(9, "日期时间框");
|
||||
|
||||
|
||||
// Mybatis-Plus 提供注解表示插入数据库时插入该值
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
private final Integer value;
|
||||
|
||||
// @JsonValue // 表示对枚举序列化时返回此字段
|
||||
private final String label;
|
||||
|
||||
|
||||
@JsonCreator
|
||||
public static QueryTypeEnum fromValue(Integer value) {
|
||||
for (QueryTypeEnum type : QueryTypeEnum.values()) {
|
||||
if (type.getValue().equals(value)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No enum constant with value " + value);
|
||||
}
|
||||
}
|
||||
28
src/main/java/com/youlai/boot/common/enums/GenderEnum.java
Normal file
28
src/main/java/com/youlai/boot/common/enums/GenderEnum.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 性别枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/14
|
||||
*/
|
||||
@Getter
|
||||
@Schema(enumAsRef = true)
|
||||
public enum GenderEnum implements IBaseEnum<Integer> {
|
||||
|
||||
MALE(1, "男"),
|
||||
FEMALE (2, "女");
|
||||
|
||||
private final Integer value;
|
||||
|
||||
private final String label;
|
||||
|
||||
GenderEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
84
src/main/java/com/youlai/boot/common/enums/JavaTypeEnum.java
Normal file
84
src/main/java/com/youlai/boot/common/enums/JavaTypeEnum.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 表单类型枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Getter
|
||||
public enum JavaTypeEnum {
|
||||
|
||||
VARCHAR("varchar", "String", "string"),
|
||||
CHAR("char", "String", "string"),
|
||||
BLOB("blob", "byte[]", "Uint8Array"),
|
||||
TEXT("text", "String", "string"),
|
||||
JSON("json", "String", "any"),
|
||||
INTEGER("int", "Integer", "number"),
|
||||
TINYINT("tinyint", "Integer", "number"),
|
||||
SMALLINT("smallint", "Integer", "number"),
|
||||
MEDIUMINT("mediumint", "Integer", "number"),
|
||||
BIGINT("bigint", "Long", "bigint"),
|
||||
FLOAT("float", "Float", "number"),
|
||||
DOUBLE("double", "Double", "number"),
|
||||
DECIMAL("decimal", "BigDecimal", "number"),
|
||||
DATE("date", "LocalDate", "Date"),
|
||||
DATETIME("datetime", "LocalDateTime", "Date");
|
||||
|
||||
// 数据库类型
|
||||
private final String dbType;
|
||||
// Java类型
|
||||
private final String javaType;
|
||||
// TypeScript类型
|
||||
private final String tsType;
|
||||
|
||||
// 数据库类型和Java类型的映射
|
||||
private static final Map<String, JavaTypeEnum> typeMap = new HashMap<>();
|
||||
|
||||
// 初始化映射关系
|
||||
static {
|
||||
for (JavaTypeEnum javaTypeEnum : JavaTypeEnum.values()) {
|
||||
typeMap.put(javaTypeEnum.getDbType(), javaTypeEnum);
|
||||
}
|
||||
}
|
||||
|
||||
JavaTypeEnum(String dbType, String javaType, String tsType) {
|
||||
this.dbType = dbType;
|
||||
this.javaType = javaType;
|
||||
this.tsType = tsType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据库类型获取对应的Java类型
|
||||
*
|
||||
* @param columnType 列类型
|
||||
* @return 对应的Java类型
|
||||
*/
|
||||
public static String getJavaTypeByColumnType(String columnType) {
|
||||
JavaTypeEnum javaTypeEnum = typeMap.get(columnType);
|
||||
if (javaTypeEnum != null) {
|
||||
return javaTypeEnum.getJavaType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Java类型获取对应的TypeScript类型
|
||||
*
|
||||
* @param javaType Java类型
|
||||
* @return 对应的TypeScript类型
|
||||
*/
|
||||
public static String getTsTypeByJavaType(String javaType) {
|
||||
for (JavaTypeEnum javaTypeEnum : JavaTypeEnum.values()) {
|
||||
if (javaTypeEnum.getJavaType().equals(javaType)) {
|
||||
return javaTypeEnum.getTsType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 日志模块枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Schema(enumAsRef = true)
|
||||
@Getter
|
||||
public enum LogModuleEnum {
|
||||
|
||||
|
||||
LOGIN("登录"),
|
||||
USER("用户"),
|
||||
DEPT("部门"),
|
||||
ROLE("角色"),
|
||||
MENU("菜单"),
|
||||
DICT("字典"),
|
||||
OTHER("其他")
|
||||
;
|
||||
|
||||
@JsonValue
|
||||
private final String moduleName;
|
||||
|
||||
LogModuleEnum(String moduleName) {
|
||||
this.moduleName = moduleName;
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/youlai/boot/common/enums/MenuTypeEnum.java
Normal file
34
src/main/java/com/youlai/boot/common/enums/MenuTypeEnum.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 菜单类型枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/4/23 9:36
|
||||
*/
|
||||
@Getter
|
||||
public enum MenuTypeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
NULL(0, null),
|
||||
MENU(1, "菜单"),
|
||||
CATALOG(2, "目录"),
|
||||
EXTLINK(3, "外链"),
|
||||
BUTTON(4, "按钮");
|
||||
|
||||
// Mybatis-Plus 提供注解表示插入数据库时插入该值
|
||||
@EnumValue
|
||||
private final Integer value;
|
||||
|
||||
// @JsonValue // 表示对枚举序列化时返回此字段
|
||||
private final String label;
|
||||
|
||||
MenuTypeEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 查询类型枚举
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum QueryTypeEnum implements IBaseEnum<Integer> {
|
||||
|
||||
/** 等于 */
|
||||
EQ(1, "="),
|
||||
|
||||
/** 模糊匹配 */
|
||||
LIKE(2, "LIKE '%s%'"),
|
||||
|
||||
/** 包含 */
|
||||
IN(3, "IN"),
|
||||
|
||||
/** 范围 */
|
||||
BETWEEN(4, "BETWEEN"),
|
||||
|
||||
/** 大于 */
|
||||
GT(5, ">"),
|
||||
|
||||
/** 大于等于 */
|
||||
GE(6, ">="),
|
||||
|
||||
/** 小于 */
|
||||
LT(7, "<"),
|
||||
|
||||
/** 小于等于 */
|
||||
LE(8, "<="),
|
||||
|
||||
/** 不等于 */
|
||||
NE(9, "!="),
|
||||
|
||||
/** 左模糊匹配 */
|
||||
LIKE_LEFT(10, "LIKE '%s'"),
|
||||
|
||||
/** 右模糊匹配 */
|
||||
LIKE_RIGHT(11, "LIKE 's%'");
|
||||
|
||||
|
||||
// 存储在数据库中的枚举属性值
|
||||
@EnumValue
|
||||
@JsonValue
|
||||
private final Integer value;
|
||||
|
||||
// 序列化成 JSON 时的属性值
|
||||
private final String label;
|
||||
|
||||
|
||||
@JsonCreator
|
||||
public static QueryTypeEnum fromValue(Integer value) {
|
||||
for (QueryTypeEnum type : QueryTypeEnum.values()) {
|
||||
if (type.getValue().equals(value)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No enum constant with value " + value);
|
||||
}
|
||||
|
||||
}
|
||||
27
src/main/java/com/youlai/boot/common/enums/StatusEnum.java
Normal file
27
src/main/java/com/youlai/boot/common/enums/StatusEnum.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.youlai.boot.common.enums;
|
||||
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 状态枚举
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/10/14
|
||||
*/
|
||||
public enum StatusEnum implements IBaseEnum<Integer> {
|
||||
|
||||
ENABLE(1, "启用"),
|
||||
DISABLE (0, "禁用");
|
||||
|
||||
@Getter
|
||||
private Integer value;
|
||||
|
||||
@Getter
|
||||
private String label;
|
||||
|
||||
StatusEnum(Integer value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.youlai.boot.common.exception;
|
||||
|
||||
import com.youlai.boot.common.result.IResultCode;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.helpers.MessageFormatter;
|
||||
|
||||
/**
|
||||
* 自定义业务异常
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2022/7/31
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
public IResultCode resultCode;
|
||||
|
||||
public BusinessException(IResultCode errorCode) {
|
||||
super(errorCode.getMsg());
|
||||
this.resultCode = errorCode;
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BusinessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BusinessException(String message, Object... args) {
|
||||
super(formatMessage(message, args));
|
||||
}
|
||||
|
||||
private static String formatMessage(String message, Object... args) {
|
||||
return MessageFormatter.arrayFormat(message, args).getMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.youlai.boot.common.exception;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.youlai.system.common.result.Result;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.jdbc.BadSqlGrammarException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
|
||||
import java.sql.SQLSyntaxErrorException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局系统异常处理器
|
||||
* <p>
|
||||
* 调整异常处理的HTTP状态码,丰富异常处理类型
|
||||
*
|
||||
* @author Gadfly
|
||||
* @since 2020-02-25 13:54
|
||||
**/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(BindException e) {
|
||||
log.error("BindException:{}", e.getMessage());
|
||||
String msg = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* RequestParam参数的校验
|
||||
*
|
||||
* @param e
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(ConstraintViolationException e) {
|
||||
log.error("ConstraintViolationException:{}", e.getMessage());
|
||||
String msg = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* RequestBody参数的校验
|
||||
*
|
||||
* @param e
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(MethodArgumentNotValidException e) {
|
||||
log.error("MethodArgumentNotValidException:{}", e.getMessage());
|
||||
String msg = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";"));
|
||||
return Result.failed(ResultCode.PARAM_ERROR, msg);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public <T> Result<T> processException(NoHandlerFoundException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* MissingServletRequestParameterException
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(MissingServletRequestParameterException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_IS_NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* MethodArgumentTypeMismatchException
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(MethodArgumentTypeMismatchException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(ResultCode.PARAM_ERROR, "类型错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* ServletException
|
||||
*/
|
||||
@ExceptionHandler(ServletException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(ServletException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
log.error("非法参数异常,异常原因:{}", e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(JsonProcessingException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleJsonProcessingException(JsonProcessingException e) {
|
||||
log.error("Json转换异常,异常原因:{}", e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* HttpMessageNotReadableException
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(HttpMessageNotReadableException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorMessage = "请求体不可为空";
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null) {
|
||||
errorMessage = convertMessage(cause);
|
||||
}
|
||||
return Result.failed(errorMessage);
|
||||
}
|
||||
|
||||
@ExceptionHandler(TypeMismatchException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> processException(TypeMismatchException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadSqlGrammarException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public <T> Result<T> handleBadSqlGrammarException(BadSqlGrammarException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String errorMsg = e.getMessage();
|
||||
if (StrUtil.isNotBlank(errorMsg) && errorMsg.contains("denied to user")) {
|
||||
return Result.failed(ResultCode.FORBIDDEN_OPERATION);
|
||||
} else {
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ExceptionHandler(SQLSyntaxErrorException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public <T> Result<T> processSQLSyntaxErrorException(SQLSyntaxErrorException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleBizException(BusinessException e) {
|
||||
log.error("biz exception: {}", e.getMessage());
|
||||
if (e.getResultCode() != null) {
|
||||
return Result.failed(e.getResultCode());
|
||||
}
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleException(Exception e) throws Exception{
|
||||
// 将 Spring Security 异常继续抛出,以便交给自定义处理器处理
|
||||
if (e instanceof AccessDeniedException
|
||||
|| e instanceof AuthenticationException) {
|
||||
throw e;
|
||||
}
|
||||
log.error("unknown exception: {}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
return Result.failed(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 传参类型错误时,用于消息转换
|
||||
*
|
||||
* @param throwable 异常
|
||||
* @return 错误信息
|
||||
*/
|
||||
private String convertMessage(Throwable throwable) {
|
||||
String error = throwable.toString();
|
||||
String regulation = "\\[\"(.*?)\"]+";
|
||||
Pattern pattern = Pattern.compile(regulation);
|
||||
Matcher matcher = pattern.matcher(error);
|
||||
String group = "";
|
||||
if (matcher.find()) {
|
||||
String matchString = matcher.group();
|
||||
matchString = matchString.replace("[", "").replace("]", "");
|
||||
matchString = "%s字段类型错误".formatted(matchString.replaceAll("\"", ""));
|
||||
group += matchString;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
32
src/main/java/com/youlai/boot/common/model/KeyValue.java
Normal file
32
src/main/java/com/youlai/boot/common/model/KeyValue.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.youlai.boot.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 下拉选项对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2024/5/25
|
||||
*/
|
||||
@Schema(description ="键值对")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class KeyValue{
|
||||
|
||||
public KeyValue(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Schema(description="选项的值")
|
||||
private String key;
|
||||
|
||||
@Schema(description="选项的标签")
|
||||
private String value;
|
||||
|
||||
}
|
||||
42
src/main/java/com/youlai/boot/common/model/Option.java
Normal file
42
src/main/java/com/youlai/boot/common/model/Option.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.youlai.boot.common.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 下拉选项对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/1/22
|
||||
*/
|
||||
@Schema(description ="下拉选项对象")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class Option<T> {
|
||||
|
||||
public Option(T value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public Option(T value, String label, List<Option<T>> children) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.children= children;
|
||||
}
|
||||
|
||||
@Schema(description="选项的值")
|
||||
private T value;
|
||||
|
||||
@Schema(description="选项的标签")
|
||||
private String label;
|
||||
|
||||
@Schema(description="子选项列表")
|
||||
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
|
||||
private List<Option<T>> children;
|
||||
|
||||
}
|
||||
15
src/main/java/com/youlai/boot/common/result/IResultCode.java
Normal file
15
src/main/java/com/youlai/boot/common/result/IResultCode.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
/**
|
||||
* 响应码接口
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2022/2/18
|
||||
**/
|
||||
public interface IResultCode {
|
||||
|
||||
String getCode();
|
||||
|
||||
String getMsg();
|
||||
|
||||
}
|
||||
46
src/main/java/com/youlai/boot/common/result/PageResult.java
Normal file
46
src/main/java/com/youlai/boot/common/result/PageResult.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页响应结构体
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2022/2/18
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private String code;
|
||||
|
||||
private Data<T> data;
|
||||
|
||||
private String msg;
|
||||
|
||||
public static <T> PageResult<T> success(IPage<T> page) {
|
||||
PageResult<T> result = new PageResult<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
|
||||
Data data = new Data<T>();
|
||||
data.setList(page.getRecords());
|
||||
data.setTotal(page.getTotal());
|
||||
|
||||
result.setData(data);
|
||||
result.setMsg(ResultCode.SUCCESS.getMsg());
|
||||
return result;
|
||||
}
|
||||
|
||||
@lombok.Data
|
||||
public static class Data<T> {
|
||||
|
||||
private List<T> list;
|
||||
|
||||
private long total;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
73
src/main/java/com/youlai/boot/common/result/Result.java
Normal file
73
src/main/java/com/youlai/boot/common/result/Result.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结构体
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2022/1/30
|
||||
**/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private String code;
|
||||
|
||||
private T data;
|
||||
|
||||
private String msg;
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(ResultCode.SUCCESS.getCode());
|
||||
result.setMsg(ResultCode.SUCCESS.getMsg());
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed() {
|
||||
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), ResultCode.SYSTEM_EXECUTION_ERROR.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(String msg) {
|
||||
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> judge(boolean status) {
|
||||
if (status) {
|
||||
return success();
|
||||
} else {
|
||||
return failed();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failed(IResultCode resultCode, String msg) {
|
||||
return result(resultCode.getCode(), msg, null);
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(IResultCode resultCode, T data) {
|
||||
return result(resultCode.getCode(), resultCode.getMsg(), data);
|
||||
}
|
||||
|
||||
private static <T> Result<T> result(String code, String msg, T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setCode(code);
|
||||
result.setData(data);
|
||||
result.setMsg(msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isSuccess(Result<?> result) {
|
||||
return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode());
|
||||
}
|
||||
}
|
||||
116
src/main/java/com/youlai/boot/common/result/ResultCode.java
Normal file
116
src/main/java/com/youlai/boot/common/result/ResultCode.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package com.youlai.boot.common.result;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 响应码枚举
|
||||
* <p>
|
||||
* 参考阿里巴巴开发手册响应码规范
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2020/6/23
|
||||
**/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public enum ResultCode implements IResultCode, Serializable {
|
||||
|
||||
SUCCESS("00000", "一切ok"),
|
||||
|
||||
USER_ERROR("A0001", "用户端错误"),
|
||||
REPEAT_SUBMIT_ERROR("A0002", "您的请求已提交,请不要重复提交或等待片刻再尝试。"),
|
||||
|
||||
USER_LOGIN_ERROR("A0200", "用户登录异常"),
|
||||
|
||||
USER_NOT_EXIST("A0201", "用户不存在"),
|
||||
USER_ACCOUNT_LOCKED("A0202", "用户账户被冻结"),
|
||||
USER_ACCOUNT_INVALID("A0203", "用户账户已作废"),
|
||||
|
||||
USERNAME_OR_PASSWORD_ERROR("A0210", "用户名或密码错误"),
|
||||
PASSWORD_ENTER_EXCEED_LIMIT("A0211", "用户输入密码次数超限"),
|
||||
CLIENT_AUTHENTICATION_FAILED("A0212", "客户端认证失败"),
|
||||
|
||||
VERIFY_CODE_TIMEOUT("A0213", "验证码已过期"),
|
||||
VERIFY_CODE_ERROR("A0214", "验证码错误"),
|
||||
|
||||
TOKEN_INVALID("A0230", "token无效或已过期"),
|
||||
TOKEN_ACCESS_FORBIDDEN("A0231", "token已被禁止访问"),
|
||||
|
||||
AUTHORIZED_ERROR("A0300", "访问权限异常"),
|
||||
ACCESS_UNAUTHORIZED("A0301", "访问未授权"),
|
||||
FORBIDDEN_OPERATION("A0302", "演示环境禁止新增、修改和删除数据,请本地部署后测试"),
|
||||
|
||||
|
||||
PARAM_ERROR("A0400", "用户请求参数错误"),
|
||||
RESOURCE_NOT_FOUND("A0401", "请求资源不存在"),
|
||||
PARAM_IS_NULL("A0410", "请求必填参数为空"),
|
||||
|
||||
USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
|
||||
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
|
||||
USER_UPLOAD_FILE_SIZE_EXCEEDS("A0702", "用户上传文件太大"),
|
||||
USER_UPLOAD_IMAGE_SIZE_EXCEEDS("A0703", "用户上传图片太大"),
|
||||
|
||||
SYSTEM_EXECUTION_ERROR("B0001", "系统执行出错"),
|
||||
SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
|
||||
SYSTEM_ORDER_PROCESSING_TIMEOUT("B0100", "系统订单处理超时"),
|
||||
|
||||
SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被触发"),
|
||||
FLOW_LIMITING("B0210", "系统限流,请稍后再试"),
|
||||
DEGRADATION("B0220", "系统功能降级"),
|
||||
|
||||
SYSTEM_RESOURCE_ERROR("B0300", "系统资源异常"),
|
||||
SYSTEM_RESOURCE_EXHAUSTION("B0310", "系统资源耗尽"),
|
||||
SYSTEM_RESOURCE_ACCESS_ERROR("B0320", "系统资源访问异常"),
|
||||
SYSTEM_READ_DISK_FILE_ERROR("B0321", "系统读取磁盘文件失败"),
|
||||
|
||||
CALL_THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
|
||||
MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错"),
|
||||
INTERFACE_NOT_EXIST("C0113", "接口不存在"),
|
||||
|
||||
MESSAGE_SERVICE_ERROR("C0120", "消息服务出错"),
|
||||
MESSAGE_DELIVERY_ERROR("C0121", "消息投递出错"),
|
||||
MESSAGE_CONSUMPTION_ERROR("C0122", "消息消费出错"),
|
||||
MESSAGE_SUBSCRIPTION_ERROR("C0123", "消息订阅出错"),
|
||||
MESSAGE_GROUP_NOT_FOUND("C0124", "消息分组未查到"),
|
||||
|
||||
DATABASE_ERROR("C0300", "数据库服务出错"),
|
||||
DATABASE_TABLE_NOT_EXIST("C0311", "表不存在"),
|
||||
DATABASE_COLUMN_NOT_EXIST("C0312", "列不存在"),
|
||||
DATABASE_DUPLICATE_COLUMN_NAME("C0321", "多表关联中存在多个相同名称的列"),
|
||||
DATABASE_DEADLOCK("C0331", "数据库死锁"),
|
||||
DATABASE_PRIMARY_KEY_CONFLICT("C0341", "主键冲突");
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
private String code;
|
||||
|
||||
private String msg;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"\"code\":\"" + code + '\"' +
|
||||
", \"msg\":\"" + msg + '\"' +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
public static ResultCode getValue(String code) {
|
||||
for (ResultCode value : values()) {
|
||||
if (value.getCode().equals(code)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return SYSTEM_EXECUTION_ERROR; // 默认系统执行错误
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.youlai.boot.common.service;
|
||||
|
||||
/**
|
||||
* 邮件服务接口层
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/8/17
|
||||
*/
|
||||
public interface MailService {
|
||||
|
||||
|
||||
/**
|
||||
* 发送简单文本邮件
|
||||
*
|
||||
* @param to 收件人地址
|
||||
* @param subject 邮件主题
|
||||
* @param text 邮件内容
|
||||
*/
|
||||
boolean sendSimpleMail(String to, String subject, String text) ;
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*
|
||||
* @param to 收件人地址
|
||||
* @param subject 邮件主题
|
||||
* @param text 邮件内容
|
||||
* @param filePath 附件路径
|
||||
*/
|
||||
boolean sendMailWithAttachment(String to, String subject, String text, String filePath);
|
||||
|
||||
}
|
||||
30
src/main/java/com/youlai/boot/common/service/OssService.java
Normal file
30
src/main/java/com/youlai/boot/common/service/OssService.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.youlai.boot.common.service;
|
||||
|
||||
import com.youlai.boot.module.system.model.dto.FileInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 对象存储服务接口层
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/11/19
|
||||
*/
|
||||
public interface OssService {
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param file 表单文件对象
|
||||
* @return 文件信息
|
||||
*/
|
||||
FileInfo uploadFile(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件完整URL
|
||||
* @return 删除结果
|
||||
*/
|
||||
boolean deleteFile(String filePath);
|
||||
|
||||
|
||||
}
|
||||
22
src/main/java/com/youlai/boot/common/service/SmsService.java
Normal file
22
src/main/java/com/youlai/boot/common/service/SmsService.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.youlai.boot.common.service;
|
||||
|
||||
/**
|
||||
* 短信服务接口层
|
||||
* <p>
|
||||
* SMS = Short Message Service 短信服务
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/8/17
|
||||
*/
|
||||
public interface SmsService {
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*
|
||||
* @param mobile 手机号 13388886666
|
||||
* @param templateCode 短信模板 SMS_194640010
|
||||
* @param templateParam 模板参数 "[{"code":"123456"}]"
|
||||
* @return boolean 是否发送成功
|
||||
*/
|
||||
boolean sendSms(String mobile, String templateCode, String templateParam);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.youlai.boot.common.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.aliyun.oss.OSS;
|
||||
import com.aliyun.oss.OSSClientBuilder;
|
||||
import com.aliyun.oss.model.ObjectMetadata;
|
||||
import com.aliyun.oss.model.PutObjectRequest;
|
||||
import com.youlai.boot.module.system.model.dto.FileInfo;
|
||||
import com.youlai.system.service.OssService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Aliyun 对象存储服务类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "oss.type", havingValue = "aliyun")
|
||||
@ConfigurationProperties(prefix = "oss.aliyun")
|
||||
@RequiredArgsConstructor
|
||||
@Data
|
||||
public class AliyunOssService implements OssService {
|
||||
/**
|
||||
* 服务Endpoint
|
||||
*/
|
||||
private String endpoint;
|
||||
/**
|
||||
* 访问凭据
|
||||
*/
|
||||
private String accessKeyId;
|
||||
/**
|
||||
* 凭据密钥
|
||||
*/
|
||||
private String accessKeySecret;
|
||||
/**
|
||||
* 存储桶名称
|
||||
*/
|
||||
private String bucketName;
|
||||
|
||||
private OSS aliyunOssClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
aliyunOssClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public FileInfo uploadFile(MultipartFile file) {
|
||||
|
||||
// 生成文件名(日期文件夹)
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix;
|
||||
// try-with-resource 语法糖自动释放流
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
|
||||
// 设置上传文件的元信息,例如Content-Type
|
||||
ObjectMetadata metadata = new ObjectMetadata();
|
||||
metadata.setContentType(file.getContentType());
|
||||
// 创建PutObjectRequest对象,指定Bucket名称、对象名称和输入流
|
||||
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, inputStream, metadata);
|
||||
// 上传文件
|
||||
aliyunOssClient.putObject(putObjectRequest);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("文件上传失败");
|
||||
}
|
||||
// 获取文件访问路径
|
||||
String fileUrl = "https://" + bucketName + "." + endpoint + "/" + fileName;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setName(fileName);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFile(String filePath) {
|
||||
Assert.notBlank(filePath, "删除文件路径不能为空");
|
||||
String fileHost = "https://" + bucketName + "." + endpoint; // 文件主机域名
|
||||
String fileName = filePath.substring(fileHost.length() + 1); // +1 是/占一个字符,截断左闭右开
|
||||
aliyunOssClient.deleteObject(bucketName, fileName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.youlai.boot.common.service.impl;
|
||||
|
||||
import com.aliyuncs.CommonRequest;
|
||||
import com.aliyuncs.CommonResponse;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.exceptions.ClientException;
|
||||
import com.aliyuncs.exceptions.ServerException;
|
||||
import com.aliyuncs.http.MethodType;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
import com.youlai.boot.config.property.AliyunSmsProperties;
|
||||
import com.youlai.system.service.SmsService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 阿里云短信业务类
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/8/17
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AliyunSmsService implements SmsService {
|
||||
|
||||
private final AliyunSmsProperties aliyunSmsProperties;
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*
|
||||
* @param mobile 手机号 13388886666
|
||||
* @param templateCode 短信模板 SMS_194640010
|
||||
* @param templateParam 模板参数 "[{"code":"123456"}]"
|
||||
*
|
||||
* @return boolean 是否发送成功
|
||||
*/
|
||||
@Override
|
||||
public boolean sendSms(String mobile,String templateCode,String templateParam) {
|
||||
|
||||
DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(),
|
||||
aliyunSmsProperties.getAccessKeyId(), aliyunSmsProperties.getAccessKeySecret());
|
||||
IAcsClient client = new DefaultAcsClient(profile);
|
||||
|
||||
// 创建通用的请求对象
|
||||
CommonRequest request = new CommonRequest();
|
||||
// 指定请求方式
|
||||
request.setSysMethod(MethodType.POST);
|
||||
// 短信api的请求地址(固定)
|
||||
request.setSysDomain(aliyunSmsProperties.getDomain());
|
||||
// 签名算法版(固定)
|
||||
request.setSysVersion("2017-05-25");
|
||||
// 请求 API 的名称(固定)
|
||||
request.setSysAction("SendSms");
|
||||
// 指定地域名称
|
||||
request.putQueryParameter("RegionId", aliyunSmsProperties.getRegionId());
|
||||
// 要给哪个手机号发送短信 指定手机号
|
||||
request.putQueryParameter("PhoneNumbers", mobile);
|
||||
// 您的申请签名
|
||||
request.putQueryParameter("SignName", aliyunSmsProperties.getSignName());
|
||||
// 您申请的模板 code
|
||||
request.putQueryParameter("TemplateCode", templateCode);
|
||||
|
||||
request.putQueryParameter("TemplateParam", templateParam);
|
||||
|
||||
try {
|
||||
CommonResponse response = client.getCommonResponse(request);
|
||||
return response.getHttpResponse().isSuccess();
|
||||
} catch (ServerException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ClientException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.youlai.boot.common.service.impl;
|
||||
|
||||
import com.youlai.boot.config.property.MailProperties;
|
||||
import com.youlai.system.service.MailService;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 邮件服务实现类
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/8/17
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MailServiceImpl implements MailService {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
|
||||
private final MailProperties mailProperties;
|
||||
|
||||
/**
|
||||
* 发送简单文本邮件
|
||||
*
|
||||
* @param to 收件人地址
|
||||
* @param subject 邮件主题
|
||||
* @param text 邮件内容
|
||||
*/
|
||||
@Override
|
||||
public boolean sendSimpleMail(String to, String subject, String text) {
|
||||
try {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setFrom(mailProperties.getFrom());
|
||||
message.setTo(to);
|
||||
message.setSubject(subject);
|
||||
message.setText(text);
|
||||
mailSender.send(message);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("发送邮件失败{}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*
|
||||
* @param to 收件人地址
|
||||
* @param subject 邮件主题
|
||||
* @param text 邮件内容
|
||||
* @param filePath 附件路径
|
||||
*/
|
||||
@Override
|
||||
public boolean sendMailWithAttachment(String to, String subject, String text, String filePath) {
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
try {
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setFrom(mailProperties.getFrom());
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(text, true); // true表示支持HTML内容
|
||||
|
||||
FileSystemResource file = new FileSystemResource(new File(filePath));
|
||||
helper.addAttachment(file.getFilename(), file);
|
||||
|
||||
mailSender.send(message);
|
||||
return true;
|
||||
} catch (MessagingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package com.youlai.boot.common.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.module.system.model.dto.FileInfo;
|
||||
import com.youlai.system.service.OssService;
|
||||
import io.minio.*;
|
||||
import io.minio.errors.*;
|
||||
import io.minio.http.Method;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* MinIO 文件上传服务类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/6/2
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "oss.type", havingValue = "minio")
|
||||
@ConfigurationProperties(prefix = "oss.minio")
|
||||
@RequiredArgsConstructor
|
||||
@Data
|
||||
public class MinioOssService implements OssService {
|
||||
|
||||
/**
|
||||
* 服务Endpoint
|
||||
*/
|
||||
private String endpoint;
|
||||
/**
|
||||
* 访问凭据
|
||||
*/
|
||||
private String accessKey;
|
||||
/**
|
||||
* 凭据密钥
|
||||
*/
|
||||
private String secretKey;
|
||||
/**
|
||||
* 存储桶名称
|
||||
*/
|
||||
private String bucketName;
|
||||
/**
|
||||
* 自定义域名
|
||||
*/
|
||||
private String customDomain;
|
||||
|
||||
private MinioClient minioClient;
|
||||
|
||||
// 依赖注入完成之后执行初始化
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
minioClient = MinioClient.builder()
|
||||
.endpoint(endpoint)
|
||||
.credentials(accessKey, secretKey)
|
||||
.build();
|
||||
// 创建存储桶(存储桶不存在)
|
||||
// createBucketIfAbsent(bucketName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param file 表单文件对象
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public FileInfo uploadFile(MultipartFile file) {
|
||||
|
||||
// 创建存储桶(存储桶不存在),如果有搭建好的minio服务,建议放在init方法中
|
||||
createBucketIfAbsent(bucketName);
|
||||
|
||||
// 生成文件名(日期文件夹)
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix;
|
||||
// try-with-resource 语法糖自动释放流
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
// 文件上传
|
||||
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(fileName)
|
||||
.contentType(file.getContentType())
|
||||
.stream(inputStream, inputStream.available(), -1)
|
||||
.build();
|
||||
minioClient.putObject(putObjectArgs);
|
||||
|
||||
// 返回文件路径
|
||||
String fileUrl;
|
||||
if (StrUtil.isBlank(customDomain)) { // 未配置自定义域名
|
||||
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
|
||||
.bucket(bucketName).object(fileName)
|
||||
.method(Method.GET)
|
||||
.build();
|
||||
|
||||
fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
|
||||
fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
|
||||
} else { // 配置自定义文件路径域名
|
||||
fileUrl = customDomain + '/' + bucketName + "/" + fileName;
|
||||
}
|
||||
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setName(fileName);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("文件上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* https://oss.youlai.tech/default/20221120/test.jpg
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteFile(String filePath) {
|
||||
Assert.notBlank(filePath, "删除文件路径不能为空");
|
||||
try {
|
||||
String fileName;
|
||||
if (StrUtil.isNotBlank(customDomain)) {
|
||||
// https://oss.youlai.tech/default/20221120/test.jpg → 20221120/test.jpg
|
||||
fileName = filePath.substring(customDomain.length() + 1 + bucketName.length() + 1); // 两个/占了2个字符长度
|
||||
} else {
|
||||
// http://localhost:9000/default/20221120/test.jpg → 20221120/test.jpg
|
||||
fileName = filePath.substring(endpoint.length() + 1 + bucketName.length() + 1);
|
||||
}
|
||||
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(fileName)
|
||||
.build();
|
||||
|
||||
minioClient.removeObject(removeObjectArgs);
|
||||
return true;
|
||||
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
|
||||
InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException |
|
||||
XmlParserException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PUBLIC桶策略
|
||||
* 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied
|
||||
*
|
||||
* @param bucketName
|
||||
* @return
|
||||
*/
|
||||
private static String publicBucketPolicy(String bucketName) {
|
||||
/**
|
||||
* AWS的S3存储桶策略
|
||||
* Principal: 生效用户对象
|
||||
* Resource: 指定存储桶
|
||||
* Action: 操作行为
|
||||
*/
|
||||
|
||||
return "{\"Version\":\"2012-10-17\","
|
||||
+ "\"Statement\":[{\"Effect\":\"Allow\","
|
||||
+ "\"Principal\":{\"AWS\":[\"*\"]},"
|
||||
+ "\"Action\":[\"s3:ListBucketMultipartUploads\",\"s3:GetBucketLocation\",\"s3:ListBucket\"],"
|
||||
+ "\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},"
|
||||
+ "{\"Effect\":\"Allow\"," + "\"Principal\":{\"AWS\":[\"*\"]},"
|
||||
+ "\"Action\":[\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\"],"
|
||||
+ "\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建存储桶(存储桶不存在)
|
||||
*
|
||||
* @param bucketName
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void createBucketIfAbsent(String bucketName) {
|
||||
BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
|
||||
if (!minioClient.bucketExists(bucketExistsArgs)) {
|
||||
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();
|
||||
|
||||
minioClient.makeBucket(makeBucketArgs);
|
||||
|
||||
// 设置存储桶访问权限为PUBLIC, 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied
|
||||
SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs
|
||||
.builder()
|
||||
.bucket(bucketName)
|
||||
.config(publicBucketPolicy(bucketName))
|
||||
.build();
|
||||
minioClient.setBucketPolicy(setBucketPolicyArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/main/java/com/youlai/boot/common/util/DateUtils.java
Normal file
61
src/main/java/com/youlai/boot/common/util/DateUtils.java
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
package com.youlai.boot.common.util;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 日期工具类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.4.2
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
/**
|
||||
* 区间日期格式化为数据库日期格式
|
||||
* <p>
|
||||
* eg:2021-01-01 → 2021-01-01 00:00:00
|
||||
*
|
||||
* @param obj 要处理的对象
|
||||
* @param startTimeFieldName 起始时间字段名
|
||||
* @param endTimeFieldName 结束时间字段名
|
||||
*/
|
||||
public static void toDatabaseFormat(Object obj, String startTimeFieldName, String endTimeFieldName) {
|
||||
Field startTimeField = ReflectUtil.getField(obj.getClass(), startTimeFieldName);
|
||||
Field endTimeField = ReflectUtil.getField(obj.getClass(), endTimeFieldName);
|
||||
|
||||
if (startTimeField != null) {
|
||||
processDateTimeField(obj, startTimeField, startTimeFieldName, "yyyy-MM-dd 00:00:00");
|
||||
}
|
||||
|
||||
if (endTimeField != null) {
|
||||
processDateTimeField(obj, endTimeField, endTimeFieldName, "yyyy-MM-dd 23:59:59");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日期字段
|
||||
*
|
||||
* @param obj 要处理的对象
|
||||
* @param field 字段
|
||||
* @param fieldName 字段名
|
||||
* @param targetPattern 目标数据库日期格式
|
||||
*/
|
||||
private static void processDateTimeField(Object obj, Field field, String fieldName, String targetPattern) {
|
||||
Object fieldValue = ReflectUtil.getFieldValue(obj, fieldName);
|
||||
if (fieldValue != null) {
|
||||
// 得到原始的日期格式
|
||||
String pattern = field.isAnnotationPresent(DateTimeFormat.class) ? field.getAnnotation(DateTimeFormat.class).pattern() : "yyyy-MM-dd";
|
||||
// 转换为日期对象
|
||||
DateTime dateTime = DateUtil.parse(StrUtil.toString(fieldValue), pattern);
|
||||
// 转换为目标数据库日期格式
|
||||
ReflectUtil.setFieldValue(obj, fieldName, dateTime.toString(targetPattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/main/java/com/youlai/boot/common/util/ExcelUtils.java
Normal file
20
src/main/java/com/youlai/boot/common/util/ExcelUtils.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.youlai.boot.common.util;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.youlai.boot.common.base.BaseAnalysisEventListener;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Excel 工具类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/03/01
|
||||
*/
|
||||
public class ExcelUtils {
|
||||
|
||||
public static <T> String importExcel(InputStream is, Class clazz, BaseAnalysisEventListener<T> listener) {
|
||||
EasyExcel.read(is, clazz, listener).sheet().doRead();
|
||||
return listener.getMsg();
|
||||
}
|
||||
}
|
||||
139
src/main/java/com/youlai/boot/common/util/IPUtils.java
Normal file
139
src/main/java/com/youlai/boot/common/util/IPUtils.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package com.youlai.boot.common.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
/**
|
||||
* IP工具类
|
||||
* <p>
|
||||
* 获取客户端IP地址和IP地址对应的地理位置信息
|
||||
* <p>
|
||||
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
|
||||
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
|
||||
* </p>
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class IPUtils {
|
||||
|
||||
private static final String DB_PATH = "/data/ip2region.xdb";
|
||||
private static Searcher searcher;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
// 从类路径加载资源文件
|
||||
InputStream inputStream = getClass().getResourceAsStream(DB_PATH);
|
||||
if (inputStream == null) {
|
||||
throw new FileNotFoundException("Resource not found: " + DB_PATH);
|
||||
}
|
||||
|
||||
// 将资源文件复制到临时文件
|
||||
Path tempDbPath = Files.createTempFile("ip2region", ".xdb");
|
||||
Files.copy(inputStream, tempDbPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// 使用临时文件初始化 Searcher 对象
|
||||
searcher = Searcher.newWithFileOnly(tempDbPath.toString());
|
||||
} catch (Exception e) {
|
||||
log.error("IpRegionUtil initialization ERROR, {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取IP地址
|
||||
*
|
||||
* @param request HttpServletRequest对象
|
||||
* @return 客户端IP地址
|
||||
*/
|
||||
public static String getIpAddr(HttpServletRequest request) {
|
||||
String ip = null;
|
||||
try {
|
||||
if (request == null) {
|
||||
return "";
|
||||
}
|
||||
ip = request.getHeader("x-forwarded-for");
|
||||
if (checkIp(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (checkIp(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (checkIp(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (checkIp(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (checkIp(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
|
||||
// 根据网卡取本机配置的IP
|
||||
ip = getLocalAddr();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("IPUtils ERROR, {}", e.getMessage());
|
||||
}
|
||||
|
||||
// 使用代理,则获取第一个IP地址
|
||||
if (StrUtil.isNotBlank(ip) && ip.indexOf(",") > 0) {
|
||||
ip = ip.substring(0, ip.indexOf(","));
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
private static boolean checkIp(String ip) {
|
||||
String unknown = "unknown";
|
||||
return StrUtil.isEmpty(ip) || unknown.equalsIgnoreCase(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本机的IP地址
|
||||
*
|
||||
* @return 本机IP地址
|
||||
*/
|
||||
private static String getLocalAddr() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("InetAddress.getLocalHost()-error, {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IP地址获取地理位置信息
|
||||
*
|
||||
* @param ip IP地址
|
||||
* @return 地理位置信息
|
||||
*/
|
||||
public static String getRegion(String ip) {
|
||||
if (searcher == null) {
|
||||
log.error("Searcher is not initialized");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return searcher.search(ip);
|
||||
} catch (Exception e) {
|
||||
log.error("IpRegionUtil ERROR, {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/main/java/com/youlai/boot/common/util/ResponseUtils.java
Normal file
51
src/main/java/com/youlai/boot/common/util/ResponseUtils.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.youlai.boot.common.util;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 响应工具类
|
||||
*
|
||||
* @author Ray Hao
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class ResponseUtils {
|
||||
|
||||
/**
|
||||
* 异常消息返回(适用过滤器中处理异常响应)
|
||||
*
|
||||
* @param response HttpServletResponse
|
||||
* @param resultCode 响应结果码
|
||||
*/
|
||||
public static void writeErrMsg(HttpServletResponse response, ResultCode resultCode) {
|
||||
// 根据不同的结果码设置HTTP状态
|
||||
int status = switch (resultCode) {
|
||||
case ACCESS_UNAUTHORIZED, TOKEN_INVALID -> HttpStatus.UNAUTHORIZED.value();
|
||||
case TOKEN_ACCESS_FORBIDDEN -> HttpStatus.FORBIDDEN.value();
|
||||
default -> HttpStatus.BAD_REQUEST.value();
|
||||
};
|
||||
|
||||
response.setStatus(status);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
|
||||
try (PrintWriter writer = response.getWriter()) {
|
||||
String jsonResponse = JSONUtil.toJsonStr(Result.failed(resultCode));
|
||||
writer.print(jsonResponse);
|
||||
writer.flush(); // 确保将响应内容写入到输出流
|
||||
} catch (IOException e) {
|
||||
log.error("响应异常处理失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user