menus = menuService.menuTree();
+ System.out.println("menus = " + menus);
+ }
+
+ @Test
+ void mailTest() {
+ mailUtils.baseSendMail("yangxx@88.com", "主题", "test", false);
+ }
+
+ @Test
+ void tokenTest(){
+ long timeout = SaTempUtil.getTimeout("asldfjaklsjfk");
+ System.out.println("timeout = " + timeout);
+ }
+
+}
diff --git a/common/common-core/pom.xml b/common/common-core/pom.xml
new file mode 100644
index 0000000..d516f4e
--- /dev/null
+++ b/common/common-core/pom.xml
@@ -0,0 +1,186 @@
+
+
+ 4.0.0
+ common-core
+ jar
+
+ common
+ com.yxx
+ 1.0.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${spring-boot.version}
+
+
+
+ org.springframework.security
+ spring-security-core
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+ ${spring-boot.version}
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ ${mybatis-plus.version}
+
+
+
+
+ mysql
+ mysql-connector-java
+ runtime
+ ${mysql.version}
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${lang3.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+ ${spring-boot.version}
+
+
+
+ io.netty
+ netty-all
+ ${netty.version}
+
+
+
+
+ org.redisson
+ redisson-spring-boot-starter
+ ${redisson.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+ ${spring-boot.version}
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+ ${sa-token.version}
+
+
+
+
+ cn.dev33
+ sa-token-jwt
+ ${sa-token.version}
+
+
+
+
+ org.apache.commons
+ commons-pool2
+ ${pool2.version}
+
+
+
+
+ cn.dev33
+ sa-token-redis-jackson
+ ${sa-token.version}
+
+
+
+
+ cn.dev33
+ sa-token-alone-redis
+ ${sa-token.version}
+
+
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+
+
+ com.dtflys.forest
+ forest-spring-boot-starter
+ ${forest.verskon}
+
+
+
+ org.jasypt
+ jasypt
+ ${jasypt.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+ ${spring-boot.version}
+
+
+
+
+ org.lionsoul
+ ip2region
+ ${ip2region.version}
+
+
+
+ commons-io
+ commons-io
+ ${ip2region-commons.version}
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ ${tomcat.version}
+
+
+
+ com.alibaba
+ transmittable-thread-local
+ ${ttl.version}
+
+
+
+
+
+
+
+ ossrh
+ OSS Snapshot repository
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+ false
+
+
+ true
+
+
+
+
+
+
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/auth/ReleaseToken.java b/common/common-core/src/main/java/com/yxx/common/annotation/auth/ReleaseToken.java
new file mode 100644
index 0000000..ef66027
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/auth/ReleaseToken.java
@@ -0,0 +1,19 @@
+package com.yxx.common.annotation.auth;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * date:Created 2021/11/30 17:20
+ * description:放行token
+ *
+ * @author yxx
+ */
+
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReleaseToken {
+}
+
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/jackson/SearchDate.java b/common/common-core/src/main/java/com/yxx/common/annotation/jackson/SearchDate.java
new file mode 100644
index 0000000..85f46a4
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/jackson/SearchDate.java
@@ -0,0 +1,31 @@
+package com.yxx.common.annotation.jackson;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.yxx.common.utils.jackson.SearchDateDeserializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author yxx
+ * @since 2022/12/2 10:36
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@JacksonAnnotationsInside
+@JsonDeserialize(using = SearchDateDeserializer.class)
+public @interface SearchDate {
+
+ /**
+ * 开始时间
+ */
+ boolean startDate() default false;
+
+ /**
+ * 结束时间
+ */
+ boolean endDate() default false;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/log/OperationLog.java b/common/common-core/src/main/java/com/yxx/common/annotation/log/OperationLog.java
new file mode 100644
index 0000000..4a9fa79
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/log/OperationLog.java
@@ -0,0 +1,26 @@
+package com.yxx.common.annotation.log;
+
+import java.lang.annotation.*;
+
+/**
+ * 自定义操作日志注解
+ *
+ * @author yxx
+ * @since 2022/7/26 11:19
+ */
+@Documented
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OperationLog {
+
+ /**
+ * 操作模块 为空的话取@Api的tags值
+ */
+ String module() default "";
+
+ /**
+ * 日志标题 为空的话取@ApiOperation的Value值
+ */
+ String title() default "";
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/mybaits/EncryptedField.java b/common/common-core/src/main/java/com/yxx/common/annotation/mybaits/EncryptedField.java
new file mode 100644
index 0000000..04bd948
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/mybaits/EncryptedField.java
@@ -0,0 +1,32 @@
+package com.yxx.common.annotation.mybaits;
+
+import com.yxx.common.utils.encryptor.DESUtil;
+import com.yxx.common.utils.encryptor.IEncryptor;
+
+import java.lang.annotation.*;
+
+/**
+ * description:
+ * 自定义注解,用来加在类字段上进行mybatis插入更新加密,查询解密
+ * 默认key为 eRiw3nvi2lg (key必须大于8位数)
+ * 默认加密类为 DESUtil.class
+ * 默认查询解密
+ *
+ * @author yxx
+ * @since 2022/11/25
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptedField {
+
+ //加密key
+ String key() default "eRiw3nvi2lg";
+
+ // 加密类
+ Class extends IEncryptor> encryptor() default DESUtil.class;
+
+ // 是否解密
+ boolean decode() default true;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/response/ResponseResult.java b/common/common-core/src/main/java/com/yxx/common/annotation/response/ResponseResult.java
new file mode 100644
index 0000000..73ddb6c
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/response/ResponseResult.java
@@ -0,0 +1,14 @@
+package com.yxx.common.annotation.response;
+
+import java.lang.annotation.*;
+
+/**
+ * @author yxx
+ * @description: controller加该注解后 可统一返回结果json格式
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD})
+@Documented
+public @interface ResponseResult {
+
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckLogin.java b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckLogin.java
new file mode 100644
index 0000000..f94a679
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckLogin.java
@@ -0,0 +1,19 @@
+package com.yxx.common.annotation.satoken;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 登录认证(User版):只有登录之后才能进入该方法
+ * 可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ */
+@SaCheckLogin(type = "user")
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaAdminCheckLogin {
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckPermission.java b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckPermission.java
new file mode 100644
index 0000000..fa4fdbd
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckPermission.java
@@ -0,0 +1,39 @@
+package com.yxx.common.annotation.satoken;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.yxx.common.utils.satoken.StpAdminUtil;
+import org.springframework.core.annotation.AliasFor;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.annotation.SaMode;
+
+/**
+ * 权限认证(User版):必须具有指定权限才能进入该方法
+ *
可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ * @author click33
+ *
+ */
+@SaCheckPermission(type = StpAdminUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaAdminCheckPermission {
+
+ /**
+ * 需要校验的权限码
+ * @return 需要校验的权限码
+ */
+ @AliasFor(annotation = SaCheckPermission.class)
+ String [] value() default {};
+
+ /**
+ * 验证模式:AND | OR,默认AND
+ * @return 验证模式
+ */
+ @AliasFor(annotation = SaCheckPermission.class)
+ SaMode mode() default SaMode.AND;
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckRole.java b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckRole.java
new file mode 100644
index 0000000..36f9454
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/annotation/satoken/SaAdminCheckRole.java
@@ -0,0 +1,39 @@
+package com.yxx.common.annotation.satoken;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.yxx.common.utils.satoken.StpAdminUtil;
+import org.springframework.core.annotation.AliasFor;
+
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+
+/**
+ * 角色认证(User版):必须具有指定角色标识才能进入该方法
+ *
可标注在函数、类上(效果等同于标注在此类的所有方法上)
+ * @author click33
+ *
+ */
+@SaCheckRole(type = StpAdminUtil.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE})
+public @interface SaAdminCheckRole {
+
+ /**
+ * 需要校验的角色标识
+ * @return 需要校验的角色标识
+ */
+ @AliasFor(annotation = SaCheckRole.class)
+ String [] value() default {};
+
+ /**
+ * 验证模式:AND | OR,默认AND
+ * @return 验证模式
+ */
+ @AliasFor(annotation = SaCheckRole.class)
+ SaMode mode() default SaMode.AND;
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/constant/Constant.java b/common/common-core/src/main/java/com/yxx/common/constant/Constant.java
new file mode 100644
index 0000000..3c4e41d
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/constant/Constant.java
@@ -0,0 +1,15 @@
+package com.yxx.common.constant;
+
+/**
+ * @author yxx
+ * @since 2022-11-11 17:46
+ */
+public interface Constant {
+
+ /**
+ * 登录用户key
+ */
+ String LOGIN_USER_KEY = "loginUser";
+
+ String EMPTY = "";
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/constant/EmailSubjectConstant.java b/common/common-core/src/main/java/com/yxx/common/constant/EmailSubjectConstant.java
new file mode 100644
index 0000000..f404943
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/constant/EmailSubjectConstant.java
@@ -0,0 +1,15 @@
+package com.yxx.common.constant;
+
+/**
+ * @author yxx
+ * @since 2023-07-25 14:07
+ */
+public interface EmailSubjectConstant {
+ String RESET_PWD = "重置密码";
+
+ String REGISTER_SUBJECT = "【SpringBoot开发模板】注册提醒";
+
+ String IP_UNUSUAL = "已使用新的 IP 地址访问您的 山河大学 帐户";
+
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/constant/LoginDevice.java b/common/common-core/src/main/java/com/yxx/common/constant/LoginDevice.java
new file mode 100644
index 0000000..bdfeb3b
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/constant/LoginDevice.java
@@ -0,0 +1,10 @@
+package com.yxx.common.constant;
+
+/**
+ * @author yxx
+ * @since 2022-11-12 14:10
+ */
+public interface LoginDevice {
+ String PC = "pc";
+ String IPHONE = "iphone";
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/constant/RedisConstant.java b/common/common-core/src/main/java/com/yxx/common/constant/RedisConstant.java
new file mode 100644
index 0000000..ebd7f1d
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/constant/RedisConstant.java
@@ -0,0 +1,41 @@
+package com.yxx.common.constant;
+
+/**
+ * redis常数
+ *
+ * @author yxx
+ * @classname RedisConstant
+ * @since 2023/07/05
+ */
+public interface RedisConstant {
+
+ /**
+ * 注册验证码
+ */
+ String EMAIL_REGISTER = "email:register:";
+
+ /**
+ * 当日发送注册验证码次数
+ */
+ String EMAIL_REGISTER_NUM = "email:register:num:";
+
+ /**
+ * 当日找回密码次数
+ */
+ String RESET_PWD_NUM = "reset:pwd:num:";
+
+ /**
+ * 重置密码内容
+ */
+ String RESET_PWD_CONTENT = "reset:pwd:content:";
+
+ /**
+ * ip异常操作
+ */
+ String IP_UNUSUAL_OPERATE = "ip:unusual:operate:";
+
+ /**
+ * ip异常登录
+ */
+ String IP_UNUSUAL_LOGIN = "ip:unusual:login:";
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/constant/SeparatorConstant.java b/common/common-core/src/main/java/com/yxx/common/constant/SeparatorConstant.java
new file mode 100644
index 0000000..2409afd
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/constant/SeparatorConstant.java
@@ -0,0 +1,14 @@
+package com.yxx.common.constant;
+
+/**
+ * @author yxx
+ * @since 2022/7/26 10:38
+ */
+public interface SeparatorConstant {
+
+ String COMMA = ",";
+
+ String BACKSLASH = "/";
+
+ String RUNG = "-";
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/LogDTO.java b/common/common-core/src/main/java/com/yxx/common/core/model/LogDTO.java
new file mode 100644
index 0000000..1717e69
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/model/LogDTO.java
@@ -0,0 +1,85 @@
+package com.yxx.common.core.model;
+
+import cn.hutool.core.convert.Convert;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ *
+ * @author yxx
+ * @since 2022/7/26 11:30
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class LogDTO extends Convert {
+ private static final long serialVersionUID = -2704193808014207916L;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 操作模块
+ */
+ private String module;
+
+ /**
+ * 日志类型 1-正常日志 2-异常日志
+ */
+ private Integer type;
+
+ /**
+ * 日志标题
+ */
+ private String title;
+
+ /**
+ * 操作IP
+ */
+ private String ip;
+
+ /**
+ * ip归属地
+ */
+ private String ipHomePlace;
+
+ /**
+ * 用户代理
+ */
+ private String userAgent;
+
+ /**
+ * 请求URI
+ */
+ private String requestUri;
+
+ private String traceId;
+
+ private String spanId;
+
+ /**
+ * 操作方式
+ */
+ private String method;
+
+ /**
+ * 操作提交的数据
+ */
+ private String params;
+
+ /**
+ * 执行时间
+ */
+ private Long time;
+
+ /**
+ * 异常信息
+ */
+ private String exception;
+
+ /**
+ * 创建人
+ */
+ private Long createUid;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/LoginUser.java b/common/common-core/src/main/java/com/yxx/common/core/model/LoginUser.java
new file mode 100644
index 0000000..2d4b054
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/model/LoginUser.java
@@ -0,0 +1,91 @@
+package com.yxx.common.core.model;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 登录用户身份权限
+ *
+ * @author yxx
+ * @since 2022/4/13 14:23
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+public class LoginUser {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户ID
+ */
+ private Long id;
+
+ /**
+ * 用户token
+ */
+ private String token;
+
+ /**
+ * 用户账号
+ */
+ private String loginCode;
+
+ /**
+ * 登录名称
+ */
+ private String loginName;
+
+ /**
+ * 学号
+ */
+ private String studentNumber;
+
+ /**
+ * 联系手机
+ */
+ private String linkPhone;
+
+ /**
+ * ip归属地
+ */
+ private String ipHomePlace;
+
+ /**
+ * 登录设备
+ */
+ private String agent;
+
+ /**
+ * 邮箱
+ */
+ private String email;
+
+ /**
+ * 菜单权限
+ */
+ private List menuPermission;
+
+ /**
+ * 按钮权限
+ */
+ private List buttonPermission;
+
+ /**
+ * 角色权限
+ */
+ private List rolePermission;
+
+ /**
+ * 登录时间
+ */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private LocalDateTime loginTime;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/OperateAdminLog.java b/common/common-core/src/main/java/com/yxx/common/core/model/OperateAdminLog.java
new file mode 100644
index 0000000..ffde787
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/model/OperateAdminLog.java
@@ -0,0 +1,112 @@
+package com.yxx.common.core.model;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * 操作日志表
+ *
+ * @author yxx
+ * @since 2022-07-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("operate_admin_log")
+public class OperateAdminLog extends Convert {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 日志类型 1-正常日志 2-异常日志
+ */
+ private Integer type;
+
+ /**
+ * 操作模块
+ */
+ private String module;
+
+ /**
+ * 日志标题
+ */
+ private String title;
+
+ /**
+ * 操作IP
+ */
+ private String ip;
+
+ /**
+ * 用户代理
+ */
+ private String userAgent;
+
+ /**
+ * 请求URI
+ */
+ private String requestUri;
+
+ /**
+ * 操作方式
+ */
+ private String method;
+
+ /**
+ * 操作提交的数据
+ */
+ private String params;
+
+ /**
+ * tLog中的traceId
+ */
+ private String traceId;
+
+ /**
+ * tLog中的spanId
+ */
+ private String spanId;
+
+ /**
+ * 执行时间
+ */
+ private Long time;
+
+ /**
+ * 异常信息
+ */
+ private String exception;
+
+ /**
+ * 创建人
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Long createUid;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Boolean isDelete;
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/core/model/OperateLog.java b/common/common-core/src/main/java/com/yxx/common/core/model/OperateLog.java
new file mode 100644
index 0000000..10402eb
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/model/OperateLog.java
@@ -0,0 +1,112 @@
+package com.yxx.common.core.model;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDateTime;
+
+/**
+ * 操作日志表
+ *
+ * @author yxx
+ * @since 2022-07-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("operate_log")
+public class OperateLog extends Convert {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 日志类型 1-正常日志 2-异常日志
+ */
+ private Integer type;
+
+ /**
+ * 操作模块
+ */
+ private String module;
+
+ /**
+ * 日志标题
+ */
+ private String title;
+
+ /**
+ * 操作IP
+ */
+ private String ip;
+
+ /**
+ * 用户代理
+ */
+ private String userAgent;
+
+ /**
+ * 请求URI
+ */
+ private String requestUri;
+
+ /**
+ * 操作方式
+ */
+ private String method;
+
+ /**
+ * 操作提交的数据
+ */
+ private String params;
+
+ /**
+ * tLog中的traceId
+ */
+ private String traceId;
+
+ /**
+ * tLog中的spanId
+ */
+ private String spanId;
+
+ /**
+ * 执行时间
+ */
+ private Long time;
+
+ /**
+ * 异常信息
+ */
+ private String exception;
+
+ /**
+ * 创建人
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private Long createUid;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Boolean isDelete;
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/core/page/BasePageRequest.java b/common/common-core/src/main/java/com/yxx/common/core/page/BasePageRequest.java
new file mode 100644
index 0000000..28d8ddf
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/page/BasePageRequest.java
@@ -0,0 +1,26 @@
+package com.yxx.common.core.page;
+
+import lombok.Data;
+
+import jakarta.validation.constraints.Min;
+
+/**
+ * 分页参数基本信息
+ *
+ * @author yxx
+ * @since 2022-02-07 21:40
+ */
+@Data
+public class BasePageRequest {
+ /**
+ * 页面大小
+ */
+ @Min(value = 1, message = "页面大小不合法")
+ private Integer pageSize = 10;
+
+ /**
+ * 页码
+ */
+ @Min(value = 1, message = "页码不合法")
+ private Integer page = 1;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/core/response/BaseResponse.java b/common/common-core/src/main/java/com/yxx/common/core/response/BaseResponse.java
new file mode 100644
index 0000000..11846c1
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/response/BaseResponse.java
@@ -0,0 +1,121 @@
+package com.yxx.common.core.response;
+
+import com.yxx.common.enums.ApiCode;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author yxx
+ * @description: 返回结果实体类
+ */
+@Data
+public class BaseResponse implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 返回码
+ */
+ private Integer code;
+
+ /**
+ * 返回消息
+ */
+ private String message;
+
+ /**
+ * 返回数据
+ */
+ private Object data;
+
+ private String traceId;
+
+ private BaseResponse() {
+
+ }
+
+ public BaseResponse(ApiCode apiCode, Object data) {
+ this.code = apiCode.code();
+ this.message = apiCode.message();
+ this.data = data;
+ }
+
+ private void setResultCode(ApiCode apiCode) {
+ this.code = apiCode.code();
+ this.message = apiCode.message();
+ }
+
+ /**
+ * 返回数据
+ *
+ * @return 返回成功
+ */
+ public static BaseResponse success() {
+ BaseResponse result = new BaseResponse();
+ result.setResultCode(ApiCode.SUCCESS);
+ return result;
+ }
+
+ /**
+ * 返回成功
+ *
+ * @param data 自定义返回结果
+ * @return 返回成功
+ */
+ public static BaseResponse success(Object data, String traceId) {
+ BaseResponse result = new BaseResponse();
+ result.setResultCode(ApiCode.SUCCESS);
+ result.setData(data);
+ result.setTraceId(traceId);
+ return result;
+ }
+
+ public static BaseResponse success(Object data) {
+ BaseResponse result = new BaseResponse();
+ result.setResultCode(ApiCode.SUCCESS);
+ result.setData(data);
+ return result;
+ }
+
+ /**
+ * 返回失败
+ *
+ * @param code 状态码
+ * @param message 描述信息
+ * @return 返回失败结果
+ */
+ public static BaseResponse fail(Integer code, String message, String traceId) {
+ BaseResponse result = new BaseResponse();
+ result.setCode(code);
+ result.setMessage(message);
+ result.setTraceId(traceId);
+ return result;
+ }
+
+ public static BaseResponse fail(Integer code, String message) {
+ BaseResponse result = new BaseResponse();
+ result.setCode(code);
+ result.setMessage(message);
+ return result;
+ }
+
+ /**
+ * 返回失败
+ *
+ * @param apiCode 状态枚举类
+ * @return 返回失败结果
+ */
+ public static BaseResponse fail(ApiCode apiCode, String traceId) {
+ BaseResponse result = new BaseResponse();
+ result.setResultCode(apiCode);
+ result.setTraceId(traceId);
+ return result;
+ }
+
+ public static BaseResponse fail(ApiCode apiCode) {
+ BaseResponse result = new BaseResponse();
+ result.setResultCode(apiCode);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/core/response/ErrorResponse.java b/common/common-core/src/main/java/com/yxx/common/core/response/ErrorResponse.java
new file mode 100644
index 0000000..ff8a7f6
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/core/response/ErrorResponse.java
@@ -0,0 +1,56 @@
+package com.yxx.common.core.response;
+
+import com.yxx.common.enums.ApiCode;
+import lombok.Data;
+
+/**
+ * 异常结果包装类
+ *
+ * @author yxx
+ */
+@Data
+public class ErrorResponse {
+
+ private Integer code;
+
+ private String message;
+
+ private String exception;
+
+ /**
+ * @param apiCode 状态枚举类
+ * @param e 异常
+ * @param message 异常描述信息
+ * @return 自定义异常返回
+ */
+ public static ErrorResponse fail(ApiCode apiCode, Throwable e, String message) {
+ ErrorResponse errorResult = ErrorResponse.fail(apiCode, e);
+ errorResult.setMessage(message);
+ return errorResult;
+ }
+
+ /**
+ * @param apiCode 状态枚举类
+ * @param e 异常
+ * @return 自定义异常返回
+ */
+ public static ErrorResponse fail(ApiCode apiCode, Throwable e) {
+ ErrorResponse errorResult = new ErrorResponse();
+ errorResult.setCode(apiCode.code());
+ errorResult.setMessage(apiCode.message());
+ errorResult.setException(e.getClass().getName());
+ return errorResult;
+ }
+
+ /**
+ * @param code 异常状态码
+ * @param message 异常描述信息
+ * @return 自定义异常返回
+ */
+ public static ErrorResponse fail(Integer code, String message) {
+ ErrorResponse errorResponse = new ErrorResponse();
+ errorResponse.setCode(code);
+ errorResponse.setMessage(message);
+ return errorResponse;
+ }
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/enums/ApiCode.java b/common/common-core/src/main/java/com/yxx/common/enums/ApiCode.java
new file mode 100644
index 0000000..7c01bae
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/enums/ApiCode.java
@@ -0,0 +1,103 @@
+package com.yxx.common.enums;
+
+import lombok.Getter;
+
+/**
+ * @author yxx
+ * @description: 状态枚举类
+ */
+@Getter
+public enum ApiCode {
+ /**
+ * 成功状态码
+ */
+ SUCCESS(200, "成功"),
+
+ TOKEN_ERROR(401, "token不存在或失效"),
+
+ /**
+ * 参数错误
+ */
+ PARAM_IS_INVALID(1001, "参数无效"),
+
+ PARAM_IS_BLANK(1002, "参数为空"),
+
+ PARAM_ERROR(1003, "参数错误"),
+
+ PARAM_TYPE_BIND_ERROR(1004, "参数类型错误"),
+
+ PARAM_NOT_COMPLETE(1005, "参数缺失"),
+
+ /**
+ * 用户错误 2001-2999
+ */
+ USER_NOT_LOGIN(2001, "用户未登录"),
+
+ USER_NOT_EXIST(2002, "账号不存在"),
+
+ USER_EXIST(2003, "账号或手机号已存在"),
+
+ PASSWORD_ERROR(2004, "密码错误"),
+
+ USER_PERMISSION_ERROR(2005, "无权查看他人信息"),
+
+ USER_NOT_ROLE(2006, "用户无角色"),
+
+ CAPTCHA_ERROR(2007, "验证码错误"),
+ ORIGINAL_PASSWORD_ERROR(2008, "原密码错误"),
+ REGISTER_MAX(2009, "已经达到今日最大注册次数,明日再试吧"),
+
+ /**
+ * 找回密码
+ */
+ RESET_PWD_MAX(3000, "已经达到今日最大找回密码次数,明日再试吧"),
+ RESET_PWD_TOKEN_ERROR(3001, "链接已失效,请重新找回密码"),
+
+
+ /**
+ * 邮件错误
+ */
+ MAIL_ERROR(8000, "邮件发送失败"),
+
+ MAIL_EXIST(8001, "邮件已成功发送过,请前往邮箱查看,如收件箱不存在,请前往垃圾邮箱查看"),
+
+ CAPTCHA_NOT_EXIST(8002, "验证码已过期或未发送,请重新发送"),
+
+ EMAIL_NOT_EXIST(8003, "邮箱不存在"),
+ EMAIL_NOT_REGISTER(8004, "邮箱尚未注册,先去注册吧~"),
+ EMAIL_EXIST(8005, "该邮箱已经注册过"),
+
+
+ /**
+ * 其它
+ */
+ DATE_ERROR(9000, "开始时间存在多个"),
+
+ ENCRYPTION_ERROR(90001, "加密异常"),
+
+ DECODE_ERROR(90002, "解密异常"),
+
+ KEY_ERROR(90003, "密钥异常"),
+
+ KEY_LENGTH_ERROR(90004, "加密失败,key不能小于8位"),
+
+
+ SYSTEM_ERROR(10000, "系统异常,请稍后重试"),
+ ;
+
+ private final Integer code;
+ private final String message;
+
+ ApiCode(Integer code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public Integer code() {
+ return this.code;
+ }
+
+ public String message() {
+ return this.message;
+ }
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/enums/LogTypeEnum.java b/common/common-core/src/main/java/com/yxx/common/enums/LogTypeEnum.java
new file mode 100644
index 0000000..95c0802
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/enums/LogTypeEnum.java
@@ -0,0 +1,32 @@
+package com.yxx.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author yxx
+ * @since 2022/7/26 11:20
+ */
+@Getter
+@AllArgsConstructor
+public enum LogTypeEnum {
+ /**
+ * 正常日志类型
+ */
+ NORMAL(1, "正常日志"),
+
+ /**
+ * 错误日志类型
+ */
+ ERROR(2, "错误日志");
+
+ /**
+ * 类型
+ */
+ private final Integer code;
+
+ /**
+ * 描述
+ */
+ private final String message;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/enums/business/DelFlagEnum.java b/common/common-core/src/main/java/com/yxx/common/enums/business/DelFlagEnum.java
new file mode 100644
index 0000000..03eb494
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/enums/business/DelFlagEnum.java
@@ -0,0 +1,26 @@
+package com.yxx.common.enums.business;
+
+import lombok.Getter;
+
+/**
+ * @author yxx
+ * @since 2022-05-07 11:36
+ */
+@Getter
+public enum DelFlagEnum {
+ /**
+ * 是否删除
+ */
+ YES(1, "删除"),
+ NO(0, "未删除");
+
+ private final Integer code;
+
+ private final String message;
+
+ DelFlagEnum(Integer code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/enums/business/RoleEnum.java b/common/common-core/src/main/java/com/yxx/common/enums/business/RoleEnum.java
new file mode 100644
index 0000000..1119135
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/enums/business/RoleEnum.java
@@ -0,0 +1,28 @@
+package com.yxx.common.enums.business;
+
+import lombok.Getter;
+
+/**
+ * @program: baseProject
+ * @description: 角色枚举
+ * @author: yxx
+ * @date: 2022-02-16 22:11
+ */
+@Getter
+public enum RoleEnum {
+ /**
+ * 角色
+ */
+ USER("user", "用户"),
+ ADMIN("admin", "管理员"),
+ SUPER_ADMIN("super-admin", "超级管理员"),
+ ;
+
+ private final String code;
+ private final String message;
+
+ RoleEnum(String code, String message){
+ this.code = code;
+ this.message = message;
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/exceptions/ApiException.java b/common/common-core/src/main/java/com/yxx/common/exceptions/ApiException.java
new file mode 100644
index 0000000..dbe9e13
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/exceptions/ApiException.java
@@ -0,0 +1,61 @@
+package com.yxx.common.exceptions;
+
+import com.yxx.common.enums.ApiCode;
+import lombok.Data;
+
+/**
+ * @author yxx
+ * @description: 自定义异常类
+ */
+@Data
+public class ApiException extends RuntimeException {
+
+ /**
+ * 错误码
+ */
+ private Integer code;
+
+ /**
+ * 错误信息
+ */
+ private String message;
+
+ public ApiException() {
+ super();
+ }
+
+ public ApiException(ApiCode resultCode) {
+ super(resultCode.message());
+ this.code = resultCode.code();
+ this.message = resultCode.message();
+ }
+
+ public ApiException(ApiCode resultCode, Throwable cause) {
+ super(resultCode.message(), cause);
+ this.code = resultCode.code();
+ this.message = resultCode.message();
+ }
+
+ public ApiException(String message) {
+ super(message);
+ this.code = -1;
+ this.message = message;
+ }
+
+ public ApiException(Integer code, String message) {
+ super(message);
+ this.code = code;
+ this.message = message;
+ }
+
+ public ApiException(Integer code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
+ this.message = message;
+ }
+
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/exceptions/BaseException.java b/common/common-core/src/main/java/com/yxx/common/exceptions/BaseException.java
new file mode 100644
index 0000000..3bc0fb2
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/exceptions/BaseException.java
@@ -0,0 +1,38 @@
+package com.yxx.common.exceptions;
+
+import com.yxx.common.enums.ApiCode;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * 基础异常
+ *
+ * @author yxx
+ * @since 2022/7/13 18:19
+ */
+@Getter
+public class BaseException extends RuntimeException implements Serializable {
+ private static final long serialVersionUID = 2588129946203384980L;
+
+ private Integer code;
+
+ public BaseException(Integer code) {
+ super();
+ this.code = code;
+ }
+
+ public BaseException(Integer code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public BaseException(ApiCode apiCode) {
+ super(apiCode.getMessage());
+ this.code = apiCode.getCode();
+ }
+
+ public BaseException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/properties/IpProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/IpProperties.java
new file mode 100644
index 0000000..7f04e16
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/properties/IpProperties.java
@@ -0,0 +1,22 @@
+package com.yxx.common.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * ip
+ *
+ * @author yxx
+ * @classname IpProperties
+ * @since 2023-08-05 23:13
+ */
+@Component
+@Data
+@ConfigurationProperties(prefix = "ip")
+public class IpProperties {
+ /**
+ * 是否校验
+ */
+ private Boolean check;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/properties/MailProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/MailProperties.java
new file mode 100644
index 0000000..f6c7e9b
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/properties/MailProperties.java
@@ -0,0 +1,44 @@
+package com.yxx.common.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author yxx
+ * @since 2022-11-12 02:11
+ */
+@Component
+@Data
+@ConfigurationProperties(prefix = "mail")
+public class MailProperties {
+ /**
+ * 发件人邮箱
+ */
+ private String from;
+
+ /**
+ * 发件人昵称
+ */
+ private String fromName;
+
+ /**
+ * 注册验证码过期时间
+ */
+ private Integer registerTime;
+
+ /**
+ * 一个邮箱每日最大注册次数 (防止恶意发送邮件)
+ */
+ private Integer registerMax;
+
+ /**
+ * 注册邮件正文
+ */
+ private String registerContent;
+
+ /**
+ * ip异常邮件正文
+ */
+ private String ipUnusualContent;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/properties/MyWebProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/MyWebProperties.java
new file mode 100644
index 0000000..d9175fa
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/properties/MyWebProperties.java
@@ -0,0 +1,22 @@
+package com.yxx.common.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 网站
+ *
+ * @author yxx
+ * @classname WebProperties
+ * @since 2023-08-06 02:24
+ */
+@Component
+@Data
+@ConfigurationProperties(prefix = "web")
+public class MyWebProperties {
+ /**
+ * 域名
+ */
+ private String domain;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/properties/ResetPwdProperties.java b/common/common-core/src/main/java/com/yxx/common/properties/ResetPwdProperties.java
new file mode 100644
index 0000000..d8035ef
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/properties/ResetPwdProperties.java
@@ -0,0 +1,38 @@
+package com.yxx.common.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 找回密码配置文件
+ *
+ * @author yxx
+ * @classname ResetPwdProperties
+ * @since 2023-07-26 10:56
+ */
+@Component
+@Data
+@ConfigurationProperties(prefix = "reset-password")
+public class ResetPwdProperties {
+
+ /**
+ * # 找回密码页面url
+ */
+ private String basePath;
+
+ /**
+ * 每日找回密码最大次数 (防止恶意发送邮件)
+ */
+ private Integer maxNumber;
+
+ /**
+ * 找回密码邮件正文
+ */
+ private String resetPwdContent;
+
+ /**
+ * 找回密码链接有效期
+ */
+ private Integer resetPwdTime;
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ApiAssert.java b/common/common-core/src/main/java/com/yxx/common/utils/ApiAssert.java
new file mode 100644
index 0000000..6927e1b
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/ApiAssert.java
@@ -0,0 +1,297 @@
+package com.yxx.common.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.yxx.common.exceptions.ApiException;
+import com.yxx.common.enums.ApiCode;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * API断言
+ *
+ * @author yxx
+ * @since 2022/4/13 14:37
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ApiAssert {
+
+ /**
+ * 两个对象必须相等
+ *
+ * @param apiCode ApiCode
+ * @param obj1 对象1
+ * @param obj2 对象2
+ */
+ public static void equals(ApiCode apiCode, Object obj1, Object obj2) {
+ equals(apiCode, obj1, obj2, null);
+ }
+
+ /**
+ * 条件必须为true
+ *
+ * @param apiCode ApiCode
+ * @param condition 判断条件
+ */
+ public static void isTrue(ApiCode apiCode, boolean condition) {
+ isTrue(apiCode, condition, null);
+ }
+
+ /**
+ * 条件必须为false
+ *
+ * @param apiCode ApiCode
+ * @param condition 判断条件
+ */
+ public static void isFalse(ApiCode apiCode, boolean condition) {
+ isFalse(apiCode, condition, null);
+ }
+
+ /**
+ * 对象必须为null
+ *
+ * @param apiCode ApiCode
+ * @param obj 校验对象
+ */
+ public static void isNull(ApiCode apiCode, Object obj) {
+ isNull(apiCode, obj, null);
+ }
+
+ /**
+ * 对象不能为null
+ *
+ * @param apiCode ApiCode
+ * @param obj 校验对象
+ */
+ public static void isNotNull(ApiCode apiCode, Object obj) {
+ isNotNull(apiCode, obj, null);
+ }
+
+ /**
+ * 数组不能为空
+ *
+ * @param apiCode ApiCode
+ * @param array 数组
+ */
+ public static void notEmpty(ApiCode apiCode, Object[] array) {
+ notEmpty(apiCode, array, null);
+ }
+
+ /**
+ * 数组不能包含空元素
+ *
+ * @param apiCode ApiCode
+ * @param array 数组
+ */
+ public static void noNullElements(ApiCode apiCode, Object[] array) {
+ noNullElements(apiCode, array, null);
+ }
+
+ /**
+ * 集合不能为空
+ *
+ * @param apiCode ApiCode
+ * @param collection 集合对象
+ */
+ public static void notEmpty(ApiCode apiCode, Collection> collection) {
+ notEmpty(apiCode, collection, null);
+ }
+
+ /**
+ * 集合必须为空
+ *
+ * @param apiCode ApiCode
+ * @param collection 集合对象
+ */
+ public static void isEmpty(ApiCode apiCode, Collection> collection) {
+ isEmpty(apiCode, collection, null);
+ }
+
+ /**
+ * Map不能为空
+ *
+ * @param apiCode ApiCode
+ * @param map MAP对象
+ */
+ public static void notEmpty(ApiCode apiCode, Map, ?> map) {
+ notEmpty(apiCode, map, null);
+ }
+
+ /**
+ * Map必须为空
+ *
+ * @param apiCode ApiCode
+ * @param map Map对象
+ */
+ public static void isEmpty(ApiCode apiCode, Map, ?> map) {
+ isEmpty(apiCode, map, null);
+ }
+
+ /**
+ * 两个对象必须相等
+ *
+ * @param apiCode ApiCode
+ * @param obj1 对象1
+ * @param obj2 对象2
+ * @param errorMsg 错误描述
+ */
+ public static void equals(ApiCode apiCode, Object obj1, Object obj2, String errorMsg) {
+ if (!Objects.equals(obj1, obj2)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 条件必须为true
+ *
+ * @param apiCode ApiCode
+ * @param condition 判断条件
+ * @param errorMsg 错误描述
+ */
+ public static void isTrue(ApiCode apiCode, boolean condition, String errorMsg) {
+ if (!condition) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 条件必须为false
+ *
+ * @param apiCode ApiCode
+ * @param condition 判断条件
+ * @param errorMsg 错误描述
+ */
+ public static void isFalse(ApiCode apiCode, boolean condition, String errorMsg) {
+ if (condition) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 对象必须为null
+ *
+ * @param apiCode ApiCode
+ * @param obj 校验对象
+ * @param errorMsg 错误描述
+ */
+ public static void isNull(ApiCode apiCode, Object obj, String errorMsg) {
+ if (ObjectUtil.isNotNull(obj)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 对象不能为null
+ *
+ * @param apiCode ApiCode
+ * @param obj 校验对象
+ * @param errorMsg 错误描述
+ */
+ public static void isNotNull(ApiCode apiCode, Object obj, String errorMsg) {
+ if (ObjectUtil.isNull(obj)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 数组不能为空
+ *
+ * @param apiCode ApiCode
+ * @param array 数组
+ * @param errorMsg 错误描述
+ */
+ public static void notEmpty(ApiCode apiCode, Object[] array, String errorMsg) {
+ if (ObjectUtil.isEmpty(array)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 数组不能包含空元素
+ *
+ * @param apiCode ApiCode
+ * @param array 数组
+ * @param errorMsg 错误描述
+ */
+ public static void noNullElements(ApiCode apiCode, Object[] array, String errorMsg) {
+ if (array != null) {
+ for (Object element : array) {
+ if (element == null) {
+ failure(apiCode, errorMsg);
+ }
+ }
+ }
+ }
+
+ /**
+ * 集合不能为空
+ *
+ * @param apiCode ApiCode
+ * @param collection 集合对象
+ * @param errorMsg 错误描述
+ */
+ public static void notEmpty(ApiCode apiCode, Collection> collection, String errorMsg) {
+ if (CollUtil.isEmpty(collection)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * 集合必须为空
+ *
+ * @param apiCode ApiCode
+ * @param collection 集合对象
+ * @param errorMsg 错误描述
+ */
+ public static void isEmpty(ApiCode apiCode, Collection> collection, String errorMsg) {
+ if (CollUtil.isNotEmpty(collection)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * Map不能为空
+ *
+ * @param apiCode ApiCode
+ * @param map MAP对象
+ * @param errorMsg 错误描述
+ */
+ public static void notEmpty(ApiCode apiCode, Map, ?> map, String errorMsg) {
+ if (MapUtil.isEmpty(map)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+ /**
+ * Map必须为空
+ *
+ * @param apiCode ApiCode
+ * @param map Map对象
+ * @param errorMsg 错误描述
+ */
+ public static void isEmpty(ApiCode apiCode, Map, ?> map, String errorMsg) {
+ if (MapUtil.isNotEmpty(map)) {
+ failure(apiCode, errorMsg);
+ }
+ }
+
+
+ /**
+ * 失败结果
+ *
+ * @param apiCode ApiCode
+ * @param errorMsg 错误描述 如果有值则覆盖原错误提示
+ */
+ public static void failure(ApiCode apiCode, String errorMsg) {
+ if (CharSequenceUtil.isBlank(errorMsg)) {
+ throw new ApiException(apiCode);
+ }
+ throw new ApiException(apiCode.getCode(), errorMsg);
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ApplicationUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/ApplicationUtils.java
new file mode 100644
index 0000000..475918f
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/ApplicationUtils.java
@@ -0,0 +1,148 @@
+package com.yxx.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.Objects;
+
+/**
+ * Spring Application 工具类
+ *
+ * @author yxx
+ * @since 2021/12/29 10:43
+ */
+@Slf4j
+@Lazy(false)
+@Component
+public class ApplicationUtils implements ApplicationContextAware, DisposableBean {
+
+ /**
+ * 全局的ApplicationContext
+ */
+ private static ApplicationContext applicationContext = null;
+
+ /**
+ * 获取ApplicationContext
+ */
+ public static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+ /**
+ * 获取springbean
+ */
+ public static T getBean(String beanName, Class requiredType) {
+ if (containsBean(beanName)) {
+ return applicationContext.getBean(beanName, requiredType);
+ }
+ return null;
+ }
+
+ /**
+ * 获取springbean
+ */
+ public static T getBean(Class requiredType) {
+ return applicationContext.getBean(requiredType);
+ }
+
+ /**
+ * 获取springbean
+ */
+ public static T getBean(String beanName) {
+ if (containsBean(beanName)) {
+ Class type = getType(beanName);
+ return applicationContext.getBean(beanName, type);
+ }
+ return null;
+ }
+
+ /**
+ * 依赖spring框架获取HttpServletRequest
+ *
+ * @return HttpServletRequest
+ */
+ public static HttpServletRequest getRequest() {
+ HttpServletRequest request = null;
+ try {
+ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ if (Objects.nonNull(requestAttributes)) {
+ request = requestAttributes.getRequest();
+ }
+ } catch (Exception ignored) {
+ }
+ return request;
+ }
+
+ public static HttpServletResponse getResponse() {
+ HttpServletResponse response = null;
+ try {
+ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ if (Objects.nonNull(requestAttributes)) {
+ response = requestAttributes.getResponse();
+ }
+ } catch (Exception ignored) {
+ }
+ return response;
+ }
+
+ /**
+ * 发布事件
+ */
+ public static void publishEvent(ApplicationEvent event) {
+ if (applicationContext == null) {
+ return;
+ }
+ applicationContext.publishEvent(event);
+ }
+
+ /**
+ * ApplicationContext是否包含该Bean
+ */
+ public static boolean containsBean(String name) {
+ return applicationContext.containsBean(name);
+ }
+
+ /**
+ * ApplicationContext该Bean是否为单例
+ */
+ public static boolean isSingleton(String name) {
+ return applicationContext.isSingleton(name);
+ }
+
+ /**
+ * 获取该Bean的Class
+ */
+ @SuppressWarnings("unchecked")
+ public static Class getType(String name) {
+ return (Class) applicationContext.getType(name);
+ }
+
+ @Override
+ public void destroy() {
+ ApplicationUtils.clearHolder();
+ }
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ ApplicationUtils.applicationContext = applicationContext;
+ }
+
+ /**
+ * 清除ApplicationUtils中的ApplicationContext为Null.
+ */
+ public static void clearHolder() {
+ log.debug("清除ApplicationUtils中的ApplicationContext:" + applicationContext);
+ applicationContext = null;
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/DateUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/DateUtils.java
new file mode 100644
index 0000000..0726963
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/DateUtils.java
@@ -0,0 +1,416 @@
+package com.yxx.common.utils;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.NumberUtil;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.yxx.common.enums.ApiCode;
+import com.yxx.common.exceptions.ApiException;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * @author yxx
+ * @since 2022/7/18 9:40
+ */
+@SuppressWarnings("unused")
+public class DateUtils extends DateUtil {
+
+ private static final String[] PARSE_PATTERNS = {
+ "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+ "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+ "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+ public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
+ public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ public static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * 格式化时间为 yyyy-MM-dd HH:mm:ss
+ *
+ * @param date 时间
+ * @return 转换后字符串
+ */
+ public static String formatDateTime(Date date) {
+ return format(date, DATETIME_FORMAT);
+ }
+
+ /**
+ * 格式化时间为 yyyy-MM
+ * 获取当前年月
+ *
+ * @return 转换后字符串
+ */
+ public static String getDateTime(LocalDateTime localDate) {
+ //设置日期格式
+ return localDate.format(DateTimeFormatter.ofPattern("yyyy-MM"));
+ }
+
+
+ /**
+ * 格式化时间为 yyyy-MM-dd HH:mm:ss
+ *
+ * @param dateTime 时间
+ * @return 转换后字符串
+ */
+ public static String formatDateTime(LocalDateTime dateTime) {
+ return format(dateTime, DATETIME_FORMAT);
+ }
+
+ /**
+ * Date类型转字符串
+ *
+ * @param date 日期
+ * @return yyyy-MM-dd HH:mm:ss
+ */
+ public static String date2Str(Date date) {
+ return date2Str(null, date);
+ }
+
+ /**
+ * Date类型转字符串
+ *
+ * @param format 日期格式
+ * @param date 日期
+ * @return 转换后的字符串
+ */
+ public static String date2Str(String format, Date date) {
+ if (ObjectUtils.isNull(date)) {
+ return null;
+ }
+ SimpleDateFormat dateFormat = CharSequenceUtil.isBlank(format) ?
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") :
+ new SimpleDateFormat(format);
+ return dateFormat.format(date);
+ }
+
+ /**
+ * 字符串转date类型
+ *
+ * @param dateStr 日期字符串
+ * @return Date
+ */
+ public static Date parseDate(Object dateStr) {
+ if (ObjectUtils.isNull(dateStr)) {
+ return null;
+ }
+ return parse(dateStr.toString(), PARSE_PATTERNS);
+ }
+
+ /**
+ * 字符串转LocalTime类型
+ *
+ * @param dateStr 日期字符串
+ * @return LocalTime
+ */
+ public static LocalTime parseLocalTime(Object dateStr) {
+ if (ObjectUtils.isNull(dateStr)) {
+ return null;
+ }
+ if (dateStr instanceof Double) {
+ return date2LocalTime(parseDate(dateStr));
+ }
+ return LocalTime.parse(dateStr.toString(), TIME_FORMATTER);
+ }
+
+ /**
+ * Date转LocalTime
+ *
+ * @param date 日期
+ * @return LocalTime
+ */
+ public static LocalTime date2LocalTime(Date date) {
+ if (null == date) {
+ return null;
+ }
+ return date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
+ }
+
+ /**
+ * LocalTime转Str
+ *
+ * @param localTime 时间
+ * @return HH:mm:ss
+ */
+ public static String localTime2Str(LocalTime localTime) {
+ return localTime2Str(null, localTime);
+ }
+
+ /**
+ * LocalTime转str
+ *
+ * @param format 格式
+ * @param localTime 时间
+ * @return 转换后的时间字符串
+ */
+ public static String localTime2Str(String format, LocalTime localTime) {
+ if (null == localTime) {
+ return null;
+ }
+ DateTimeFormatter formatter = CharSequenceUtil.isBlank(format) ?
+ TIME_FORMATTER : DateTimeFormatter.ofPattern(format);
+ return localTime.format(formatter);
+ }
+
+ /**
+ * 字符串转LocalDate类型
+ *
+ * @param dateStr 日期字符串
+ * @return LocalDate
+ */
+ public static LocalDate parseLocalDate(Object dateStr) {
+ if (ObjectUtils.isNull(dateStr)) {
+ return null;
+ }
+ if (dateStr instanceof Double) {
+ return date2LocalDate(parseDate(dateStr));
+ }
+ return LocalDate.parse(dateStr.toString(), DATE_FORMATTER);
+ }
+
+ /**
+ * Date转LocalDate
+ *
+ * @param date 日期
+ * @return LocalDate
+ */
+ public static LocalDate date2LocalDate(Date date) {
+ if (null == date) {
+ return null;
+ }
+ return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ }
+
+ /**
+ * LocalDate转Str
+ *
+ * @param localDate 日期
+ * @return 转换后的字符串
+ */
+ public static String localDate2Str(LocalDate localDate) {
+ return localDate2Str(null, localDate);
+ }
+
+ /**
+ * LocalDate转Str
+ *
+ * @param format 格式
+ * @param localDate 日期
+ * @return 转换后的字符串
+ */
+ public static String localDate2Str(String format, LocalDate localDate) {
+ if (null == localDate) {
+ return null;
+ }
+ DateTimeFormatter formatter = CharSequenceUtil.isBlank(format) ?
+ DATE_FORMATTER : DateTimeFormatter.ofPattern(format);
+ return localDate.format(formatter);
+ }
+
+ /**
+ * 字符串转LocalDateTime类型
+ *
+ * @param dateStr 日期字符串
+ * @return LocalDateTime
+ */
+ public static LocalDateTime parseLocalDateTime(Object dateStr) {
+ if (ObjectUtils.isNull(dateStr)) {
+ return null;
+ }
+ if (dateStr instanceof Double) {
+ return date2LocalDateTime(parseDate(dateStr));
+ }
+ return LocalDateTime.parse(dateStr.toString(), DATETIME_FORMATTER);
+ }
+
+ /**
+ * Date转LocalDateTime
+ *
+ * @param date 日期
+ * @return LocalDateTime
+ */
+ public static LocalDateTime date2LocalDateTime(Date date) {
+ if (null == date) {
+ return null;
+ }
+ return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+ }
+
+ /**
+ * LocalDate转Str
+ *
+ * @param localDateTime 时间
+ * @return 转换后的日期字符串
+ */
+ public static String localDateTime2Str(LocalDateTime localDateTime) {
+ return localDateTime2Str(null, localDateTime);
+ }
+
+ /**
+ * LocalDate转Str
+ *
+ * @param format 格式
+ * @param localDateTime 时间
+ * @return 转换后的日期字符串
+ */
+ public static String localDateTime2Str(String format, LocalDateTime localDateTime) {
+ if (null == localDateTime) {
+ return null;
+ }
+ DateTimeFormatter formatter = CharSequenceUtil.isBlank(format) ?
+ DATETIME_FORMATTER : DateTimeFormatter.ofPattern(format);
+ return localDateTime.format(formatter);
+ }
+
+ public static LocalDate convertLocalDate(String source) {
+ source = source.trim();
+ if ("".equals(source)) {
+ return null;
+ }
+ if (source.matches("^\\d{4}-\\d{1,2}$")) {
+ // yyyy-MM
+ return LocalDate.parse(source + "-01", DATE_FORMATTER);
+ } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
+ // yyyy-MM-dd
+ return LocalDate.parse(source, DATE_FORMATTER);
+ } else if (NumberUtil.isLong(source)) {
+ if (source.length() == 10 || source.length() == 13) {
+ source = source.length() == 10 ? source + "000" : source;
+ return Instant.ofEpochMilli(Long.parseLong(source)).atZone(ZoneId.systemDefault()).toLocalDate();
+ }
+ return null;
+ } else {
+ throw new IllegalArgumentException("Invalid datetime value '" + source + "'");
+ }
+ }
+
+ public static LocalDateTime convertLocalDateTime(String source) {
+ source = source.trim();
+ if ("".equals(source)) {
+ return null;
+ }
+ if (source.matches("^\\d{4}-\\d{1,2}$")) {
+ // yyyy-MM
+ return LocalDateTime.parse(source + "-01 00:00:00", DATETIME_FORMATTER);
+ } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
+ // yyyy-MM-dd
+ return LocalDateTime.parse(source + " 00:00:00", DATETIME_FORMATTER);
+ } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}$")) {
+ // yyyy-MM-dd HH:mm
+ return LocalDateTime.parse(source + ":00", DATETIME_FORMATTER);
+ } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
+ // yyyy-MM-dd HH:mm:ss
+ return LocalDateTime.parse(source, DATETIME_FORMATTER);
+ } else if (NumberUtil.isLong(source)) {
+ if (source.length() == 10 || source.length() == 13) {
+ source = source.length() == 10 ? source + "000" : source;
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(source)), ZoneId.systemDefault());
+ }
+ return null;
+ } else {
+ throw new IllegalArgumentException("Invalid datetime value '" + source + "'");
+ }
+ }
+
+ /**
+ * 传入 字符串格式 日期,获得周几
+ *
+ * @param date 字符串类型日期
+ * @return 周几: 1-周一;2-周二;3-周三;4-周四;5-周五;6-周六;7-周日
+ */
+ public static Integer whichDayToWeekByStr(String date) {
+ try {
+ DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ Date enteringDate = sdf.parse(date);
+
+ // 创建Calendar类实例
+ Calendar instance = Calendar.getInstance();
+ // 获取传入的日期是周几
+ instance.setTime(enteringDate);
+ int i = instance.get(Calendar.DAY_OF_WEEK);
+ // 获取周日
+ int sunday = Calendar.SUNDAY;
+ // 如果是周日 特殊处理
+ if (i == sunday) {
+ return 7;
+ } else {
+ return i - 1;
+ }
+ } catch (ParseException e) {
+ throw new ApiException(ApiCode.DATE_ERROR);
+ }
+ }
+
+ /**
+ * 传入 LocalDateTime 格式日期 获得周几
+ *
+ * @param enteringDate LocalDateTime格式日期
+ * @return 周几: 1-周一;2-周二;3-周三;4-周四;5-周五;6-周六;7-周日
+ */
+ public static Integer whichDayToWeekByLDT(LocalDate enteringDate) {
+ ZoneId zoneId = ZoneId.systemDefault();
+ ZonedDateTime zdt = enteringDate.atStartOfDay().atZone(zoneId);
+ Date date = Date.from(zdt.toInstant());
+
+ // 创建Calendar类实例
+ Calendar instance = Calendar.getInstance();
+ // 获取传入的日期是周几
+ instance.setTime(date);
+ int i = instance.get(Calendar.DAY_OF_WEEK);
+ // 获取周日
+ int sunday = Calendar.SUNDAY;
+ // 如果是周日 特殊处理
+ if (i == sunday) {
+ return 7;
+ } else {
+ return i - 1;
+ }
+ }
+
+ /**
+ * 传入 LocalDateTime 格式日期 和 n 天 获取n天后的日期
+ *
+ * @param enteringDate 开始时间 LocalDateTime格式
+ * @param day n为正数 为后n天; n为负数 为前n天
+ * @return n天后的日期 LocalDateTime 格式
+ */
+ public static LocalDate getBeforeNumDay(LocalDate enteringDate, int day) {
+ ZoneId zoneId = ZoneId.systemDefault();
+ ZonedDateTime zdt = enteringDate.atStartOfDay().atZone(zoneId);
+ Date date = Date.from(zdt.toInstant());
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.add(Calendar.DATE, day);
+ Date time = calendar.getTime();
+
+ Instant instant = time.toInstant();
+ return instant.atZone(zoneId).toLocalDate();
+ }
+
+ /**
+ * 今天剩余时间 单位(秒)
+ *
+ * @return {@link Long }
+ * @author yxx
+ */
+ public static Long theRestOfTheDaySecond(){
+ // 获取当前日期和时间
+ LocalDateTime now = LocalDateTime.now();
+
+ // 获取今晚的十二点整时间
+ LocalDateTime midnight = now.toLocalDate().atTime(LocalTime.MAX);
+
+ // 计算当前时间距离今晚十二点整的秒数
+ Duration duration = Duration.between(now, midnight);
+ return duration.getSeconds();
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/RepeatedlyRequestWrapper.java b/common/common-core/src/main/java/com/yxx/common/utils/RepeatedlyRequestWrapper.java
new file mode 100644
index 0000000..d9abfb3
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/RepeatedlyRequestWrapper.java
@@ -0,0 +1,68 @@
+package com.yxx.common.utils;
+
+import cn.hutool.core.io.IoUtil;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 构建可重复读取inputStream的request
+ *
+ * @author yxx
+ * @since 2022/4/13 17:29
+ */
+public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
+ private final byte[] body;
+
+ public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
+ super(request);
+ request.setCharacterEncoding("UTF-8");
+ response.setCharacterEncoding("UTF-8");
+
+ body = IoUtil.readUtf8(request.getInputStream()).getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public BufferedReader getReader() {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+ return new ServletInputStream() {
+ @Override
+ public int read() {
+ return bais.read();
+ }
+
+ @Override
+ public int available() {
+ return body.length;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+ };
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/RequestWrapper.java b/common/common-core/src/main/java/com/yxx/common/utils/RequestWrapper.java
new file mode 100644
index 0000000..acb8c38
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/RequestWrapper.java
@@ -0,0 +1,131 @@
+package com.yxx.common.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import java.io.*;
+import java.nio.charset.Charset;
+
+/**
+ * 包装HttpServletRequest,目的是让其输入流可重复读
+ *
+ * @author jing.li
+ * @since 1.2.5
+ */
+public class RequestWrapper extends HttpServletRequestWrapper {
+
+ private static final Logger log = LoggerFactory.getLogger(RequestWrapper.class);
+
+ /**
+ * 存储body数据的容器
+ */
+ private final byte[] body;
+
+ /**
+ * Constructs a request object wrapping the given request.
+ *
+ * @param request The request to wrap
+ * @throws IllegalArgumentException if the request is null
+ */
+ public RequestWrapper(HttpServletRequest request) {
+ super(request);
+ String bodyStr = getBodyString(request);
+ body = bodyStr.getBytes(Charset.defaultCharset());
+ }
+
+
+ /**
+ * 获取请求Body
+ *
+ * @param request request
+ * @return String
+ */
+ public String getBodyString(final ServletRequest request) {
+ try {
+ return inputStream2String(request.getInputStream());
+ } catch (IOException e) {
+ log.error("", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 获取请求Body
+ *
+ * @return String
+ */
+ public String getBodyString() {
+ final InputStream inputStream = new ByteArrayInputStream(body);
+
+ return inputStream2String(inputStream);
+ }
+
+ /**
+ * 将inputStream里的数据读取出来并转换成字符串
+ *
+ * @param inputStream inputStream
+ * @return String
+ */
+ private String inputStream2String(InputStream inputStream) {
+ StringBuilder sb = new StringBuilder();
+ BufferedReader reader = null;
+
+ try {
+ reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ } catch (IOException e) {
+ log.error("", e);
+ throw new RuntimeException(e);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ log.error("", e);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+
+ final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
+
+ return new ServletInputStream() {
+ @Override
+ public int read() throws IOException {
+ return inputStream.read();
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ }
+ };
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ServletUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/ServletUtils.java
new file mode 100644
index 0000000..44afe7c
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/ServletUtils.java
@@ -0,0 +1,70 @@
+package com.yxx.common.utils;
+
+import cn.hutool.core.map.MapUtil;
+import com.yxx.common.utils.jackson.JacksonUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Spring Application 工具类
+ *
+ * @author yxx
+ * @since 2022/4/13 11:45
+ */
+public class ServletUtils {
+
+ public static ServletRequestAttributes getRequestAttributes() {
+ RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+ return (ServletRequestAttributes) attributes;
+ }
+
+ /**
+ * 获取request
+ */
+ public static HttpServletRequest getRequest() {
+ return getRequestAttributes().getRequest();
+ }
+
+ /**
+ * 获取请求参数
+ *
+ * @param request HttpServletRequest
+ * @return 请求参数
+ */
+ public static String getRequestParms(HttpServletRequest request) {
+ String params = "";
+ if (isJsonRequest(request)) {
+ params = new RequestWrapper(request).getBodyString();
+ } else {
+ Map parameterMap = request.getParameterMap();
+ if (MapUtil.isNotEmpty(parameterMap)) {
+ params = JacksonUtil.toJson(parameterMap);
+ }
+ }
+ return params;
+ }
+
+ /**
+ * 判断本次请求的数据类型是否为json
+ *
+ * @param request request
+ * @return boolean
+ */
+ public static boolean isJsonRequest(HttpServletRequest request) {
+ String contentType = request.getContentType();
+ if (contentType != null) {
+ return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)
+ && request instanceof RepeatedlyRequestWrapper
+ && Arrays.asList(HttpMethod.POST.name(), HttpMethod.PUT.name()).contains(request.getMethod());
+ }
+ return false;
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/SnowflakeConfig.java b/common/common-core/src/main/java/com/yxx/common/utils/SnowflakeConfig.java
new file mode 100644
index 0000000..bab13f7
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/SnowflakeConfig.java
@@ -0,0 +1,44 @@
+package com.yxx.common.utils;
+
+import cn.hutool.core.lang.Snowflake;
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.core.util.IdUtil;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import jakarta.annotation.PostConstruct;
+
+/**
+ * @author yxx
+ */
+@Component
+@Slf4j
+public class SnowflakeConfig {
+ /**
+ * 终端ID
+ */
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ private long workerId;
+
+ /**
+ * 数据中心ID
+ */
+ private final long datacenterId = 1;
+ private final Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId);
+
+ @PostConstruct
+ public void init() {
+ workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
+ log.info("当前机器的workId:{}", workerId);
+ }
+
+ public synchronized long snowflakeId() {
+ return snowflake.nextId();
+ }
+
+ public synchronized long snowflakeId(long workerId, long datacenterId) {
+ Snowflake snowflake = IdUtil.getSnowflake(workerId, datacenterId);
+ return snowflake.nextId();
+ }
+}
+
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/TreeUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/TreeUtil.java
new file mode 100644
index 0000000..23d5d8a
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/TreeUtil.java
@@ -0,0 +1,140 @@
+package com.yxx.common.utils;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author yxx
+ * @apiNote 通用树形工具类
+ * @since 2023-05-18 14:22
+ */
+public class TreeUtil {
+
+
+ /***
+ * 构建树
+ *
+ * @param listData 需要构建的结果集
+ * @param parentKeyFunction 父节点id
+ * @param keyFunction 主键
+ * @param setChildrenFunction 子集
+ * @param rootParentValue 父节点的值 0就填0 null就填null
+ * @return java.util.List
+ */
+ public static List buildTree(List listData, Function parentKeyFunction,
+ Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue) {
+ return buildTree(listData, parentKeyFunction, keyFunction, setChildrenFunction, rootParentValue, null, null);
+ }
+
+ /***
+ * 构建树,并且对结果集做升序处理
+ *
+ * @param listData 需要构建的结构集
+ * @param parentKeyFunction 父节点
+ * @param keyFunction 主键
+ * @param setChildrenFunction 子集
+ * @param rootParentValue 父节点的值
+ * @param sortFunction 排序字段
+ * @return java.util.List
+ */
+ public static List buildAscTree(List listData, Function parentKeyFunction,
+ Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue,
+ Function sortFunction) {
+ List resultList =
+ buildTree(listData, parentKeyFunction, keyFunction, setChildrenFunction, rootParentValue, sortFunction, 0);
+
+ return sortList(resultList, sortFunction, 0);
+ }
+
+ /***
+ * 构建树,并且对结果集做降序处理
+ *
+ * @param listData 需要构建的结构集
+ * @param parentKeyFunction 父节点
+ * @param keyFunction 主键
+ * @param setChildrenFunction 子集
+ * @param rootParentValue 父节点的值
+ * @param sortFunction 排序字段
+ * @return java.util.List
+ */
+ public static List buildDescTree(List listData, Function parentKeyFunction,
+ Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue,
+ Function sortFunction) {
+ List resultList =
+ buildTree(listData, parentKeyFunction, keyFunction, setChildrenFunction, rootParentValue, sortFunction, 1);
+
+ return sortList(resultList, sortFunction, 1);
+ }
+
+ private static List buildTree(List listData, Function parentKeyFunction,
+ Function keyFunction, BiConsumer> setChildrenFunction, U rootParentValue,
+ Function sortFunction, Integer sortedFlag) {
+ // 筛选出根节点
+ Map rootKeyMap = new HashMap<>();
+ // 所有的节点
+ Map allKeyMap = new HashMap<>();
+ // 存id:List
+ Map> keyParentKeyMap = new HashMap<>();
+
+ for (T t : listData) {
+ Comparable key = keyFunction.apply(t);
+ Comparable parentKey = parentKeyFunction.apply(t);
+ // 如果根节点标识为null,值也为null,表示为根节点
+ if (rootParentValue == null && parentKeyFunction.apply(t) == null) {
+ rootKeyMap.put(key, t);
+ }
+ // 根节点标识有值,值相同表示为根节点
+ if (rootParentValue != null && parentKeyFunction.apply(t).compareTo(rootParentValue) == 0) {
+ rootKeyMap.put(key, t);
+ }
+ allKeyMap.put(key, t);
+ if (parentKey != null) {
+ List children = keyParentKeyMap.getOrDefault(parentKey, new ArrayList<>());
+ children.add(key);
+ keyParentKeyMap.put(parentKey, children);
+ }
+ }
+ List returnList = new ArrayList<>();
+ // 封装根节点数据
+ for (Comparable comparable : rootKeyMap.keySet()) {
+ setChildren(comparable, returnList, allKeyMap, keyParentKeyMap, setChildrenFunction, sortFunction,
+ sortedFlag);
+ }
+
+ return returnList;
+ }
+
+ private static void setChildren(Comparable comparable, List childrenList, Map childrenKeyMap,
+ Map> keyParentKeyMap, BiConsumer> setChildrenFunction,
+ Function sortFunction, Integer sortedFlag) {
+ T t = childrenKeyMap.get(comparable);
+ if (keyParentKeyMap.containsKey(comparable)) {
+ List subChildrenList = new ArrayList<>();
+ List childrenComparable = keyParentKeyMap.get(comparable);
+ for (Comparable c : childrenComparable) {
+ setChildren(c, subChildrenList, childrenKeyMap, keyParentKeyMap, setChildrenFunction, sortFunction,
+ sortedFlag);
+ }
+ subChildrenList = sortList(subChildrenList, sortFunction, sortedFlag);
+ setChildrenFunction.accept(t, subChildrenList);
+
+ }
+ childrenList.add(t);
+
+ }
+
+ private static List sortList(List list, Function sortFunction,
+ Integer sortedFlag) {
+ if (sortFunction != null) {
+ if (sortedFlag == 1) {
+ return (List) list.stream().sorted(Comparator.comparing(sortFunction).reversed())
+ .collect(Collectors.toList());
+ } else {
+ return (List) list.stream().sorted(Comparator.comparing(sortFunction)).collect(Collectors.toList());
+ }
+ }
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/agent/UserAgentUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/agent/UserAgentUtil.java
new file mode 100644
index 0000000..e2ff7d4
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/agent/UserAgentUtil.java
@@ -0,0 +1,51 @@
+package com.yxx.common.utils.agent;
+
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.http.useragent.UserAgent;
+import com.yxx.common.utils.jackson.JacksonUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * agen工具类
+ *
+ * @author yxx
+ * @classname UserAgentUtil
+ * @since 2023-08-01 18:27
+ */
+@Slf4j
+public class UserAgentUtil {
+ /**
+ * 获得代理
+ *
+ * @param userAgent 用户代理
+ * @return {@link String }
+ * @author yxx
+ */
+ public static String getAgent(String userAgent) {
+ if (CharSequenceUtil.isBlank(userAgent)) {
+ return "未知";
+ }
+ UserAgent ua = cn.hutool.http.useragent.UserAgentUtil.parse(userAgent);
+ String json = JacksonUtil.toJson(ua);
+ log.info("ua json = " + json);
+ String platform = ua.getPlatform().toString();//Windows
+ log.info("系统:{} ", platform);
+ String os = ua.getOs().toString();//Windows 7
+ log.info("系统版本:{} ", os);
+ String browser = ua.getBrowser().toString();//Chrome
+ log.info("浏览器:{} ", browser);
+ String version = ua.getVersion();//14.0.835.163
+ log.info("版本:{} ", version);
+ String engine = ua.getEngine().toString();//Webkit
+ log.info("浏览器引擎:{} ", engine);
+ String engineVersion = ua.getEngineVersion();//535.1
+ log.info("浏览器引擎版本:{} ", engineVersion);
+ boolean mobile = ua.isMobile();
+ log.info("是否是移动端:{} ", mobile);
+ if (CharSequenceUtil.isBlank(platform) || "Unknown".equals(platform) || "null".equals(platform)) {
+ return userAgent;
+ } else {
+ return platform + "-" + os + "-" + browser + "-" + version;
+ }
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginAdminUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginAdminUtils.java
new file mode 100644
index 0000000..5b42946
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginAdminUtils.java
@@ -0,0 +1,81 @@
+package com.yxx.common.utils.auth;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.yxx.common.constant.Constant;
+import com.yxx.common.core.model.LoginUser;
+import com.yxx.common.utils.satoken.StpAdminUtil;
+
+import java.util.List;
+
+/**
+ * 登录鉴权工具
+ *
+ * @author yxx
+ * @since 2022/4/13 14:22
+ */
+public class LoginAdminUtils {
+
+ /**
+ * 登录系统
+ *
+ * @param loginUser 登录用户信息
+ */
+ public static void login(LoginUser loginUser) {
+ StpAdminUtil.login(loginUser.getLoginCode());
+ setLoginUser(loginUser);
+ }
+
+ /**
+ * 登录系统 并指定登录设备类型
+ *
+ * @param device 登录设备类型
+ * @param loginUser 登录用户信息
+ */
+ public static void login(LoginUser loginUser, String device) {
+ StpAdminUtil.login(loginUser.getLoginCode(), device);
+ setLoginUser(loginUser);
+ loginUser.setToken(StpAdminUtil.getTokenValue());
+ }
+
+ /**
+ * 设置用户数据
+ */
+ public static void setLoginUser(LoginUser loginUser) {
+ StpAdminUtil.getTokenSession().set(Constant.LOGIN_USER_KEY, loginUser);
+ }
+
+ /**
+ * 获取用户
+ **/
+ public static LoginUser getLoginUser() {
+ return (LoginUser) StpAdminUtil.getTokenSession().get(Constant.LOGIN_USER_KEY);
+ }
+
+ /**
+ * 获取用户id
+ */
+ public static Long getUserId() {
+ LoginUser loginUser = getLoginUser();
+ if (ObjectUtil.isNull(loginUser)) {
+ return null;
+ }
+ return loginUser.getId();
+ }
+
+ /**
+ * 获取用户账户
+ **/
+ public static String getLoginCode() {
+ return getLoginUser().getLoginCode();
+ }
+
+ /**
+ * 获取用户角色
+ *
+ * @return 用户角色
+ */
+ public static List getRoleCode() {
+ return getLoginUser().getRolePermission();
+ }
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginUtils.java
new file mode 100644
index 0000000..c5cadf8
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/auth/LoginUtils.java
@@ -0,0 +1,81 @@
+package com.yxx.common.utils.auth;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.yxx.common.constant.Constant;
+import com.yxx.common.core.model.LoginUser;
+
+import java.util.List;
+
+/**
+ * 登录鉴权工具
+ *
+ * @author yxx
+ * @since 2022/4/13 14:22
+ */
+public class LoginUtils {
+
+ /**
+ * 登录系统
+ *
+ * @param loginUser 登录用户信息
+ */
+ public static void login(LoginUser loginUser) {
+ StpUtil.login(loginUser.getLoginCode());
+ setLoginUser(loginUser);
+ }
+
+ /**
+ * 登录系统 并指定登录设备类型
+ *
+ * @param device 登录设备类型
+ * @param loginUser 登录用户信息
+ */
+ public static void login(LoginUser loginUser, String device) {
+ StpUtil.login(loginUser.getLoginCode(), device);
+ setLoginUser(loginUser);
+ loginUser.setToken(StpUtil.getTokenValue());
+ }
+
+ /**
+ * 设置用户数据
+ */
+ public static void setLoginUser(LoginUser loginUser) {
+ StpUtil.getTokenSession().set(Constant.LOGIN_USER_KEY, loginUser);
+ }
+
+ /**
+ * 获取用户
+ **/
+ public static LoginUser getLoginUser() {
+ return (LoginUser) StpUtil.getTokenSession().get(Constant.LOGIN_USER_KEY);
+ }
+
+ /**
+ * 获取用户id
+ */
+ public static Long getUserId() {
+ LoginUser loginUser = getLoginUser();
+ if (ObjectUtil.isNull(loginUser)) {
+ return null;
+ }
+ return loginUser.getId();
+ }
+
+ /**
+ * 获取用户账户
+ **/
+ public static String getLoginCode() {
+ return getLoginUser().getLoginCode();
+ }
+
+ /**
+ * 获取用户角色
+ *
+ * @return 用户角色
+ */
+ public static List getRoleCode() {
+ return getLoginUser().getRolePermission();
+ }
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/email/MailUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/email/MailUtils.java
new file mode 100644
index 0000000..11dde45
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/email/MailUtils.java
@@ -0,0 +1,84 @@
+package com.yxx.common.utils.email;
+
+import com.yxx.common.enums.ApiCode;
+import com.yxx.common.exceptions.ApiException;
+import com.yxx.common.properties.MailProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Component;
+
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.MimeMessage;
+import java.io.UnsupportedEncodingException;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MailUtils {
+
+ private final JavaMailSender mailSender;
+
+ private final MailProperties mailProperties;
+
+
+ /**
+ * 发送邮件 单个接收人
+ *
+ * @param to 接收人邮箱
+ * @param subject 邮件主题
+ * @param text 邮件正文
+ * @param html true:发送html格式邮件;false:发送普通邮件
+ * @author yxx
+ */
+ public void baseSendMail(String to, String subject, String text, boolean html) {
+ MimeMessage message = mailSender.createMimeMessage();
+ try {
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
+ helper.setFrom(mailProperties.getFrom(), mailProperties.getFromName());
+ helper.setTo(to);
+ helper.setSubject(subject);
+ if (html) {
+ helper.setText(text, true);
+ } else {
+ helper.setText(text);
+ }
+ mailSender.send(message);
+ log.info("邮件已经发送");
+ } catch (MessagingException | UnsupportedEncodingException e) {
+ log.error("发送邮件时发生异常!", e);
+ throw new ApiException(ApiCode.MAIL_ERROR);
+ }
+ }
+
+ /**
+ * 发送邮件,多个接收人
+ *
+ * @param to 接收人邮箱
+ * @param subject 邮件主题
+ * @param text 邮件正文
+ * @param html true:发送html格式邮件;false:发送普通邮件
+ * @author yxx
+ */
+ public void baseSendMail(String[] to, String subject, String text, boolean html) {
+ MimeMessage message = mailSender.createMimeMessage();
+ try {
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
+ helper.setFrom(mailProperties.getFrom(), mailProperties.getFromName());
+ helper.setTo(to);
+ helper.setSubject(subject);
+ if (html) {
+ helper.setText(text, true);
+ } else {
+ helper.setText(text);
+ }
+ mailSender.send(message);
+ log.info("邮件已经发送");
+ } catch (MessagingException | UnsupportedEncodingException e) {
+ log.error("发送邮件时发生异常!", e);
+ throw new ApiException(ApiCode.MAIL_ERROR);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/encryptor/DESUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/DESUtil.java
new file mode 100644
index 0000000..58f4f26
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/DESUtil.java
@@ -0,0 +1,131 @@
+package com.yxx.common.utils.encryptor;
+
+import com.yxx.common.enums.ApiCode;
+import com.yxx.common.exceptions.ApiException;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Base64;
+
+/**
+ * Description:
+ *
+ * @author yxx
+ * @since 2022-11-25
+ */
+public class DESUtil implements IEncryptor {
+ /**
+ * 偏移变量,固定占8位字节
+ */
+ private static final String IV_PARAMETER = "12345678";
+ /**
+ * 加密算法
+ */
+ private static final String ALGORITHM = "DES";
+ /**
+ * 加密/解密算法-工作模式-填充模式
+ */
+ private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding";
+ /**
+ * 默认编码
+ */
+ private static final String CHARSET = String.valueOf(StandardCharsets.UTF_8);
+
+ /**
+ * 生成key
+ *
+ * @param password
+ * @return
+ * @throws Exception
+ */
+ private static Key generateKey(String password) {
+ try {
+ DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET));
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
+ return keyFactory.generateSecret(dks);
+ } catch (InvalidKeySpecException | InvalidKeyException | UnsupportedEncodingException |
+ NoSuchAlgorithmException e) {
+ throw new ApiException(ApiCode.KEY_ERROR);
+ }
+ }
+
+
+ /**
+ * DES加密字符串
+ *
+ * @param key 加密密码,长度不能够小于8位
+ * @param obj 待加密字符串
+ * @return 加密后内容
+ */
+ @Override
+ public String encrypt(Object obj, String key) {
+ String data = obj.toString();
+ if (key == null){
+ return null;
+ }
+ if ( key.length() < 8) {
+ throw new ApiException(ApiCode.KEY_LENGTH_ERROR);
+ }
+ try {
+ Key secretKey = generateKey(key);
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
+
+ // 加密
+ byte[] bytes = cipher.doFinal(data.getBytes(CHARSET));
+
+ // base64编码 JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder
+ byte[] encode = Base64.getEncoder().encode(bytes);
+
+ return new String(encode);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return data;
+ }
+ }
+
+ /**
+ * DES解密字符串
+ *
+ * @param key 解密密码,长度不能够小于8位
+ * @param obj 待解密字符串
+ * @return 解密后内容
+ */
+ @Override
+ public String decrypt(Object obj, String key) {
+ String data = obj.toString();
+ if (key == null || key.length() < 8) {
+ throw new ApiException(ApiCode.KEY_LENGTH_ERROR);
+ }
+ if (data == null)
+ return null;
+ try {
+ Key secretKey = generateKey(key);
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
+
+ // base64解码
+ byte[] decode = Base64.getDecoder().decode(data.getBytes(CHARSET));
+
+ // 解密
+ byte[] decrypt = cipher.doFinal(decode);
+
+ return new String(decrypt, CHARSET);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return data;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/encryptor/IEncryptor.java b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/IEncryptor.java
new file mode 100644
index 0000000..4873a90
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/encryptor/IEncryptor.java
@@ -0,0 +1,12 @@
+package com.yxx.common.utils.encryptor;
+
+/**
+ * @author yxx
+ */
+public interface IEncryptor {
+
+ String encrypt(Object val2bEncrypted, String key);
+
+ String decrypt(Object val2bDecrypted, String key);
+
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/enums/EnumUtils.java b/common/common-core/src/main/java/com/yxx/common/utils/enums/EnumUtils.java
new file mode 100644
index 0000000..74faf38
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/enums/EnumUtils.java
@@ -0,0 +1,35 @@
+package com.yxx.common.utils.enums;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@Slf4j
+public class EnumUtils {
+
+ /**
+ * 判断值是否在枚举类的code中
+ * @param clzz 枚举类
+ * @param code 值
+ * @return true-在;false-不在
+ */
+ public static boolean isInclude(Class> clzz, String code) {
+ boolean include = false;
+ try {
+ if (clzz.isEnum()) {
+ Object[] enumConstants = clzz.getEnumConstants();
+ Method getCode = clzz.getMethod("getCode");
+ for (Object enumConstant : enumConstants) {
+ if (getCode.invoke(enumConstant).equals(code)) {
+ include = true;
+ break;
+ }
+ }
+ }
+ } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
+ log.error(String.valueOf(e));
+ }
+ return include;
+ }
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ip/AddressUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/ip/AddressUtil.java
new file mode 100644
index 0000000..6b4a6e5
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/ip/AddressUtil.java
@@ -0,0 +1,172 @@
+package com.yxx.common.utils.ip;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+import org.springframework.stereotype.Component;
+
+import java.io.FileInputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 解析ip属地
+ *
+ * @author yxx
+ * @classname AddressUtil
+ * @since 2023/07/28
+ */
+@Slf4j
+@Component
+public class AddressUtil {
+
+ /**
+ * 根据IP地址查询登录来源 线上用
+ *
+ * @param ip ip
+ * @return {@link String }
+ * @author yxx
+ */
+ public static String getCityInfo(String ip) {
+ if (isIPv6Address(ip)) {
+ return null;
+ }
+ if (!isValidIPv4(ip)) {
+ return null;
+ }
+
+ try {
+ String path = System.getProperty("user.dir");
+ FileInputStream fileInputStream = new FileInputStream(path + "/ip2region.xdb");
+ Searcher searcher = Searcher.newWithBuffer(IoUtil.readBytes(fileInputStream));
+ return searcher.search(ip);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 得到ip归属地
+ *
+ * @return {@link String }
+ * @author yxx
+ */
+ public static String getIpHomePlace(String requestIp, Integer num) {
+ log.info("ip:{}", requestIp);
+ String cityInfo = AddressUtil.getCityInfo(requestIp);
+ log.info("地理位置信息:{}", cityInfo);
+ String ipHomePlace = extractLocationInfo(cityInfo, num);
+ log.info("解析后地址为:{}", ipHomePlace);
+ return ipHomePlace;
+ }
+
+ /**
+ * 提取位置信息
+ *
+ * @param ipLocation ip位置
+ * @param dataType 数据类型
+ * @return {@link String }
+ * @author yxx
+ */
+ public static String extractLocationInfo(String ipLocation, int dataType) {
+ if (CharSequenceUtil.isBlank(ipLocation)) {
+ return "未知";
+ }
+ // 按照“|”符号将IP归属地拆分成数组
+ String[] locationParts = ipLocation.split("\\|");
+
+ // 获取省份和市区信息
+ String province = locationParts[2];
+ String city = locationParts[3];
+
+ // 检查是否为内网IP
+ if ("内网IP".equals(province) || "内网IP".equals(city)) {
+ return "内网";
+ }
+
+ // 获取省份数据
+ String result = "";
+ if (dataType == 2) {
+ result = removeSuffix(province);
+ }
+ // 获取市区数据,如果市区为空,则返回省份数据;如果省份数据也为空,则返回“未知”
+ else if (dataType == 3) {
+ result = removeSuffix(city);
+ if ("0".equals(result) || "".equals(result)) {
+ result = removeSuffix(province);
+ if ("0".equals(result) || "".equals(result)) {
+ result = "未知";
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 辅助方法:去除字符串末尾的特定后缀
+ *
+ * @param str str
+ * @return {@link String }
+ * @author yxx
+ */
+ private static String removeSuffix(String str) {
+ if (str.endsWith("省") || str.endsWith("市")) {
+ return str.substring(0, str.length() - 1);
+ }
+ return str;
+ }
+
+ /**
+ * 是有效ipv4
+ *
+ * @param ip ip地址
+ * @return boolean
+ * @author yxx
+ */
+ public static boolean isValidIPv4(String ip) {
+ if (ip == null || ip.isEmpty()) {
+ return false;
+ }
+
+ // 切分IP地址的四个部分
+ String[] parts = ip.split("\\.");
+ if (parts.length != 4) {
+ return false;
+ }
+
+ // 检查每个部分是否是有效的数字(0-255之间)
+ for (String part : parts) {
+ try {
+ int num = Integer.parseInt(part);
+ if (num < 0 || num > 255) {
+ return false;
+ }
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * ipv6地址是
+ *
+ * @param ipAddress ip地址
+ * @return boolean
+ * @author yxx
+ */
+ public static boolean isIPv6Address(String ipAddress) {
+ if (ipAddress == null || ipAddress.isEmpty()) {
+ return false;
+ }
+
+ try {
+ InetAddress inetAddress = InetAddress.getByName(ipAddress);
+ return inetAddress instanceof java.net.Inet6Address;
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/ip/IpUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/ip/IpUtil.java
new file mode 100644
index 0000000..1749a2b
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/ip/IpUtil.java
@@ -0,0 +1,78 @@
+package com.yxx.common.utils.ip;
+
+import com.yxx.common.constant.SeparatorConstant;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * IP工具类
+ *
+ * @author yxx
+ * @since 2022/07/19
+ */
+@Slf4j
+public class IpUtil {
+
+ private static final Integer MAX_LENGTH = 15;
+ private static final String UNKNOWN = "unknown";
+ private static final String IPV6_LOCAL = "0:0:0:0:0:0:0:1";
+
+ private IpUtil() {
+ throw new AssertionError();
+ }
+
+ /**
+ * 获取请求用户的IP地址
+ *
+ * @return IP地址
+ */
+ public static String getRequestIp() {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
+ return getRequestIp(request);
+ }
+
+ /**
+ * 获取请求用户的IP地址
+ *
+ * @param request HttpServletRequest
+ * @return IP地址
+ */
+ public static String getRequestIp(HttpServletRequest request) {
+ String ip = request.getHeader("x-forwarded-for");
+ if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+ ip = request.getHeader("Proxy-Client-IP");
+ }
+ if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+ ip = request.getHeader("WL-Proxy-Client-IP");
+ }
+ if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+ ip = request.getRemoteAddr();
+ }
+
+ if (IPV6_LOCAL.equals(ip)) {
+ ip = getLocalhostIp();
+ }
+
+ if (ip != null && ip.length() > MAX_LENGTH) {
+ if (ip.indexOf(SeparatorConstant.COMMA) > 0) {
+ ip = ip.substring(0, ip.indexOf(SeparatorConstant.COMMA));
+ }
+ }
+ return ip;
+ }
+
+ public static String getLocalhostIp() {
+ try {
+ return InetAddress.getLocalHost().getHostAddress();
+ } catch (UnknownHostException ignored) {
+ }
+ return null;
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/jackson/JacksonUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/jackson/JacksonUtil.java
new file mode 100644
index 0000000..cfec4e3
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/jackson/JacksonUtil.java
@@ -0,0 +1,206 @@
+package com.yxx.common.utils.jackson;
+
+import cn.hutool.core.date.DatePattern;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+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.*;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.DateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import com.yxx.common.utils.DateUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Jackson工具类
+ *
+ * @author yxx
+ * @since 2022/7/15 16:54
+ */
+@Slf4j
+@SuppressWarnings("unused")
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public abstract class JacksonUtil {
+
+ private static final ObjectMapper OBJECT_MAPPER;
+
+ static {
+ OBJECT_MAPPER = initObjectMapper(new ObjectMapper());
+ }
+
+ /**
+ * 转换Json
+ *
+ * @param object 需要转换的对象
+ * @return String
+ */
+ public static String toJson(Object object) {
+ if (Boolean.TRUE.equals(isCharSequence(object))) {
+ return (String) object;
+ }
+ try {
+ return getObjectMapper().writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Json转换为对象 转换失败返回null
+ *
+ * @param json Json字符串
+ * @return Object
+ */
+ public static Object parse(String json) {
+ Object object = null;
+ try {
+ object = getObjectMapper().readValue(json, Object.class);
+ } catch (Exception ignored) {
+ log.error("json转换对象异常");
+ }
+ return object;
+ }
+
+ /**
+ * Json转换为对象 转换失败返回null
+ *
+ * @param json Json字符串
+ * @param clazz 转换的类型
+ * @return 转换后的对象
+ */
+ public static T readValue(String json, Class clazz) {
+ T t = null;
+ try {
+ t = getObjectMapper().readValue(json, clazz);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return t;
+ }
+
+ /**
+ * Json转换为对象 转换失败返回null
+ *
+ * @param json json字符串
+ * @param valueTypeRef TypeReference
+ * @return 对象
+ */
+ public static T readValue(String json, TypeReference valueTypeRef) {
+ T t = null;
+ try {
+ t = getObjectMapper().readValue(json, valueTypeRef);
+ } catch (Exception e) {
+ log.error("JacksonUtil readValue", e);
+ }
+ return t;
+ }
+
+ /**
+ * 获取ObjectMapper
+ *
+ * @return ObjectMapper
+ */
+ public static ObjectMapper getObjectMapper() {
+ return OBJECT_MAPPER;
+ }
+
+ /**
+ *
+ * 是否为CharSequence类型
+ *
+ *
+ * @param object 对象
+ * @return boolean
+ */
+ public static Boolean isCharSequence(Object object) {
+ return !Objects.isNull(object) && CharSequence.class.isAssignableFrom(object.getClass());
+ }
+
+ /**
+ * 初始化 ObjectMapper
+ *
+ * @param objectMapper ObjectMapper
+ * @return ObjectMapper
+ */
+ public static ObjectMapper initObjectMapper(ObjectMapper objectMapper) {
+ if (Objects.isNull(objectMapper)) {
+ objectMapper = new ObjectMapper();
+ }
+ return doInitObjectMapper(objectMapper);
+ }
+
+ /**
+ * 初始化 ObjectMapper 时间方法
+ *
+ * @param objectMapper ObjectMapper
+ * @return ObjectMapper
+ */
+ private static ObjectMapper doInitObjectMapper(ObjectMapper objectMapper) {
+ // 忽略不能转移的字符
+ JsonMapper.builder().configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
+ // 忽略目标对象没有的属性
+ JsonMapper.builder().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ JsonMapper.builder().configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ //忽略transient
+ JsonMapper.builder().configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);
+ objectMapper.getSerializerProvider().setNullValueSerializer(new NullValueSerializer());
+ return registerModule(objectMapper);
+ }
+
+ /**
+ * 注册模块
+ *
+ * @param objectMapper ObjectMapper
+ * @return ObjectMapper
+ */
+ private static ObjectMapper registerModule(ObjectMapper objectMapper) {
+ SimpleModule simpleModule = new SimpleModule();
+ simpleModule.addSerializer(Date.class, new DateSerializer(true, null));
+ simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
+
+ simpleModule.addDeserializer(LocalDateTime.class, new JsonDeserializer<>() {
+ @Override
+ public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ return DateUtils.convertLocalDateTime(p.getText());
+ }
+ });
+ simpleModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
+ simpleModule.addDeserializer(LocalDate.class, new JsonDeserializer<>() {
+ @Override
+ public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ return DateUtils.convertLocalDate(p.getText());
+ }
+ });
+
+ simpleModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
+ simpleModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
+ objectMapper.registerModule(simpleModule);
+ return objectMapper;
+ }
+
+ /**
+ * NULL值处理为空字符串
+ */
+ public static class NullValueSerializer extends JsonSerializer {
+ @Override
+ public void serialize(Object object, JsonGenerator generator, SerializerProvider provider) throws IOException {
+ generator.writeString("");
+ }
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/jackson/SearchDateDeserializer.java b/common/common-core/src/main/java/com/yxx/common/utils/jackson/SearchDateDeserializer.java
new file mode 100644
index 0000000..f3a03ae
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/jackson/SearchDateDeserializer.java
@@ -0,0 +1,62 @@
+package com.yxx.common.utils.jackson;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
+import com.yxx.common.annotation.jackson.SearchDate;
+
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * @author yxx
+ * @since 2022/11/2 10:38
+ */
+public class SearchDateDeserializer extends JsonDeserializer implements ContextualDeserializer {
+
+ private SearchDate searchDate;
+
+ @SuppressWarnings("unused")
+ public SearchDateDeserializer() {
+ super();
+ }
+
+ public SearchDateDeserializer(final SearchDate searchDate) {
+ super();
+ this.searchDate = searchDate;
+ }
+
+ @Override
+ public Date deserialize(JsonParser p, DeserializationContext context) throws IOException {
+ Date date = p.readValueAs(Date.class);
+ if (ObjectUtil.isNull(date)) {
+ return null;
+ }
+ if (searchDate.startDate()) {
+ return DateUtil.beginOfDay(date);
+ }
+ if (searchDate.endDate()) {
+ return DateUtil.endOfDay(date);
+ }
+ return date;
+ }
+
+ @Override
+ public JsonDeserializer> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
+ if (property != null) {
+ if (ObjectUtil.equal(property.getType().getRawClass(), Date.class)) {
+ searchDate = property.getAnnotation(SearchDate.class);
+ if (searchDate != null) {
+ return new SearchDateDeserializer(searchDate);
+ }
+ }
+ return context.findContextualValueDeserializer(property.getType(), property);
+ }
+ return context.findNonContextualValueDeserializer(context.getContextualType());
+ }
+}
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/redis/RedissonCache.java b/common/common-core/src/main/java/com/yxx/common/utils/redis/RedissonCache.java
new file mode 100644
index 0000000..89eb3ea
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/redis/RedissonCache.java
@@ -0,0 +1,279 @@
+package com.yxx.common.utils.redis;
+
+import com.yxx.common.constant.Constant;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.Redisson;
+import org.redisson.api.*;
+import org.redisson.client.codec.Codec;
+import org.redisson.client.codec.StringCodec;
+import org.redisson.codec.JsonJacksonCodec;
+import org.redisson.config.Config;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.context.annotation.Configuration;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Resource;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author yxx
+ * @description redisson工具类
+ */
+@Slf4j
+@Configuration
+public class RedissonCache {
+
+ /**
+ * 默认缓存时间
+ */
+ private static final Long DEFAULT_EXPIRED = 5 * 60L;
+ /**
+ * redis key前缀
+ */
+ private static final String REDIS_KEY_PREFIX = "";
+ /**
+ * redisson client对象
+ */
+ private RedissonClient redisson;
+
+ @Resource
+ private RedisProperties redisProperties;
+
+ /**
+ * 初始化连接
+ *
+ * @throws IOException
+ */
+ @PostConstruct
+ public void init(){
+ // 初始化配置
+ Config config = new Config();
+ // redis url
+ String redisUrl = String.format("redis://%s:%s", redisProperties.getHost() + Constant.EMPTY, redisProperties.getPort() + Constant.EMPTY);
+ // 设置url和密码
+ config.useSingleServer().setAddress(redisUrl).setPassword(redisProperties.getPassword());
+ // 设置库
+ config.useSingleServer().setDatabase(redisProperties.getDatabase());
+ // 设置最小空闲Redis连接量
+ config.useSingleServer().setConnectionMinimumIdleSize(10);
+ // 设置Redis数据编解码器 序列化方式
+ Codec codec = new JsonJacksonCodec();
+ config.setCodec(codec);
+ if (redisson == null) {
+ redisson = Redisson.create(config);
+ log.info( "redis连接成功,server={}", redisUrl);
+ } else {
+ log.warn("redis 重复连接,config={}", config);
+ }
+ }
+
+ /**
+ * 读取缓存
+ *
+ * @param key 缓存key
+ * @param
+ * @return 缓存返回值
+ */
+ public T get(String key) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key);
+ return bucket.get();
+ }
+
+ /**
+ * 判断key是否存在
+ *
+ * @param key key
+ * @return {@link Boolean }
+ * @author yxx
+ */
+ public Boolean isExists(String key) {
+ return redisson.getBucket(REDIS_KEY_PREFIX + key).isExists();
+ }
+
+ /**
+ * 以string的方式读取缓存
+ *
+ * @param key 缓存key
+ * @return 缓存返回值
+ */
+ public String getString(String key) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE);
+ return bucket.get();
+ }
+
+ /**
+ * 设置缓存(注:redisson会自动选择序列化反序列化方式)
+ *
+ * @param key 缓存key
+ * @param value 缓存值
+ * @param
+ */
+ public void put(String key, T value) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key);
+ bucket.set(value, DEFAULT_EXPIRED, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 以string的方式设置缓存
+ *
+ * @param key
+ * @param value
+ */
+ public void putString(String key, String value) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE);
+ bucket.set(value, DEFAULT_EXPIRED, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 以string的方式保存缓存(与其他应用共用redis时需要使用该函数)
+ *
+ * @param key 缓存key
+ * @param value 缓存值
+ * @param expired 缓存过期时间
+ */
+ public void putString(String key, String value, long expired) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE);
+ bucket.set(value, expired <= 0 ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 以string的方式保存缓存(与其他应用共用redis时需要使用该函数)
+ *
+ * @param key 缓存key
+ * @param value 缓存值
+ * @param expired 缓存过期时间
+ * @param timeStyle 时间风格 {@link TimeUnit}
+ * @author yxx
+ */
+ public void putString(String key, String value, long expired, TimeUnit timeStyle) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE);
+ bucket.set(value, expired <= 0 ? DEFAULT_EXPIRED : expired, timeStyle);
+ }
+
+ /**
+ * 如果不存在则写入缓存(string方式,不带有redisson的格式信息)
+ *
+ * @param key 缓存key
+ * @param value 缓存值
+ * @param expired 缓存过期时间
+ */
+ public boolean putStringIfAbsent(String key, String value, long expired) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE);
+ return bucket.trySet(value, expired <= 0 ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 如果不存在则写入缓存(string方式,不带有redisson的格式信息)(不带过期时间,永久保存)
+ *
+ * @param key 缓存key
+ * @param value 缓存值
+ */
+ public boolean putStringIfAbsent(String key, String value) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key, StringCodec.INSTANCE);
+ return bucket.trySet(value);
+ }
+
+ /**
+ * 设置缓存
+ *
+ * @param key 缓存key
+ * @param value 缓存值
+ * @param expired 缓存过期时间
+ * @param 类型
+ */
+ public void put(String key, T value, long expired) {
+ RBucket bucket = redisson.getBucket(REDIS_KEY_PREFIX + key);
+ bucket.set(value, expired <= 0 ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS);
+ }
+
+ /**
+ * 移除缓存
+ *
+ * @param key
+ */
+ public void remove(String key) {
+ redisson.getBucket(REDIS_KEY_PREFIX + key).delete();
+ }
+
+ /**
+ * 判断缓存是否存在
+ *
+ * @param key
+ * @return
+ */
+ public boolean exists(String key) {
+ return redisson.getBucket(REDIS_KEY_PREFIX + key).isExists();
+ }
+
+
+ /**
+ * 暴露redisson的RList对象
+ *
+ * @param key
+ * @param
+ * @return
+ */
+ public RList getRedisList(String key) {
+ return redisson.getList(REDIS_KEY_PREFIX + key);
+ }
+
+ /**
+ * 暴露redisson的RMapCache对象
+ *
+ * @param key
+ * @param
+ * @param
+ * @return
+ */
+ public RMapCache getRedisMap(String key) {
+ return redisson.getMapCache(REDIS_KEY_PREFIX + key);
+ }
+
+ /**
+ * 暴露redisson的RSET对象
+ *
+ * @param key
+ * @param
+ * @return
+ */
+ public RSet getRedisSet(String key) {
+ return redisson.getSet(REDIS_KEY_PREFIX + key);
+ }
+
+
+ /**
+ * 暴露redisson的RScoredSortedSet对象
+ *
+ * @param key
+ * @param
+ * @return
+ */
+ public RScoredSortedSet getRedisScoredSortedSet(String key) {
+ return redisson.getScoredSortedSet(REDIS_KEY_PREFIX + key);
+ }
+
+ /**
+ * 获取redisson的Lock对象
+ *
+ * @param key key
+ * @return 锁
+ */
+ public RLock getRedisLock(String key) {
+ return redisson.getLock(REDIS_KEY_PREFIX + key);
+ }
+
+
+ @PreDestroy
+ public void close() {
+ try {
+ if (redisson != null) {
+ redisson.shutdown();
+ }
+ } catch (Exception ex) {
+ log.error( ex.getMessage(), ex);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/common/common-core/src/main/java/com/yxx/common/utils/satoken/StpAdminUtil.java b/common/common-core/src/main/java/com/yxx/common/utils/satoken/StpAdminUtil.java
new file mode 100644
index 0000000..88148cd
--- /dev/null
+++ b/common/common-core/src/main/java/com/yxx/common/utils/satoken/StpAdminUtil.java
@@ -0,0 +1,1221 @@
+package com.yxx.common.utils.satoken;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.fun.SaFunction;
+import cn.dev33.satoken.listener.SaTokenEventCenter;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.TokenSign;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.SaTokenInfo;
+import cn.dev33.satoken.stp.StpLogic;
+
+import java.util.List;
+
+/**
+ * Sa-Token 权限认证工具类
+ *
+ * @author click33
+ * @since 1.0.0
+ */
+public class StpAdminUtil {
+
+ private StpAdminUtil() {}
+
+ /**
+ * 多账号体系下的类型标识
+ */
+ public static final String TYPE = "admin";
+
+ /**
+ * 底层使用的 StpLogic 对象
+ */
+ public static StpLogic stpLogic = new StpLogic(TYPE);
+
+ /**
+ * 获取当前 StpLogic 的账号类型
+ *
+ * @return /
+ */
+ public static String getLoginType(){
+ return stpLogic.getLoginType();
+ }
+
+ /**
+ * 安全的重置 StpLogic 对象
+ *
+ * 1、更改此账户的 StpLogic 对象
+ * 2、put 到全局 StpLogic 集合中
+ * 3、发送日志
+ *
+ * @param newStpLogic /
+ */
+ public static void setStpLogic(StpLogic newStpLogic) {
+ // 1、重置此账户的 StpLogic 对象
+ stpLogic = newStpLogic;
+
+ // 2、添加到全局 StpLogic 集合中
+ // 以便可以通过 SaManager.getStpLogic(type) 的方式来全局获取到这个 StpLogic
+ SaManager.putStpLogic(newStpLogic);
+
+ // 3、$$ 发布事件:更新了 stpLogic 对象
+ SaTokenEventCenter.doSetStpLogic(stpLogic);
+ }
+
+ /**
+ * 获取 StpLogic 对象
+ *
+ * @return /
+ */
+ public static StpLogic getStpLogic() {
+ return stpLogic;
+ }
+
+
+ // ------------------- 获取 token 相关 -------------------
+
+ /**
+ * 返回 token 名称,此名称在以下地方体现:Cookie 保存 token 时的名称、提交 token 时参数的名称、存储 token 时的 key 前缀
+ *
+ * @return /
+ */
+ public static String getTokenName() {
+ return stpLogic.getTokenName();
+ }
+
+ /**
+ * 在当前会话写入指定 token 值
+ *
+ * @param tokenValue token 值
+ */
+ public static void setTokenValue(String tokenValue){
+ stpLogic.setTokenValue(tokenValue);
+ }
+
+ /**
+ * 在当前会话写入指定 token 值
+ *
+ * @param tokenValue token 值
+ * @param cookieTimeout Cookie存活时间(秒)
+ */
+ public static void setTokenValue(String tokenValue, int cookieTimeout){
+ stpLogic.setTokenValue(tokenValue, cookieTimeout);
+ }
+
+ /**
+ * 在当前会话写入指定 token 值
+ *
+ * @param tokenValue token 值
+ * @param loginModel 登录参数
+ */
+ public static void setTokenValue(String tokenValue, SaLoginModel loginModel){
+ stpLogic.setTokenValue(tokenValue, loginModel);
+ }
+
+ /**
+ * 获取当前请求的 token 值
+ *
+ * @return 当前tokenValue
+ */
+ public static String getTokenValue() {
+ return stpLogic.getTokenValue();
+ }
+
+ /**
+ * 获取当前请求的 token 值 (不裁剪前缀)
+ *
+ * @return /
+ */
+ public static String getTokenValueNotCut(){
+ return stpLogic.getTokenValueNotCut();
+ }
+
+ /**
+ * 获取当前会话的 token 参数信息
+ *
+ * @return token 参数信息
+ */
+ public static SaTokenInfo getTokenInfo() {
+ return stpLogic.getTokenInfo();
+ }
+
+
+ // ------------------- 登录相关操作 -------------------
+
+ // --- 登录
+
+ /**
+ * 会话登录
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ */
+ public static void login(Object id) {
+ stpLogic.login(id);
+ }
+
+ /**
+ * 会话登录,并指定登录设备类型
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param device 设备类型
+ */
+ public static void login(Object id, String device) {
+ stpLogic.login(id, device);
+ }
+
+ /**
+ * 会话登录,并指定是否 [记住我]
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param isLastingCookie 是否为持久Cookie,值为 true 时记住我,值为 false 时关闭浏览器需要重新登录
+ */
+ public static void login(Object id, boolean isLastingCookie) {
+ stpLogic.login(id, isLastingCookie);
+ }
+
+ /**
+ * 会话登录,并指定此次登录 token 的有效期, 单位:秒
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param timeout 此次登录 token 的有效期, 单位:秒
+ */
+ public static void login(Object id, long timeout) {
+ stpLogic.login(id, timeout);
+ }
+
+ /**
+ * 会话登录,并指定所有登录参数 Model
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param loginModel 此次登录的参数Model
+ */
+ public static void login(Object id, SaLoginModel loginModel) {
+ stpLogic.login(id, loginModel);
+ }
+
+ /**
+ * 创建指定账号 id 的登录会话数据
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @return 返回会话令牌
+ */
+ public static String createLoginSession(Object id) {
+ return stpLogic.createLoginSession(id);
+ }
+
+ /**
+ * 创建指定账号 id 的登录会话数据
+ *
+ * @param id 账号id,建议的类型:(long | int | String)
+ * @param loginModel 此次登录的参数Model
+ * @return 返回会话令牌
+ */
+ public static String createLoginSession(Object id, SaLoginModel loginModel) {
+ return stpLogic.createLoginSession(id, loginModel);
+ }
+
+ // --- 注销
+
+ /**
+ * 在当前客户端会话注销
+ */
+ public static void logout() {
+ stpLogic.logout();
+ }
+
+ /**
+ * 会话注销,根据账号id
+ *
+ * @param loginId 账号id
+ */
+ public static void logout(Object loginId) {
+ stpLogic.logout(loginId);
+ }
+
+ /**
+ * 会话注销,根据账号id 和 设备类型
+ *
+ * @param loginId 账号id
+ * @param device 设备类型 (填 null 代表注销该账号的所有设备类型)
+ */
+ public static void logout(Object loginId, String device) {
+ stpLogic.logout(loginId, device);
+ }
+
+ /**
+ * 会话注销,根据指定 Token
+ *
+ * @param tokenValue 指定 token
+ */
+ public static void logoutByTokenValue(String tokenValue) {
+ stpLogic.logoutByTokenValue(tokenValue);
+ }
+
+ /**
+ * 踢人下线,根据账号id
+ * 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5
+ *
+ * @param loginId 账号id
+ */
+ public static void kickout(Object loginId) {
+ stpLogic.kickout(loginId);
+ }
+
+ /**
+ * 踢人下线,根据账号id 和 设备类型
+ * 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5
+ *
+ * @param loginId 账号id
+ * @param device 设备类型 (填 null 代表踢出该账号的所有设备类型)
+ */
+ public static void kickout(Object loginId, String device) {
+ stpLogic.kickout(loginId, device);
+ }
+
+ /**
+ * 踢人下线,根据指定 token
+ * 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-5
+ *
+ * @param tokenValue 指定 token
+ */
+ public static void kickoutByTokenValue(String tokenValue) {
+ stpLogic.kickoutByTokenValue(tokenValue);
+ }
+
+ /**
+ * 顶人下线,根据账号id 和 设备类型
+ * 当对方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4
+ *
+ * @param loginId 账号id
+ * @param device 设备类型 (填 null 代表顶替该账号的所有设备类型)
+ */
+ public static void replaced(Object loginId, String device) {
+ stpLogic.replaced(loginId, device);
+ }
+
+ // 会话查询
+
+ /**
+ * 判断当前会话是否已经登录
+ *
+ * @return 已登录返回 true,未登录返回 false
+ */
+ public static boolean isLogin() {
+ return stpLogic.isLogin();
+ }
+
+ /**
+ * 判断指定账号是否已经登录
+ *
+ * @return 已登录返回 true,未登录返回 false
+ */
+ public static boolean isLogin(Object loginId) {
+ return stpLogic.isLogin(loginId);
+ }
+
+ /**
+ * 检验当前会话是否已经登录,如未登录,则抛出异常
+ */
+ public static void checkLogin() {
+ stpLogic.checkLogin();
+ }
+
+ /**
+ * 获取当前会话账号id,如果未登录,则抛出异常
+ *
+ * @return 账号id
+ */
+ public static Object getLoginId() {
+ return stpLogic.getLoginId();
+ }
+
+ /**
+ * 获取当前会话账号id, 如果未登录,则返回默认值
+ *
+ * @param 返回类型
+ * @param defaultValue 默认值
+ * @return 登录id
+ */
+ public static T getLoginId(T defaultValue) {
+ return stpLogic.getLoginId(defaultValue);
+ }
+
+ /**
+ * 获取当前会话账号id, 如果未登录,则返回null
+ *
+ * @return 账号id
+ */
+ public static Object getLoginIdDefaultNull() {
+ return stpLogic.getLoginIdDefaultNull();
+ }
+
+ /**
+ * 获取当前会话账号id, 并转换为 String 类型
+ *
+ * @return 账号id
+ */
+ public static String getLoginIdAsString() {
+ return stpLogic.getLoginIdAsString();
+ }
+
+ /**
+ * 获取当前会话账号id, 并转换为 int 类型
+ *
+ * @return 账号id
+ */
+ public static int getLoginIdAsInt() {
+ return stpLogic.getLoginIdAsInt();
+ }
+
+ /**
+ * 获取当前会话账号id, 并转换为 long 类型
+ *
+ * @return 账号id
+ */
+ public static long getLoginIdAsLong() {
+ return stpLogic.getLoginIdAsLong();
+ }
+
+ /**
+ * 获取指定 token 对应的账号id,如果未登录,则返回 null
+ *
+ * @param tokenValue token
+ * @return 账号id
+ */
+ public static Object getLoginIdByToken(String tokenValue) {
+ return stpLogic.getLoginIdByToken(tokenValue);
+ }
+
+ /**
+ * 获取当前 Token 的扩展信息(此函数只在jwt模式下生效)
+ *
+ * @param key 键值
+ * @return 对应的扩展数据
+ */
+ public static Object getExtra(String key) {
+ return stpLogic.getExtra(key);
+ }
+
+ /**
+ * 获取指定 Token 的扩展信息(此函数只在jwt模式下生效)
+ *
+ * @param tokenValue 指定的 Token 值
+ * @param key 键值
+ * @return 对应的扩展数据
+ */
+ public static Object getExtra(String tokenValue, String key) {
+ return stpLogic.getExtra(tokenValue, key);
+ }
+
+
+ // ------------------- Account-Session 相关 -------------------
+
+ /**
+ * 获取指定账号 id 的 Account-Session, 如果该 SaSession 尚未创建,isCreate=是否新建并返回
+ *
+ * @param loginId 账号id
+ * @param isCreate 是否新建
+ * @return SaSession 对象
+ */
+ public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
+ return stpLogic.getSessionByLoginId(loginId, isCreate);
+ }
+
+ /**
+ * 获取指定 key 的 SaSession, 如果该 SaSession 尚未创建,则返回 null
+ *
+ * @param sessionId SessionId
+ * @return Session对象
+ */
+ public static SaSession getSessionBySessionId(String sessionId) {
+ return stpLogic.getSessionBySessionId(sessionId);
+ }
+
+ /**
+ * 获取指定账号 id 的 Account-Session,如果该 SaSession 尚未创建,则新建并返回
+ *
+ * @param loginId 账号id
+ * @return SaSession 对象
+ */
+ public static SaSession getSessionByLoginId(Object loginId) {
+ return stpLogic.getSessionByLoginId(loginId);
+ }
+
+ /**
+ * 获取当前已登录账号的 Account-Session, 如果该 SaSession 尚未创建,isCreate=是否新建并返回
+ *
+ * @param isCreate 是否新建
+ * @return Session对象
+ */
+ public static SaSession getSession(boolean isCreate) {
+ return stpLogic.getSession(isCreate);
+ }
+
+ /**
+ * 获取当前已登录账号的 Account-Session,如果该 SaSession 尚未创建,则新建并返回
+ *
+ * @return Session对象
+ */
+ public static SaSession getSession() {
+ return stpLogic.getSession();
+ }
+
+
+ // ------------------- Token-Session 相关 -------------------
+
+ /**
+ * 获取指定 token 的 Token-Session,如果该 SaSession 尚未创建,则新建并返回
+ *
+ * @param tokenValue Token值
+ * @return Session对象
+ */
+ public static SaSession getTokenSessionByToken(String tokenValue) {
+ return stpLogic.getTokenSessionByToken(tokenValue);
+ }
+
+ /**
+ * 获取当前 token 的 Token-Session,如果该 SaSession 尚未创建,则新建并返回
+ *
+ * @return Session对象
+ */
+ public static SaSession getTokenSession() {
+ return stpLogic.getTokenSession();
+ }
+
+ /**
+ * 获取当前匿名 Token-Session (可在未登录情况下使用的Token-Session)
+ *
+ * @return Token-Session 对象
+ */
+ public static SaSession getAnonTokenSession() {
+ return stpLogic.getAnonTokenSession();
+ }
+
+
+ // ------------------- Active-Timeout token 最低活跃度 验证相关 -------------------
+
+ /**
+ * 续签当前 token:(将 [最后操作时间] 更新为当前时间戳)
+ *
+ * 请注意: 即使 token 已被冻结 也可续签成功,
+ * 如果此场景下需要提示续签失败,可在此之前调用 checkActiveTimeout() 强制检查是否冻结即可
+ *
+ */
+ public static void updateLastActiveToNow() {
+ stpLogic.updateLastActiveToNow();
+ }
+
+ /**
+ * 检查当前 token 是否已被冻结,如果是则抛出异常
+ */
+ public static void checkActiveTimeout() {
+ stpLogic.checkActiveTimeout();
+ }
+
+
+ // ------------------- 过期时间相关 -------------------
+
+ /**
+ * 获取当前会话 token 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值)
+ *
+ * @return token剩余有效时间
+ */
+ public static long getTokenTimeout() {
+ return stpLogic.getTokenTimeout();
+ }
+
+ /**
+ * 获取指定 token 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值)
+ *
+ * @param token 指定token
+ * @return token剩余有效时间
+ */
+ public static long getTokenTimeout(String token) {
+ return stpLogic.getTokenTimeout(token);
+ }
+
+ /**
+ * 获取当前登录账号的 Account-Session 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值)
+ *
+ * @return token剩余有效时间
+ */
+ public static long getSessionTimeout() {
+ return stpLogic.getSessionTimeout();
+ }
+
+ /**
+ * 获取当前 token 的 Token-Session 剩余有效时间(单位: 秒,返回 -1 代表永久有效,-2 代表没有这个值)
+ *
+ * @return token剩余有效时间
+ */
+ public static long getTokenSessionTimeout() {
+ return stpLogic.getTokenSessionTimeout();
+ }
+
+ /**
+ * 获取当前 token 剩余活跃有效期:当前 token 距离被冻结还剩多少时间(单位: 秒,返回 -1 代表永不冻结,-2 代表没有这个值或 token 已被冻结了)
+ *
+ * @return /
+ */
+ public static long getTokenActiveTimeout() {
+ return stpLogic.getTokenActiveTimeout();
+ }
+
+ /**
+ * 对当前 token 的 timeout 值进行续期
+ *
+ * @param timeout 要修改成为的有效时间 (单位: 秒)
+ */
+ public static void renewTimeout(long timeout) {
+ stpLogic.renewTimeout(timeout);
+ }
+
+ /**
+ * 对指定 token 的 timeout 值进行续期
+ *
+ * @param tokenValue 指定 token
+ * @param timeout 要修改成为的有效时间 (单位: 秒,填 -1 代表要续为永久有效)
+ */
+ public static void renewTimeout(String tokenValue, long timeout) {
+ stpLogic.renewTimeout(tokenValue, timeout);
+ }
+
+
+ // ------------------- 角色认证操作 -------------------
+
+ /**
+ * 获取:当前账号的角色集合
+ *
+ * @return /
+ */
+ public static List getRoleList() {
+ return stpLogic.getRoleList();
+ }
+
+ /**
+ * 获取:指定账号的角色集合
+ *
+ * @param loginId 指定账号id
+ * @return /
+ */
+ public static List getRoleList(Object loginId) {
+ return stpLogic.getRoleList(loginId);
+ }
+
+ /**
+ * 判断:当前账号是否拥有指定角色, 返回 true 或 false
+ *
+ * @param role 角色
+ * @return /
+ */
+ public static boolean hasRole(String role) {
+ return stpLogic.hasRole(role);
+ }
+
+ /**
+ * 判断:指定账号是否含有指定角色标识, 返回 true 或 false
+ *
+ * @param loginId 账号id
+ * @param role 角色标识
+ * @return 是否含有指定角色标识
+ */
+ public static boolean hasRole(Object loginId, String role) {
+ return stpLogic.hasRole(loginId, role);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定角色标识 [ 指定多个,必须全部验证通过 ]
+ *
+ * @param roleArray 角色标识数组
+ * @return true或false
+ */
+ public static boolean hasRoleAnd(String... roleArray){
+ return stpLogic.hasRoleAnd(roleArray);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定角色标识 [ 指定多个,只要其一验证通过即可 ]
+ *
+ * @param roleArray 角色标识数组
+ * @return true或false
+ */
+ public static boolean hasRoleOr(String... roleArray){
+ return stpLogic.hasRoleOr(roleArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
+ *
+ * @param role 角色标识
+ */
+ public static void checkRole(String role) {
+ stpLogic.checkRole(role);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定角色标识 [ 指定多个,必须全部验证通过 ]
+ *
+ * @param roleArray 角色标识数组
+ */
+ public static void checkRoleAnd(String... roleArray){
+ stpLogic.checkRoleAnd(roleArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定角色标识 [ 指定多个,只要其一验证通过即可 ]
+ *
+ * @param roleArray 角色标识数组
+ */
+ public static void checkRoleOr(String... roleArray){
+ stpLogic.checkRoleOr(roleArray);
+ }
+
+
+ // ------------------- 权限认证操作 -------------------
+
+ /**
+ * 获取:当前账号的权限码集合
+ *
+ * @return /
+ */
+ public static List getPermissionList() {
+ return stpLogic.getPermissionList();
+ }
+
+ /**
+ * 获取:指定账号的权限码集合
+ *
+ * @param loginId 指定账号id
+ * @return /
+ */
+ public static List getPermissionList(Object loginId) {
+ return stpLogic.getPermissionList(loginId);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限, 返回 true 或 false
+ *
+ * @param permission 权限码
+ * @return 是否含有指定权限
+ */
+ public static boolean hasPermission(String permission) {
+ return stpLogic.hasPermission(permission);
+ }
+
+ /**
+ * 判断:指定账号 id 是否含有指定权限, 返回 true 或 false
+ *
+ * @param loginId 账号 id
+ * @param permission 权限码
+ * @return 是否含有指定权限
+ */
+ public static boolean hasPermission(Object loginId, String permission) {
+ return stpLogic.hasPermission(loginId, permission);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限 [ 指定多个,必须全部具有 ]
+ *
+ * @param permissionArray 权限码数组
+ * @return true 或 false
+ */
+ public static boolean hasPermissionAnd(String... permissionArray){
+ return stpLogic.hasPermissionAnd(permissionArray);
+ }
+
+ /**
+ * 判断:当前账号是否含有指定权限 [ 指定多个,只要其一验证通过即可 ]
+ *
+ * @param permissionArray 权限码数组
+ * @return true 或 false
+ */
+ public static boolean hasPermissionOr(String... permissionArray){
+ return stpLogic.hasPermissionOr(permissionArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
+ *
+ * @param permission 权限码
+ */
+ public static void checkPermission(String permission) {
+ stpLogic.checkPermission(permission);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限 [ 指定多个,必须全部验证通过 ]
+ *
+ * @param permissionArray 权限码数组
+ */
+ public static void checkPermissionAnd(String... permissionArray) {
+ stpLogic.checkPermissionAnd(permissionArray);
+ }
+
+ /**
+ * 校验:当前账号是否含有指定权限 [ 指定多个,只要其一验证通过即可 ]
+ *
+ * @param permissionArray 权限码数组
+ */
+ public static void checkPermissionOr(String... permissionArray) {
+ stpLogic.checkPermissionOr(permissionArray);
+ }
+
+
+ // ------------------- id 反查 token 相关操作 -------------------
+
+ /**
+ * 获取指定账号 id 的 token
+ *
+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个 token,
+ * 如果你需要返回此账号 id 的所有 token,请调用 getTokenValueListByLoginId
+ *
+ *
+ * @param loginId 账号id
+ * @return token值
+ */
+ public static String getTokenValueByLoginId(Object loginId) {
+ return stpLogic.getTokenValueByLoginId(loginId);
+ }
+
+ /**
+ * 获取指定账号 id 指定设备类型端的 token
+ *
+ * 在配置为允许并发登录时,此方法只会返回队列的最后一个 token,
+ * 如果你需要返回此账号 id 的所有 token,请调用 getTokenValueListByLoginId
+ *
+ *
+ * @param loginId 账号id
+ * @param device 设备类型,填 null 代表不限设备类型
+ * @return token值
+ */
+ public static String getTokenValueByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueByLoginId(loginId, device);
+ }
+
+ /**
+ * 获取指定账号 id 的 token 集合
+ *
+ * @param loginId 账号id
+ * @return 此 loginId 的所有相关 token
+ */
+ public static List getTokenValueListByLoginId(Object loginId) {
+ return stpLogic.getTokenValueListByLoginId(loginId);
+ }
+
+ /**
+ * 获取指定账号 id 指定设备类型端的 token 集合
+ *
+ * @param loginId 账号id
+ * @param device 设备类型,填 null 代表不限设备类型
+ * @return 此 loginId 的所有登录 token
+ */
+ public static List getTokenValueListByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenValueListByLoginId(loginId, device);
+ }
+
+ /**
+ * 获取指定账号 id 指定设备类型端的 tokenSign 集合
+ *
+ * @param loginId 账号id
+ * @param device 设备类型,填 null 代表不限设备类型
+ * @return 此 loginId 的所有登录 tokenSign
+ */
+ public static List getTokenSignListByLoginId(Object loginId, String device) {
+ return stpLogic.getTokenSignListByLoginId(loginId, device);
+ }
+
+ /**
+ * 返回当前会话的登录设备类型
+ *
+ * @return 当前令牌的登录设备类型
+ */
+ public static String getLoginDevice() {
+ return stpLogic.getLoginDevice();
+ }
+
+
+ // ------------------- 会话管理 -------------------
+
+ /**
+ * 根据条件查询缓存中所有的 token
+ *
+ * @param keyword 关键字
+ * @param start 开始处索引
+ * @param size 获取数量 (-1代表一直获取到末尾)
+ * @param sortType 排序类型(true=正序,false=反序)
+ *
+ * @return token集合
+ */
+ public static List searchTokenValue(String keyword, int start, int size, boolean sortType) {
+ return stpLogic.searchTokenValue(keyword, start, size, sortType);
+ }
+
+ /**
+ * 根据条件查询缓存中所有的 SessionId
+ *
+ * @param keyword 关键字
+ * @param start 开始处索引
+ * @param size 获取数量 (-1代表一直获取到末尾)
+ * @param sortType 排序类型(true=正序,false=反序)
+ *
+ * @return sessionId集合
+ */
+ public static List searchSessionId(String keyword, int start, int size, boolean sortType) {
+ return stpLogic.searchSessionId(keyword, start, size, sortType);
+ }
+
+ /**
+ * 根据条件查询缓存中所有的 Token-Session-Id
+ *
+ * @param keyword 关键字
+ * @param start 开始处索引
+ * @param size 获取数量 (-1代表一直获取到末尾)
+ * @param sortType 排序类型(true=正序,false=反序)
+ *
+ * @return sessionId集合
+ */
+ public static List searchTokenSessionId(String keyword, int start, int size, boolean sortType) {
+ return stpLogic.searchTokenSessionId(keyword, start, size, sortType);
+ }
+
+
+ // ------------------- 账号封禁 -------------------
+
+ /**
+ * 封禁:指定账号
+ * 此方法不会直接将此账号id踢下线,如需封禁后立即掉线,请追加调用 StpUtil.logout(id)
+ *
+ * @param loginId 指定账号id
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ */
+ public static void disable(Object loginId, long time) {
+ stpLogic.disable(loginId, time);
+ }
+
+ /**
+ * 判断:指定账号是否已被封禁 (true=已被封禁, false=未被封禁)
+ *
+ * @param loginId 账号id
+ * @return /
+ */
+ public static boolean isDisable(Object loginId) {
+ return stpLogic.isDisable(loginId);
+ }
+
+ /**
+ * 校验:指定账号是否已被封禁,如果被封禁则抛出异常
+ *
+ * @param loginId 账号id
+ */
+ public static void checkDisable(Object loginId) {
+ stpLogic.checkDisable(loginId);
+ }
+
+ /**
+ * 获取:指定账号剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
+ *
+ * @param loginId 账号id
+ * @return /
+ */
+ public static long getDisableTime(Object loginId) {
+ return stpLogic.getDisableTime(loginId);
+ }
+
+ /**
+ * 解封:指定账号
+ *
+ * @param loginId 账号id
+ */
+ public static void untieDisable(Object loginId) {
+ stpLogic.untieDisable(loginId);
+ }
+
+
+ // ------------------- 分类封禁 -------------------
+
+ /**
+ * 封禁:指定账号的指定服务
+ *
此方法不会直接将此账号id踢下线,如需封禁后立即掉线,请追加调用 StpUtil.logout(id)
+ *
+ * @param loginId 指定账号id
+ * @param service 指定服务
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ */
+ public static void disable(Object loginId, String service, long time) {
+ stpLogic.disable(loginId, service, time);
+ }
+
+ /**
+ * 判断:指定账号的指定服务 是否已被封禁(true=已被封禁, false=未被封禁)
+ *
+ * @param loginId 账号id
+ * @param service 指定服务
+ * @return /
+ */
+ public static boolean isDisable(Object loginId, String service) {
+ return stpLogic.isDisable(loginId, service);
+ }
+
+ /**
+ * 校验:指定账号 指定服务 是否已被封禁,如果被封禁则抛出异常
+ *
+ * @param loginId 账号id
+ * @param services 指定服务,可以指定多个
+ */
+ public static void checkDisable(Object loginId, String... services) {
+ stpLogic.checkDisable(loginId, services);
+ }
+
+ /**
+ * 获取:指定账号 指定服务 剩余封禁时间,单位:秒(-1=永久封禁,-2=未被封禁)
+ *
+ * @param loginId 账号id
+ * @param service 指定服务
+ * @return see note
+ */
+ public static long getDisableTime(Object loginId, String service) {
+ return stpLogic.getDisableTime(loginId, service);
+ }
+
+ /**
+ * 解封:指定账号、指定服务
+ *
+ * @param loginId 账号id
+ * @param services 指定服务,可以指定多个
+ */
+ public static void untieDisable(Object loginId, String... services) {
+ stpLogic.untieDisable(loginId, services);
+ }
+
+
+ // ------------------- 阶梯封禁 -------------------
+
+ /**
+ * 封禁:指定账号,并指定封禁等级
+ *
+ * @param loginId 指定账号id
+ * @param level 指定封禁等级
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ */
+ public static void disableLevel(Object loginId, int level, long time) {
+ stpLogic.disableLevel(loginId, level, time);
+ }
+
+ /**
+ * 封禁:指定账号的指定服务,并指定封禁等级
+ *
+ * @param loginId 指定账号id
+ * @param service 指定封禁服务
+ * @param level 指定封禁等级
+ * @param time 封禁时间, 单位: 秒 (-1=永久封禁)
+ */
+ public static void disableLevel(Object loginId, String service, int level, long time) {
+ stpLogic.disableLevel(loginId, service, level, time);
+ }
+
+ /**
+ * 判断:指定账号是否已被封禁到指定等级
+ *
+ * @param loginId 指定账号id
+ * @param level 指定封禁等级
+ * @return /
+ */
+ public static boolean isDisableLevel(Object loginId, int level) {
+ return stpLogic.isDisableLevel(loginId, level);
+ }
+
+ /**
+ * 判断:指定账号的指定服务,是否已被封禁到指定等级
+ *
+ * @param loginId 指定账号id
+ * @param service 指定封禁服务
+ * @param level 指定封禁等级
+ * @return /
+ */
+ public static boolean isDisableLevel(Object loginId, String service, int level) {
+ return stpLogic.isDisableLevel(loginId, service, level);
+ }
+
+ /**
+ * 校验:指定账号是否已被封禁到指定等级(如果已经达到,则抛出异常)
+ *
+ * @param loginId 指定账号id
+ * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
+ */
+ public static void checkDisableLevel(Object loginId, int level) {
+ stpLogic.checkDisableLevel(loginId, level);
+ }
+
+ /**
+ * 校验:指定账号的指定服务,是否已被封禁到指定等级(如果已经达到,则抛出异常)
+ *
+ * @param loginId 指定账号id
+ * @param service 指定封禁服务
+ * @param level 封禁等级 (只有 封禁等级 ≥ 此值 才会抛出异常)
+ */
+ public static void checkDisableLevel(Object loginId, String service, int level) {
+ stpLogic.checkDisableLevel(loginId, service, level);
+ }
+
+ /**
+ * 获取:指定账号被封禁的等级,如果未被封禁则返回-2
+ *
+ * @param loginId 指定账号id
+ * @return /
+ */
+ public static int getDisableLevel(Object loginId) {
+ return stpLogic.getDisableLevel(loginId);
+ }
+
+ /**
+ * 获取:指定账号的 指定服务 被封禁的等级,如果未被封禁则返回-2
+ *
+ * @param loginId 指定账号id
+ * @param service 指定封禁服务
+ * @return /
+ */
+ public static int getDisableLevel(Object loginId, String service) {
+ return stpLogic.getDisableLevel(loginId, service);
+ }
+
+
+ // ------------------- 临时身份切换 -------------------
+
+ /**
+ * 临时切换身份为指定账号id
+ *
+ * @param loginId 指定loginId
+ */
+ public static void switchTo(Object loginId) {
+ stpLogic.switchTo(loginId);
+ }
+
+ /**
+ * 结束临时切换身份
+ */
+ public static void endSwitch() {
+ stpLogic.endSwitch();
+ }
+
+ /**
+ * 判断当前请求是否正处于 [ 身份临时切换 ] 中
+ *
+ * @return /
+ */
+ public static boolean isSwitch() {
+ return stpLogic.isSwitch();
+ }
+
+ /**
+ * 在一个 lambda 代码段里,临时切换身份为指定账号id,lambda 结束后自动恢复
+ *
+ * @param loginId 指定账号id
+ * @param function 要执行的方法
+ */
+ public static void switchTo(Object loginId, SaFunction function) {
+ stpLogic.switchTo(loginId, function);
+ }
+
+
+ // ------------------- 二级认证 -------------------
+
+ /**
+ * 在当前会话 开启二级认证
+ *
+ * @param safeTime 维持时间 (单位: 秒)
+ */
+ public static void openSafe(long safeTime) {
+ stpLogic.openSafe(safeTime);
+ }
+
+ /**
+ * 在当前会话 开启二级认证
+ *
+ * @param service 业务标识
+ * @param safeTime 维持时间 (单位: 秒)
+ */
+ public static void openSafe(String service, long safeTime) {
+ stpLogic.openSafe(service, safeTime);
+ }
+
+ /**
+ * 判断:当前会话是否处于二级认证时间内
+ *
+ * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
+ */
+ public static boolean isSafe() {
+ return stpLogic.isSafe();
+ }
+
+ /**
+ * 判断:当前会话 是否处于指定业务的二级认证时间内
+ *
+ * @param service 业务标识
+ * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
+ */
+ public static boolean isSafe(String service) {
+ return stpLogic.isSafe(service);
+ }
+
+ /**
+ * 判断:指定 token 是否处于二级认证时间内
+ *
+ * @param tokenValue Token 值
+ * @param service 业务标识
+ * @return true=二级认证已通过, false=尚未进行二级认证或认证已超时
+ */
+ public static boolean isSafe(String tokenValue, String service) {
+ return stpLogic.isSafe(tokenValue, service);
+ }
+
+ /**
+ * 校验:当前会话是否已通过二级认证,如未通过则抛出异常
+ */
+ public static void checkSafe() {
+ stpLogic.checkSafe();
+ }
+
+ /**
+ * 校验:检查当前会话是否已通过指定业务的二级认证,如未通过则抛出异常
+ *
+ * @param service 业务标识
+ */
+ public static void checkSafe(String service) {
+ stpLogic.checkSafe(service);
+ }
+
+ /**
+ * 获取:当前会话的二级认证剩余有效时间(单位: 秒, 返回-2代表尚未通过二级认证)
+ *
+ * @return 剩余有效时间
+ */
+ public static long getSafeTime() {
+ return stpLogic.getSafeTime();
+ }
+
+ /**
+ * 获取:当前会话的二级认证剩余有效时间(单位: 秒, 返回-2代表尚未通过二级认证)
+ *
+ * @param service 业务标识
+ * @return 剩余有效时间
+ */
+ public static long getSafeTime(String service) {
+ return stpLogic.getSafeTime(service);
+ }
+
+ /**
+ * 在当前会话 结束二级认证
+ */
+ public static void closeSafe() {
+ stpLogic.closeSafe();
+ }
+
+ /**
+ * 在当前会话 结束指定业务标识的二级认证
+ *
+ * @param service 业务标识
+ */
+ public static void closeSafe(String service) {
+ stpLogic.closeSafe(service);
+ }
+
+}
diff --git a/common/common-framework/pom.xml b/common/common-framework/pom.xml
new file mode 100644
index 0000000..8fb2b16
--- /dev/null
+++ b/common/common-framework/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+ common-framework
+ common-framework
+ common-framework
+
+
+ common
+ com.yxx
+ 1.0.0
+
+
+
+
+
+ com.yxx
+ common-core
+ 1.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${spring-boot.version}
+
+
+
+
diff --git a/common/common-framework/src/main/java/com/yxx/framework/advice/ExceptionAdvice.java b/common/common-framework/src/main/java/com/yxx/framework/advice/ExceptionAdvice.java
new file mode 100644
index 0000000..7a8ded5
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/advice/ExceptionAdvice.java
@@ -0,0 +1,119 @@
+package com.yxx.framework.advice;
+
+import cn.dev33.satoken.exception.DisableServiceException;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotRoleException;
+import com.yxx.common.core.response.ErrorResponse;
+import com.yxx.common.enums.ApiCode;
+import com.yxx.common.exceptions.ApiException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+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.multipart.MaxUploadSizeExceededException;
+
+import jakarta.validation.ConstraintViolationException;
+
+
+/**
+ * @author yxx
+ * @description: 全局异常处理类
+ */
+@Slf4j
+@RestControllerAdvice
+public class ExceptionAdvice {
+ private static final String ERROR_CODE = "发生业务异常!原因是: {}";
+ private static final String LOGIN_ERROR_CODE = "发生登录异常!原因是: {},被封禁账号id: {}";
+ private static final String UN_KNOW_ERROR_CODE = "发生未知异常!原因是: {}";
+ private static final String CHECK_PARAMETERS_ERROR_CODE = "发生参数校验异常!原因是:{}";
+ private static final String CHECK_PARAMETERS_ERROR = "发生参数校验异常!原因是:";
+ private static final String REQUEST_ERROR_CODE = "发生请求异常!原因是:{}";
+
+ /**
+ * 处理自定义的业务异常
+ *
+ * @param e 异常对象
+ * @return 错误结果
+ */
+ @ExceptionHandler(ApiException.class)
+ public ErrorResponse bizExceptionHandler(ApiException e) {
+ log.error(ERROR_CODE, e.getMessage());
+ return ErrorResponse.fail(e.getCode(), e.getMessage());
+ }
+
+ @ExceptionHandler(NotLoginException.class)
+ public ErrorResponse notLoginException(NotLoginException e) {
+ log.error(ERROR_CODE, e.getMessage());
+ return switch (e.getType()) {
+ case NotLoginException.NOT_TOKEN,
+ NotLoginException.INVALID_TOKEN,
+ NotLoginException.TOKEN_TIMEOUT,
+ NotLoginException.BE_REPLACED -> ErrorResponse.fail(ApiCode.TOKEN_ERROR.getCode(), e.getMessage());
+ default -> ErrorResponse.fail(Integer.parseInt(e.getType()), e.getMessage());
+ };
+ }
+
+ @ExceptionHandler(NotRoleException.class)
+ public ErrorResponse notRoleException(NotRoleException e) {
+ log.error(ERROR_CODE, e.getMessage());
+ return ErrorResponse.fail(10000, e.getMessage());
+ }
+
+ @ExceptionHandler(DisableServiceException.class)
+ public ErrorResponse disableLoginException(DisableServiceException e) {
+ log.error(LOGIN_ERROR_CODE, e.getMessage(), e.getLoginId());
+ long disableTime = e.getDisableTime();
+ long day = disableTime / 86400L;
+ long hour = (disableTime % 86400) / 3600L;
+ long minute = (disableTime % 86400) % 3600L / 60L;
+ long second = (disableTime % 86400) % 3600L % 60L;
+ return ErrorResponse.fail(10000, e.getMessage() + ",距离解封还剩:" + day + "天" + hour + "时"
+ + minute + "分" + second + "秒");
+ }
+
+ /**
+ * 拦截抛出的异常
+ *
+ * @param e 异常
+ * @return 拦截抛出的异常
+ */
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ @ExceptionHandler(Throwable.class)
+ public ErrorResponse handlerThrowable(Throwable e) {
+ log.error(UN_KNOW_ERROR_CODE, e.getMessage());
+ return ErrorResponse.fail(ApiCode.SYSTEM_ERROR, e);
+ }
+
+ /**
+ * 参数校验异常
+ *
+ * @param e 异常
+ * @return 参数校验异常返回
+ */
+ @ExceptionHandler(BindException.class)
+ public ErrorResponse bindException(BindException e) {
+ log.error(CHECK_PARAMETERS_ERROR, e);
+ return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getAllErrors().get(0).getDefaultMessage());
+ }
+
+ @ExceptionHandler(ConstraintViolationException.class)
+ public ErrorResponse constraintViolationException(ConstraintViolationException e) {
+ log.error(CHECK_PARAMETERS_ERROR_CODE, e.getConstraintViolations().stream().iterator().next().getMessage());
+ return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getConstraintViolations().iterator().next().getMessage());
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ErrorResponse methodArgumentNotValidException(MethodArgumentNotValidException e) {
+ log.error(CHECK_PARAMETERS_ERROR_CODE, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
+ return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
+ }
+
+ @ExceptionHandler(MaxUploadSizeExceededException.class)
+ public ErrorResponse maxUploadSizeExceededException(MaxUploadSizeExceededException e) {
+ log.error(REQUEST_ERROR_CODE, e.getMessage());
+ return ErrorResponse.fail(ApiCode.PARAM_IS_INVALID, e, e.getMessage());
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/advice/ResponseResultHandler.java b/common/common-framework/src/main/java/com/yxx/framework/advice/ResponseResultHandler.java
new file mode 100644
index 0000000..ccec9f1
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/advice/ResponseResultHandler.java
@@ -0,0 +1,80 @@
+package com.yxx.framework.advice;
+
+import com.yxx.common.annotation.response.ResponseResult;
+import com.yxx.common.core.response.BaseResponse;
+import com.yxx.common.core.response.ErrorResponse;
+import com.yxx.framework.context.AppContext;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * @author yxx
+ * @description: 使用 @ControllerAdvice & ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体
+ */
+@Slf4j
+@ControllerAdvice
+public class ResponseResultHandler implements ResponseBodyAdvice {
+
+ /**
+ * 标记名称
+ */
+ public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT";
+
+
+ /**
+ * 判断是否要执行 beforeBodyWrite 方法,true为执行,false不执行,有注解标记的时候处理返回值
+ *
+ * @param arg0 the return type
+ * @param arg1 the selected converter type
+ * @return return
+ */
+ @Override
+ public boolean supports(@NotNull MethodParameter arg0, @NotNull Class extends HttpMessageConverter>> arg1) {
+ ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ assert sra != null;
+ HttpServletRequest request = sra.getRequest();
+ // 判断请求是否有包装标记
+ ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
+ return responseResultAnn != null;
+ }
+
+
+ /**
+ * 对返回值做包装处理,如果属于异常结果,则需要再包装
+ *
+ * @param body the body to be written
+ * @param arg1 the return type of the controller method
+ * @param arg2 the content type selected through content negotiation
+ * @param arg3 the converter type selected to write to the response
+ * @param arg4 the current request
+ * @param arg5 the current response
+ * @return the body that was passed in or a modified (possibly new) instance
+ */
+ @Override
+ public Object beforeBodyWrite(Object body, @NotNull MethodParameter arg1, @NotNull MediaType arg2,
+ @NotNull Class extends HttpMessageConverter>> arg3,
+ @NotNull ServerHttpRequest arg4, @NotNull ServerHttpResponse arg5) {
+ String traceId = AppContext.getContext().getTraceId();
+ if (body instanceof ErrorResponse error) {
+ return BaseResponse.fail(error.getCode(), error.getMessage(), traceId);
+ } else if (body instanceof BaseResponse baseResponse) {
+ baseResponse.setTraceId(traceId);
+ return body;
+ } else if (body instanceof String) {
+ return BaseResponse.success(body, traceId);
+ }
+
+ return BaseResponse.success(body, traceId);
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/FilterConfig.java b/common/common-framework/src/main/java/com/yxx/framework/config/FilterConfig.java
new file mode 100644
index 0000000..b914cf0
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/config/FilterConfig.java
@@ -0,0 +1,29 @@
+package com.yxx.framework.config;
+
+import com.yxx.framework.filter.RepeatableFilter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+
+/**
+ *
+ * @author yxx
+ * @since 2022/4/13 17:30
+ */
+@Configuration
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+public class FilterConfig {
+ @Bean
+ public FilterRegistrationBean someFilterRegistration() {
+ FilterRegistrationBean registration = new FilterRegistrationBean<>();
+ registration.setFilter(new RepeatableFilter());
+ registration.addUrlPatterns("/*");
+ registration.setName("repeatableFilter");
+ registration.setOrder(Ordered.LOWEST_PRECEDENCE);
+ return registration;
+ }
+
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/WebConfigurer.java b/common/common-framework/src/main/java/com/yxx/framework/config/WebConfigurer.java
new file mode 100644
index 0000000..8934600
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/config/WebConfigurer.java
@@ -0,0 +1,92 @@
+package com.yxx.framework.config;
+
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
+import cn.dev33.satoken.stp.StpLogic;
+import com.yxx.framework.interceptor.response.ResponseResultInterceptor;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+/**
+ * 注册拦截器
+ *
+ * @author zhanglf
+ * @since 2022/11/12 03:21
+ */
+@Configuration
+public class WebConfigurer implements WebMvcConfigurer {
+
+
+ private final ResponseResultInterceptor interceptor;
+
+ @Autowired(required = false)
+ public WebConfigurer(ResponseResultInterceptor interceptor) {
+ this.interceptor = interceptor;
+ }
+
+ /**
+ * Sa-Token 整合 jwt (Style模式)
+ *
+ * @return 权限认证,逻辑实现类
+ */
+ @Bean
+ public StpLogic getStpLogicJwt() {
+ return new StpLogicJwtForSimple();
+ }
+
+ /**
+ * 用来注册拦截器,拦截器需要通过这里添加注册才能生效
+ *
+ * @param registry 拦截器
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // addPathPatterns("/**") 表示拦截所有的请求,
+ // excludePathPatterns("/login", "/register") 表示除了登录与注册之外,因为登录注册不需要登录也可以访问
+ registry.addInterceptor(interceptor).addPathPatterns("/**");
+ // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
+ registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
+ }
+
+ @Override
+ public void extendMessageConverters(List> converters) {
+ // 删除springboot默认的StringHttpMessageConverter解析器
+ // 不删除的话,ResponseResultHandler类的beforeBodyWrite方法解析封装String时会出现转换异常
+ converters.removeIf(StringHttpMessageConverter.class::isInstance);
+ }
+
+ /**
+ * 配置静态资源,比如html,js,css,等等
+ *
+ * @param registry registry
+ */
+ @Override
+ public void addResourceHandlers(@NotNull ResourceHandlerRegistry registry) {
+ // 配置静态资源,比如html,js,css,等等
+ }
+
+ /**
+ * 跨域配置
+ *
+ * @param registry 跨域注册表
+ */
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**")
+ // SpringBoot2.4.0 [allowedOriginPatterns]代替[allowedOrigins]
+ .allowedOriginPatterns("*")
+ .allowedMethods("*")
+ .maxAge(3600)
+ .allowCredentials(true);
+ }
+}
\ No newline at end of file
diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/mybatis/MyBatisPlusConfig.java b/common/common-framework/src/main/java/com/yxx/framework/config/mybatis/MyBatisPlusConfig.java
new file mode 100644
index 0000000..472c7dd
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/config/mybatis/MyBatisPlusConfig.java
@@ -0,0 +1,33 @@
+package com.yxx.framework.config.mybatis;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.yxx.framework.hander.CommonMetaObjectHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author yxx
+ */
+@Configuration
+public class MyBatisPlusConfig {
+ /**
+ * 分页插件
+ */
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+ return interceptor;
+ }
+
+ /**
+ * 自动填充参数
+ */
+ @Bean
+ public CommonMetaObjectHandler commonMetaObjectHandler() {
+ return new CommonMetaObjectHandler();
+ }
+
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/config/redis/RedisConfig.java b/common/common-framework/src/main/java/com/yxx/framework/config/redis/RedisConfig.java
new file mode 100644
index 0000000..24adb75
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/config/redis/RedisConfig.java
@@ -0,0 +1,53 @@
+package com.yxx.framework.config.redis;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * @author yxx
+ * @description Redis相关配置
+ */
+@EnableCaching
+@Configuration
+public class RedisConfig extends CachingConfigurerSupport {
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+ RedisSerializer serializer = redisSerializer();
+ RedisTemplate redisTemplate = new RedisTemplate<>();
+ redisTemplate.setConnectionFactory(redisConnectionFactory);
+ redisTemplate.setKeySerializer(new StringRedisSerializer());
+ redisTemplate.setValueSerializer(serializer);
+ redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+ redisTemplate.setHashValueSerializer(serializer);
+ redisTemplate.afterPropertiesSet();
+ return redisTemplate;
+ }
+
+ @Bean
+ public RedisSerializer redisSerializer() {
+ //创建JSON序列化器
+ Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+ objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
+ ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
+ serializer.setObjectMapper(objectMapper);
+ return serializer;
+ }
+
+
+}
+
diff --git a/common/common-framework/src/main/java/com/yxx/framework/context/AppContext.java b/common/common-framework/src/main/java/com/yxx/framework/context/AppContext.java
new file mode 100644
index 0000000..3535731
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/context/AppContext.java
@@ -0,0 +1,41 @@
+package com.yxx.framework.context;
+
+import com.alibaba.ttl.TransmittableThreadLocal;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+public class AppContext implements Serializable {
+ public static final String KEY_TRACE_ID = "Trace-Id";
+ @Serial
+ private static final long serialVersionUID = -979220111440953115L;
+
+ private String traceId;
+
+ private static final TransmittableThreadLocal LOCAL = new TransmittableThreadLocal<>() {
+ @Override
+ protected AppContext initialValue() {
+ return new AppContext();
+ }
+ };
+
+ public static AppContext getContext() {
+ return LOCAL.get();
+ }
+
+ public static void setContext(AppContext context) {
+ LOCAL.set(context);
+ }
+
+ public static void removeContext() {
+ LOCAL.remove();
+ }
+
+ public String getTraceId() {
+ return traceId;
+ }
+
+ public void setTraceId(String traceId) {
+ this.traceId = traceId;
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/filter/RepeatableFilter.java b/common/common-framework/src/main/java/com/yxx/framework/filter/RepeatableFilter.java
new file mode 100644
index 0000000..bbd9e16
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/filter/RepeatableFilter.java
@@ -0,0 +1,47 @@
+package com.yxx.framework.filter;
+
+import cn.hutool.core.text.CharSequenceUtil;
+import com.yxx.common.utils.ApplicationUtils;
+import com.yxx.common.utils.RepeatedlyRequestWrapper;
+import com.yxx.common.utils.SnowflakeConfig;
+import com.yxx.framework.context.AppContext;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.MediaType;
+
+import java.io.IOException;
+
+/**
+ * Repeatable过滤器
+ *
+ * @author yxx
+ * @since 2022/4/13 17:29
+ */
+@Slf4j
+public class RepeatableFilter implements Filter {
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ ServletRequest requestWrapper = null;
+ if (request instanceof HttpServletRequest httpServletRequest
+ && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
+ requestWrapper = new RepeatedlyRequestWrapper(httpServletRequest, response);
+ String traceId = httpServletRequest.getHeader("Trace-Id");
+ if (CharSequenceUtil.isNotBlank(traceId)) {
+ log.info(traceId);
+ } else {
+ SnowflakeConfig snowflake = ApplicationUtils.getBean(SnowflakeConfig.class);
+ AppContext.getContext().setTraceId(String.valueOf(snowflake.snowflakeId()));
+ }
+ }
+ if (null == requestWrapper) {
+ chain.doFilter(request, response);
+ } else {
+ chain.doFilter(requestWrapper, response);
+ }
+ }
+
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/hander/CommonMetaObjectHandler.java b/common/common-framework/src/main/java/com/yxx/framework/hander/CommonMetaObjectHandler.java
new file mode 100644
index 0000000..797caae
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/hander/CommonMetaObjectHandler.java
@@ -0,0 +1,68 @@
+package com.yxx.framework.hander;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.yxx.common.utils.auth.LoginAdminUtils;
+import com.yxx.common.utils.auth.LoginUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.beans.factory.annotation.Value;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author yxx
+ * @since 2022/4/13 11:23
+ */
+@Slf4j
+public class CommonMetaObjectHandler implements MetaObjectHandler {
+ @Value("${app.name}")
+ private String appName;
+
+ /**
+ * 创建人
+ */
+ private static final String CREATE_UID = "createUid";
+
+ /**
+ * 更新人
+ */
+ private static final String UPDATE_UID = "updateUid";
+
+ /**
+ * 创建时间
+ */
+ private static final String CREATE_TIME = "createTime";
+
+ /**
+ * 修改时间
+ */
+ private static final String UPDATE_TIME = "updateTime";
+
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ Long uid = currentUid();
+ strictInsertFill(metaObject, CREATE_UID, Long.class, uid);
+ strictInsertFill(metaObject, UPDATE_UID, Long.class, uid);
+ strictInsertFill(metaObject, CREATE_TIME, LocalDateTime.class, LocalDateTime.now());
+ strictInsertFill(metaObject, UPDATE_TIME, LocalDateTime.class, LocalDateTime.now());
+ }
+
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ strictUpdateFill(metaObject, UPDATE_UID, Long.class, currentUid());
+ strictUpdateFill(metaObject, UPDATE_TIME, LocalDateTime.class, LocalDateTime.now());
+ }
+
+ public Long currentUid() {
+ try {
+ if ("user".equals(appName)) {
+ return LoginUtils.getUserId();
+ } else {
+ return LoginAdminUtils.getUserId();
+ }
+ } catch (Exception ignore) {
+ log.error("生成uid错误");
+ }
+ return 1L;
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/interceptor/log/LogAspect.java b/common/common-framework/src/main/java/com/yxx/framework/interceptor/log/LogAspect.java
new file mode 100644
index 0000000..f94a275
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/interceptor/log/LogAspect.java
@@ -0,0 +1,300 @@
+package com.yxx.framework.interceptor.log;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.yxx.common.annotation.log.OperationLog;
+import com.yxx.common.constant.Constant;
+import com.yxx.common.constant.EmailSubjectConstant;
+import com.yxx.common.constant.RedisConstant;
+import com.yxx.common.core.model.LogDTO;
+import com.yxx.common.core.model.LoginUser;
+import com.yxx.common.enums.LogTypeEnum;
+import com.yxx.common.properties.IpProperties;
+import com.yxx.common.properties.MailProperties;
+import com.yxx.common.properties.MyWebProperties;
+import com.yxx.common.utils.ServletUtils;
+import com.yxx.common.utils.agent.UserAgentUtil;
+import com.yxx.common.utils.auth.LoginUtils;
+import com.yxx.common.utils.email.MailUtils;
+import com.yxx.common.utils.ip.AddressUtil;
+import com.yxx.common.utils.ip.IpUtil;
+import com.yxx.common.utils.redis.RedissonCache;
+import com.yxx.framework.context.AppContext;
+import com.yxx.framework.service.OperationLogService;
+import com.yxx.framework.service.impl.OperationLogDefaultServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.*;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @author yxx
+ * @since 2022/11/12 03:21
+ */
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class LogAspect {
+ private final MailUtils mailUtils;
+
+ private final MailProperties mailProperties;
+
+ private final RedissonCache redissonCache;
+
+ private final IpProperties ipProperties;
+
+ private final MyWebProperties myWebProperties;
+
+ /**
+ * 用来记录请求进入的时间,防止多线程时出错,这里用了ThreadLocal
+ */
+ ThreadLocal startTime = new ThreadLocal<>();
+
+ private OperationLogService logService;
+
+ /**
+ * 定义切入点,controller下面的所有类的所有公有方法
+ */
+ @Pointcut("execution(public * com.yxx..controller.*.*(..))")
+ public void requestLog() {
+ // document why this method is empty
+ }
+
+ /**
+ * 方法之前执行,日志打印请求信息
+ *
+ * @param joinPoint joinPoint
+ */
+ @Before("requestLog()")
+ public void doBefore(JoinPoint joinPoint) {
+ MDC.put(AppContext.KEY_TRACE_ID, AppContext.getContext().getTraceId());
+ startTime.set(System.currentTimeMillis());
+ ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+ HttpServletRequest request = servletRequestAttributes.getRequest();
+ //打印当前的请求路径
+ log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
+ log.info("RequestMapping:[{}]", request.getRequestURI());
+
+
+ //这里是从token中获取用户信息,打印当前的访问用户,代码不通用
+ if (StpUtil.isLogin()) {
+ Object loginId = StpUtil.getLoginId();
+ log.info("User is:" + loginId);
+ }
+
+ // 打印请求参数,如果需要打印其他的信息可以到request中去拿
+ log.info("RequestParam:{}", Arrays.toString(joinPoint.getArgs()));
+ log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
+ // 获取请求ip
+ String requestIp = IpUtil.getRequestIp();
+ // 判断是否登录
+ boolean isLogin = StpUtil.isLogin();
+ if (isLogin) {
+ LoginUser loginUser = LoginUtils.getLoginUser();
+ log.info("异步前");
+ // 获取agent
+ String requestAgent = request.getHeader("user-agent");
+ CompletableFuture.runAsync(() -> checkIpUnusual(requestIp, loginUser, requestAgent));
+ log.info("异步后");
+ }
+ }
+
+ /**
+ * 方法返回之前执行,打印才返回值以及方法消耗时间
+ *
+ * @param response 返回值
+ */
+ @AfterReturning(returning = "response", pointcut = "requestLog()")
+ public void doAfterRunning(Object response) {
+ try {
+ //打印返回值信息
+ log.info("++++++++++++++++++++++++++++++++++++++++++++");
+ ObjectMapper jsonMapper = new ObjectMapper();
+ jsonMapper.registerModule(new JavaTimeModule());
+ log.info("Response:[{}]", jsonMapper.writeValueAsString(response));
+ //打印请求耗时
+ log.info("Request spend times : [{}ms]", System.currentTimeMillis() - startTime.get());
+ log.info("++++++++++++++++++++++++++++++++++++++++++++");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ startTime.remove();
+ }
+ }
+
+ public OperationLogService getLogService() {
+ try {
+ if (null == logService) {
+ logService = SpringUtil.getBean(OperationLogService.class);
+ }
+ } catch (NoSuchBeanDefinitionException e) {
+ log.warn("Please implement this OperationLogService interface");
+ logService = new OperationLogDefaultServiceImpl();
+ }
+ return logService;
+ }
+
+ @Around("@annotation(operationLog)")
+ @SneakyThrows
+ public Object around(ProceedingJoinPoint point, OperationLog operationLog) {
+ String strClassName = point.getTarget().getClass().getName();
+ String strMethodName = point.getSignature().getName();
+ log.info("[类名]:{},[方法]:{}", strClassName, strMethodName);
+
+ // 方法开始时间
+ Long beginTime = System.currentTimeMillis();
+ LogTypeEnum type = LogTypeEnum.NORMAL;
+ String exception = null;
+ Object obj;
+ try {
+ obj = point.proceed();
+ } catch (Exception e) {
+ type = LogTypeEnum.ERROR;
+ exception = e.getMessage();
+ throw e;
+ } finally {
+ // 结束时间
+ Long endTime = System.currentTimeMillis();
+ Long time = endTime - beginTime;
+ MethodSignature signature = (MethodSignature) point.getSignature();
+ log.info("signature:[{}]", signature);
+ String module = operationLog.module();
+ String title = operationLog.title();
+ String traceId = AppContext.getContext().getTraceId();
+ String spanId = null;
+ LogDTO dto = createLog(module, title, type, time, traceId, spanId, exception);
+ getLogService().saveLog(dto);
+ }
+
+ return obj;
+ }
+
+ private LogDTO createLog(String module, String title, LogTypeEnum logType, Long time,
+ String traceId, String spanId, String exception) {
+ HttpServletRequest request = ServletUtils.getRequest();
+
+ LogDTO logDTO = new LogDTO();
+ logDTO.setModule(module);
+ logDTO.setTitle(title);
+ logDTO.setType(logType.getCode());
+
+ String ip = IpUtil.getRequestIp();
+ log.info("当前IP为{}", ip);
+ if (Boolean.TRUE.equals(ipProperties.getCheck())) {
+ String ipHomePlace = AddressUtil.getIpHomePlace(ip, 2);
+ logDTO.setIpHomePlace(ipHomePlace);
+ }
+ logDTO.setIp(ip);
+ logDTO.setUserAgent(request.getHeader("user-agent"));
+ logDTO.setMethod(request.getMethod());
+ logDTO.setTime(time);
+ logDTO.setException(exception);
+
+ if (StpUtil.isLogin()) {
+ LoginUser loginUser = (LoginUser) StpUtil.getTokenSession().get(Constant.LOGIN_USER_KEY);
+ logDTO.setUserId(loginUser.getId());
+ logDTO.setCreateUid(loginUser.getId());
+ }
+ logDTO.setParams(ServletUtils.getRequestParms(request));
+ logDTO.setRequestUri(request.getRequestURI());
+ logDTO.setTraceId(traceId);
+ logDTO.setSpanId(spanId);
+ return logDTO;
+ }
+
+ /**
+ * 检查ip是否异常
+ *
+ * @param requestIp 请求ip
+ * @author yxx
+ */
+ private void checkIpUnusual(String requestIp, LoginUser loginUser, String requestAgent) {
+ if (Boolean.TRUE.equals(ipProperties.getCheck())) {
+ log.info("开始校验ip是否异常~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ // 如果是有效ip 进行校验
+ if (AddressUtil.isValidIPv4(requestIp) || AddressUtil.isIPv6Address(requestIp)) {
+ // 判断ip是否是ipv6
+ boolean iPv6Address = AddressUtil.isIPv6Address(requestIp);
+ if (iPv6Address) {
+ log.info("该ip:{},为ipv6暂不解析", requestIp);
+ }
+ // 如果已经登录 并且是ipv4 则判断ip是否异常
+ if (AddressUtil.isValidIPv4(requestIp)) {
+ log.info("校验ipv4");
+ // 获取登录时ip属地
+ String ipHomePlace = loginUser.getIpHomePlace();
+ // 登录时设备名称
+ String loginAgent = loginUser.getAgent();
+ // 判断是否发送过ip异常邮件 如果没发送过,进行ip异常校验
+ boolean exists = redissonCache.exists(RedisConstant.IP_UNUSUAL_OPERATE + loginUser.getId());
+ if (!exists) {
+ // 当前操作的ip
+ String currentIpHomePlace = AddressUtil.getIpHomePlace(requestIp, 2);
+ String agent = UserAgentUtil.getAgent(requestAgent);
+ // 判断当前ip归属地和登录时ip归属地是否一致,如果不一致,发送邮件告知用户
+ if (!currentIpHomePlace.equals(ipHomePlace) && !loginAgent.equals(agent)) {
+ log.info("校验完成,ip行为异常");
+ // 邮件正文
+ String unusual = AddressUtil.getIpHomePlace(requestIp, 3);
+ String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN);
+ String emailContent = mailProperties.getIpUnusualContent().replace("{time}", time)
+ .replace("{ip}", requestIp)
+ .replace("{address}", unusual)
+ .replace("{agent}", agent)
+ .replace("{formName}", mailProperties.getFromName())
+ .replace("{form}", mailProperties.getFrom()
+ .replace("{domain}", myWebProperties.getDomain()));
+ // 发送邮件
+ mailUtils.baseSendMail(loginUser.getEmail(), EmailSubjectConstant.IP_UNUSUAL, emailContent, true);
+ // 加入redis(一天提醒一次)
+ // 今天剩余时间
+ Long residueTime = theRestOfTheDaySecond();
+ redissonCache.put(RedisConstant.IP_UNUSUAL_OPERATE + loginUser.getId(), Boolean.TRUE, residueTime);
+ }
+ }
+ }
+ } else {
+ log.info("ip地址:{} 无法解析", requestIp);
+ }
+ log.info("校验ip是否异常结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+ }
+ }
+
+ /**
+ * 今天剩余时间(单位秒)
+ *
+ * @return {@link Long }
+ * @author yxx
+ */
+ public static Long theRestOfTheDaySecond() {
+ // 获取当前日期和时间
+ LocalDateTime now = LocalDateTime.now();
+
+ // 获取今晚的十二点整时间
+ LocalDateTime midnight = now.toLocalDate().atTime(LocalTime.MAX);
+
+ // 计算当前时间距离今晚十二点整的秒数
+ Duration duration = Duration.between(now, midnight);
+ return duration.getSeconds();
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/interceptor/response/ResponseResultInterceptor.java b/common/common-framework/src/main/java/com/yxx/framework/interceptor/response/ResponseResultInterceptor.java
new file mode 100644
index 0000000..3c26444
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/interceptor/response/ResponseResultInterceptor.java
@@ -0,0 +1,60 @@
+package com.yxx.framework.interceptor.response;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.yxx.common.annotation.auth.ReleaseToken;
+import com.yxx.common.annotation.response.ResponseResult;
+import com.yxx.common.utils.satoken.StpAdminUtil;
+import com.yxx.framework.context.AppContext;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+/**
+ * @author yxx
+ * @since 2022/11/12 03:21
+ */
+@Component
+public class ResponseResultInterceptor implements HandlerInterceptor {
+ /**
+ * 标记名称
+ */
+ public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT";
+
+ @Value("${app.name}")
+ private String appName;
+
+ @Override
+ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
+ @NotNull Object handler) {
+ if (handler instanceof HandlerMethod handlerMethod) {
+ final Class> clazz = handlerMethod.getBeanType();
+ final Method method = handlerMethod.getMethod();
+
+ // 判断是否在类对象上添加了格式化返回结果注解
+ if (clazz.isAnnotationPresent(ResponseResult.class)) {
+ // 设置此请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断
+ request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
+ } else if (method.isAnnotationPresent(ResponseResult.class)) {
+ request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
+ }
+
+ // 判断方法上是否加了放行token校验的注解
+ if ("user".equals(appName) && !method.isAnnotationPresent(ReleaseToken.class)) {
+ StpUtil.checkLogin();
+ }
+
+ if ("admin".equals(appName) && !method.isAnnotationPresent(ReleaseToken.class)) {
+ StpAdminUtil.checkLogin();
+ }
+ }
+ MDC.put(AppContext.KEY_TRACE_ID, AppContext.getContext().getTraceId());
+ return true;
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/listener/MySaTokenListener.java b/common/common-framework/src/main/java/com/yxx/framework/listener/MySaTokenListener.java
new file mode 100644
index 0000000..8ae172a
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/listener/MySaTokenListener.java
@@ -0,0 +1,120 @@
+package com.yxx.framework.listener;
+
+import cn.dev33.satoken.listener.SaTokenListener;
+import cn.dev33.satoken.stp.SaLoginModel;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 自定义侦听器的实现
+ *
+ * @author yxx
+ * @since 2022/11/12 03:21
+ */
+@Slf4j
+@Component
+public class MySaTokenListener implements SaTokenListener {
+ private static final String HALVING_LINE = "--------------------------------------------";
+ /**
+ * 每次登录时触发
+ */
+ @Override
+ public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, 正在登录[{}]端", loginId, loginModel.getDevice());
+ log.info(HALVING_LINE);
+ }
+
+ /**
+ * 每次注销时触发
+ */
+ @Override
+ public void doLogout(String loginType, Object loginId, String tokenValue) {
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, 正在注销登录", loginId);
+ log.info(HALVING_LINE);
+ }
+
+ /**
+ * 每次被踢下线时触发
+ */
+ @Override
+ public void doKickout(String loginType, Object loginId, String tokenValue) {
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, 被踢下线", loginId);
+ log.info(HALVING_LINE);
+ }
+
+ /**
+ * 每次被顶下线时触发
+ */
+ @Override
+ public void doReplaced(String loginType, Object loginId, String tokenValue) {
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, 被顶下线,token值为[{}]", loginId, tokenValue);
+ log.info(HALVING_LINE);
+ }
+
+ /**
+ * 每次被封禁时触发
+ */
+ @Override
+ public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
+ long day = disableTime / 86400L;
+ long hour = (disableTime % 86400) / 3600L;
+ long minute = (disableTime % 86400) % 3600L / 60L;
+ long second = (disableTime % 86400) % 3600L % 60L;
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, 被封禁账号,封禁时间为[{}]天[{}]小时[{}]分钟[{}]秒", loginId, day, hour, minute, second);
+ log.info(HALVING_LINE);
+ }
+
+ /**
+ * 每次被解封时触发
+ */
+ @Override
+ public void doUntieDisable(String loginType, Object loginId, String service) {
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, 已经解封", loginId);
+ log.info(HALVING_LINE);
+ }
+
+ /** 每次二级认证时触发 */
+ @Override
+ public void doOpenSafe(String s, String s1, String s2, long l) {
+ log.info("---------- 自定义侦听器实现 doOpenSafe");
+ }
+
+ /** 每次退出二级认证时触发 */
+ @Override
+ public void doCloseSafe(String s, String s1, String s2) {
+ log.info("---------- 自定义侦听器实现 doOpenSafe");
+ }
+
+ /**
+ * 每次创建Session时触发
+ */
+ @Override
+ public void doCreateSession(String id) {
+ log.info("session被创建");
+ }
+
+ /**
+ * 每次注销Session时触发
+ */
+ @Override
+ public void doLogoutSession(String id) {
+ log.info("session被注销");
+ }
+
+ /**
+ * 每次Token续期时触发
+ */
+ @Override
+ public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
+ log.info(HALVING_LINE);
+ log.info("ID为:[{}]的用户, token被续期,续期时间为[{}]秒", loginId, timeout);
+ log.info(HALVING_LINE);
+ }
+}
+
diff --git a/common/common-framework/src/main/java/com/yxx/framework/service/OperationLogService.java b/common/common-framework/src/main/java/com/yxx/framework/service/OperationLogService.java
new file mode 100644
index 0000000..159f6e6
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/service/OperationLogService.java
@@ -0,0 +1,19 @@
+package com.yxx.framework.service;
+
+
+import com.yxx.common.core.model.LogDTO;
+
+/**
+ *
+ * @author yxx
+ * @since 2022/7/26 11:29
+ */
+public interface OperationLogService {
+
+ /**
+ * 保存操作日志
+ *
+ * @param dto LogDTO
+ */
+ void saveLog(LogDTO dto);
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/service/impl/OperationLogDefaultServiceImpl.java b/common/common-framework/src/main/java/com/yxx/framework/service/impl/OperationLogDefaultServiceImpl.java
new file mode 100644
index 0000000..3efc1ea
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/service/impl/OperationLogDefaultServiceImpl.java
@@ -0,0 +1,20 @@
+package com.yxx.framework.service.impl;
+
+import com.yxx.common.core.model.LogDTO;
+import com.yxx.common.utils.jackson.JacksonUtil;
+import com.yxx.framework.service.OperationLogService;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ *
+ * @author yxx
+ * @since 2022/7/26 14:44
+ */
+@Slf4j
+public class OperationLogDefaultServiceImpl implements OperationLogService {
+
+ @Override
+ public void saveLog(LogDTO dto) {
+ log.info("操作日志====> {}", JacksonUtil.toJson(dto));
+ }
+}
diff --git a/common/common-framework/src/main/java/com/yxx/framework/service/impl/SaInterfaceImpl.java b/common/common-framework/src/main/java/com/yxx/framework/service/impl/SaInterfaceImpl.java
new file mode 100644
index 0000000..ebad958
--- /dev/null
+++ b/common/common-framework/src/main/java/com/yxx/framework/service/impl/SaInterfaceImpl.java
@@ -0,0 +1,31 @@
+package com.yxx.framework.service.impl;
+
+import cn.dev33.satoken.stp.StpInterface;
+import com.yxx.common.core.model.LoginUser;
+import com.yxx.common.utils.auth.LoginUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author yxx
+ * @since 2022/4/13 14:21
+ */
+@Component
+public class SaInterfaceImpl implements StpInterface {
+
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ LoginUser loginUser = LoginUtils.getLoginUser();
+ List permissions = loginUser.getMenuPermission();
+ permissions.addAll(loginUser.getButtonPermission());
+ return new ArrayList<>(permissions);
+ }
+
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ LoginUser loginUser = LoginUtils.getLoginUser();
+ return new ArrayList<>(loginUser.getRolePermission());
+ }
+}
diff --git a/common/pom.xml b/common/pom.xml
new file mode 100644
index 0000000..1826d94
--- /dev/null
+++ b/common/pom.xml
@@ -0,0 +1,19 @@
+
+
+ 4.0.0
+ common
+ pom
+ common
+
+
+ base
+ com.yxx
+ 1.0.0
+
+
+ common-core
+ common-framework
+
+
+
diff --git a/db/db.sql b/db/db.sql
new file mode 100644
index 0000000..aa75e96
--- /dev/null
+++ b/db/db.sql
@@ -0,0 +1,208 @@
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for admin_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `admin_menu`;
+CREATE TABLE `admin_menu` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `parent_id` int DEFAULT NULL COMMENT '父id。顶级节点为null',
+ `menu_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单标识',
+ `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除: 0- 否; 1- 是',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-菜单表';
+
+-- ----------------------------
+-- Table structure for admin_role
+-- ----------------------------
+DROP TABLE IF EXISTS `admin_role`;
+CREATE TABLE `admin_role` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '业务主键',
+ `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '编码',
+ `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
+ `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除',
+ `update_time` datetime NOT NULL COMMENT '修改时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-角色表';
+
+-- ----------------------------
+-- Table structure for admin_role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `admin_role_menu`;
+CREATE TABLE `admin_role_menu` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `role_id` int NOT NULL COMMENT '角色id',
+ `menu_id` int NOT NULL COMMENT '菜单id',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-否;1-是',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-角色—菜单关联表';
+
+-- ----------------------------
+-- Table structure for admin_user
+-- ----------------------------
+DROP TABLE IF EXISTS `admin_user`;
+CREATE TABLE `admin_user` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `login_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号',
+ `login_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称',
+ `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
+ `link_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '联系号码',
+ `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '邮箱',
+ `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地',
+ `agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登录设备名称',
+ `student_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '学号',
+ `update_time` datetime NOT NULL COMMENT '修改时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `create_uid` bigint NOT NULL COMMENT '创建人',
+ `update_uid` bigint NOT NULL COMMENT '修改人',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-用户表';
+
+-- ----------------------------
+-- Table structure for admin_user_role
+-- ----------------------------
+DROP TABLE IF EXISTS `admin_user_role`;
+CREATE TABLE `admin_user_role` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '业务主键',
+ `user_id` bigint NOT NULL COMMENT '用户id',
+ `role_id` int NOT NULL COMMENT '角色id',
+ `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除',
+ `update_time` datetime NOT NULL COMMENT '修改时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='管理段-用户角色关联表';
+
+-- ----------------------------
+-- Table structure for menu
+-- ----------------------------
+DROP TABLE IF EXISTS `menu`;
+CREATE TABLE `menu` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `parent_id` int DEFAULT NULL COMMENT '父id。顶级节点为null',
+ `menu_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单标识',
+ `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除: 0- 否; 1- 是',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单表';
+
+-- ----------------------------
+-- Table structure for operate_admin_log
+-- ----------------------------
+DROP TABLE IF EXISTS `operate_admin_log`;
+CREATE TABLE `operate_admin_log` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `user_id` bigint DEFAULT NULL COMMENT '用户id',
+ `type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '类型:1-正常日志 2-异常日志',
+ `module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作模块',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '日志标题',
+ `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作IP',
+ `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地',
+ `user_agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户代理',
+ `request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求URI',
+ `method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作方式',
+ `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '操作提交的数据',
+ `trace_id` bigint DEFAULT NULL COMMENT 'tLog中的traceId',
+ `span_id` int DEFAULT NULL COMMENT 'tLog中的spanId',
+ `time` bigint DEFAULT NULL COMMENT '执行时间',
+ `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '异常信息',
+ `create_uid` bigint DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作日志表';
+
+-- ----------------------------
+-- Table structure for operate_log
+-- ----------------------------
+DROP TABLE IF EXISTS `operate_log`;
+CREATE TABLE `operate_log` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `user_id` bigint DEFAULT NULL COMMENT '用户id',
+ `type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '类型:1-正常日志 2-异常日志',
+ `module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作模块',
+ `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '日志标题',
+ `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作IP',
+ `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地',
+ `user_agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户代理',
+ `request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '请求URI',
+ `method` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作方式',
+ `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '操作提交的数据',
+ `trace_id` bigint DEFAULT NULL COMMENT 'tLog中的traceId',
+ `span_id` int DEFAULT NULL COMMENT 'tLog中的spanId',
+ `time` bigint DEFAULT NULL COMMENT '执行时间',
+ `exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '异常信息',
+ `create_uid` bigint DEFAULT NULL COMMENT '创建人',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作日志表';
+
+-- ----------------------------
+-- Table structure for role
+-- ----------------------------
+DROP TABLE IF EXISTS `role`;
+CREATE TABLE `role` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '业务主键',
+ `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '编码',
+ `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
+ `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
+ `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除',
+ `update_time` datetime NOT NULL COMMENT '修改时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表';
+
+-- ----------------------------
+-- Table structure for role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `role_menu`;
+CREATE TABLE `role_menu` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
+ `role_id` int NOT NULL COMMENT '角色id',
+ `menu_id` int NOT NULL COMMENT '菜单id',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-否;1-是',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色—菜单关联表';
+
+-- ----------------------------
+-- Table structure for user
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `login_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号',
+ `login_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名称',
+ `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
+ `link_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '联系号码',
+ `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '邮箱',
+ `ip_home_place` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'ip归属地',
+ `agent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '登录设备名称',
+ `student_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '学号',
+ `update_time` datetime NOT NULL COMMENT '修改时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ `create_uid` bigint NOT NULL COMMENT '创建人',
+ `update_uid` bigint NOT NULL COMMENT '修改人',
+ `is_delete` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
+
+-- ----------------------------
+-- Table structure for user_role
+-- ----------------------------
+DROP TABLE IF EXISTS `user_role`;
+CREATE TABLE `user_role` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '业务主键',
+ `user_id` bigint NOT NULL COMMENT '用户id',
+ `role_id` int NOT NULL COMMENT '角色id',
+ `is_delete` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '是否删除:0-未删除;1-已删除',
+ `update_time` datetime NOT NULL COMMENT '修改时间',
+ `create_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户角色关联表';
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..af97d6a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,159 @@
+
+
+ 4.0.0
+ com.yxx
+ base
+ 1.0.0
+ base-springboot
+ base-springboot
+ pom
+
+
+ common
+ business
+ admin
+
+
+
+ 17
+ UTF-8
+ UTF-8
+ 3.0.2
+ 3.5.3
+ 8.0.30
+ 1.35.0.RC
+ 3.12.0
+ 2.11.1
+ 2.19.0
+ 1.18.28
+ 5.8.21
+ 1.5.27
+ 3.17.0
+ 4.1.32.Final
+ 2.9.3
+ 1.9.3
+ 2.6.4
+ 2.6
+ 10.1.5
+ 2.11.4
+
+
+
+
+