From 9742357d7624ce611894acce432ef72f37e2a825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AD=8F=E7=81=BF=E6=96=8C?= <15815221751@163.com> Date: Wed, 19 Nov 2025 12:43:59 +0800 Subject: [PATCH] first commit --- .gitignore | 38 + .idea/.gitignore | 8 + .idea/ApifoxUploaderProjectSetting.xml | 6 + .idea/copilot.data.migration.agent.xml | 6 + .idea/copilot.data.migration.ask.xml | 6 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/copilot.data.migration.edit.xml | 6 + .idea/encodings.xml | 22 + .idea/misc.xml | 13 + .idea/uiDesigner.xml | 124 ++ pom.xml | 96 ++ yuxingshi-ssm-common/pom.xml | 28 + .../yuxingshi-ssm-core/pom.xml | 63 ++ .../common/core/constants/CacheConstants.java | 27 + .../core/constants/CommonConstants.java | 23 + .../ssm/common/core/constants/ResultCode.java | 180 +++ .../core/constants/SecurityConstants.java | 38 + .../common/core/constants/TokenConstants.java | 29 + .../common/core/domain/dto/BasePageDTO.java | 41 + .../core/domain/dto/BasePageReqDTO.java | 32 + .../common/core/domain/dto/LoginUserDTO.java | 54 + .../ssm/common/core/domain/vo/R.java | 141 +++ .../ssm/common/core/utils/JsonUtil.java | 240 ++++ .../ssm/common/core/utils/ServletUtil.java | 157 +++ .../ssm/common/core/utils/StringUtil.java | 56 + .../yuxingshi-ssm-intercepter/pom.xml | 38 + .../common/intercepter/config/WebConfig.java | 22 + .../intercepter/filer/AuthInterceptor.java | 37 + ...ot.autoconfigure.AutoConfiguration.imports | 2 + .../yuxingshi-ssm-mybatisplus/pom.xml | 36 + .../mybatisplus/config/MyBatisPlusConfig.java | 25 + .../common/mybatisplus/domain/BaseEntity.java | 21 + .../handler/MyMetaObjectHandler.java | 25 + ...ot.autoconfigure.AutoConfiguration.imports | 2 + .../yuxingshi-ssm-redis/pom.xml | 45 + .../ssm/common/redis/config/RedisConfig.java | 118 ++ .../ssm/common/redis/server/RedisService.java | 1002 +++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 2 + .../yuxingshi-ssm-security/pom.xml | 53 + .../common/security/annotation/ValueIn.java | 21 + .../security/annotation/ValueInValidator.java | 57 + .../ssm/common/security/dto/TokenDto.java | 28 + .../common/security/service/TokenService.java | 171 +++ .../ssm/common/security/utils/JwtUtil.java | 156 +++ .../common/security/utils/SecurityUtil.java | 53 + yuxingshi-ssm-server/pom.xml | 47 + .../main/java/yuxingshi/ssm/server/Main.java | 7 + .../src/main/resources/application.yaml | 0 48 files changed, 3408 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/ApifoxUploaderProjectSetting.xml create mode 100644 .idea/copilot.data.migration.agent.xml create mode 100644 .idea/copilot.data.migration.ask.xml create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/copilot.data.migration.edit.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 pom.xml create mode 100644 yuxingshi-ssm-common/pom.xml create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/pom.xml create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CacheConstants.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CommonConstants.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/ResultCode.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/SecurityConstants.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/TokenConstants.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageDTO.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageReqDTO.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/LoginUserDTO.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/vo/R.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/JsonUtil.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/ServletUtil.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/StringUtil.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-intercepter/pom.xml create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/config/WebConfig.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/filer/AuthInterceptor.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/pom.xml create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/config/MyBatisPlusConfig.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/domain/BaseEntity.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/handler/MyMetaObjectHandler.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-redis/pom.xml create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/config/RedisConfig.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/server/RedisService.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/pom.xml create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueIn.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueInValidator.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/dto/TokenDto.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/service/TokenService.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/JwtUtil.java create mode 100644 yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/SecurityUtil.java create mode 100644 yuxingshi-ssm-server/pom.xml create mode 100644 yuxingshi-ssm-server/src/main/java/yuxingshi/ssm/server/Main.java create mode 100644 yuxingshi-ssm-server/src/main/resources/application.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml new file mode 100644 index 0000000..4c89849 --- /dev/null +++ b/.idea/ApifoxUploaderProjectSetting.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..4ea72a9 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 0000000..7ef04e2 --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..8648f94 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..970f226 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e8d124d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b30fa80 --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm + 1.0-SNAPSHOT + pom + + yuxingshi-ssm-server + yuxingshi-ssm-common + + + + 17 + 17 + UTF-8 + + 3.3.3 + 3.50.0 + 3.5.7 + 4.4 + 2.0.0 + 0.9.1 + 2.3.0 + 5.8.22 + 2.14.4 + 1.0.0 + + test + -Xmx256m + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + javax.xml.bind + jaxb-api + ${jaxb.version} + + + cn.hutool + hutool-all + ${hutool.version} + + + + com.alibaba + transmittable-thread-local + ${transmittable-thread-local.version} + + + + com.a3test.component + idworker-snowflake + ${idworker-snowflake.version} + + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/pom.xml b/yuxingshi-ssm-common/pom.xml new file mode 100644 index 0000000..bc17674 --- /dev/null +++ b/yuxingshi-ssm-common/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm + 1.0-SNAPSHOT + + + yuxingshi-ssm-common + pom + + yuxingshi-ssm-redis + yuxingshi-ssm-core + yuxingshi-ssm-mybatisplus + yuxingshi-ssm-security + yuxingshi-ssm-intercepter + + + + 17 + 17 + UTF-8 + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/pom.xml b/yuxingshi-ssm-common/yuxingshi-ssm-core/pom.xml new file mode 100644 index 0000000..f436f60 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm-common + 1.0-SNAPSHOT + + + yuxingshi.ssm.common.core + yuxingshi-ssm-core + + + 17 + 17 + UTF-8 + + + + + org.projectlombok + lombok + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + org.apache.commons + commons-lang3 + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.springframework + spring-web + + + io.projectreactor + reactor-core + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CacheConstants.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CacheConstants.java new file mode 100644 index 0000000..0703b63 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CacheConstants.java @@ -0,0 +1,27 @@ +package yuxingshi.ssm.common.core.constants; + +/** + * 缓存常量 + */ +public class CacheConstants { + /** + * 缓存分割符 + */ + public final static String CACHE_SPLIT_COLON = ":"; + + + /** + * 登录用户缓存有效期,默认720(分钟) + */ + public final static long LOGIN_USER_EXPIRATION = 720; + + /** + * 缓存刷新时间,默认120(分钟) + */ + public final static long REFRESH_TIME = 120; + + /** + * 用户登录凭证有效期延期临界值 + */ + public static final long LOGIN_USER_EXP_CRITICAL_TIME = 120L; +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CommonConstants.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CommonConstants.java new file mode 100644 index 0000000..8bc382d --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/CommonConstants.java @@ -0,0 +1,23 @@ +package yuxingshi.ssm.common.core.constants; + +/** + * 通用常量 + */ +public class CommonConstants { + /** + * 通用日期格式 + */ + public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + /** + * 默认编码 + */ + public final static String UTF8 = "UTF-8"; + /** + * 默认分隔符 + */ + public final static String DEFAULT_DELIMITER = ", "; + /** + * 空字符串 + */ + public final static String EMPTY_STR = ""; +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/ResultCode.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/ResultCode.java new file mode 100644 index 0000000..44f562b --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/ResultCode.java @@ -0,0 +1,180 @@ +package yuxingshi.ssm.common.core.constants; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ResultCode { + + //---------------------------2xx + + /** + * 操作成功 + */ + SUCCESS (200000, "操作成功"), + + //------------------------4xx + //400 + + /** + * 无效的参数 + */ + INVALID_PARA (400000, "无效的参数"), + /** + * 无效的验证码 + */ + INVALID_CODE (400001, "无效的验证码"), + + /** + * 错误的验证码 + */ + ERROR_CODE (400002, "错误的验证码"), + + /** + * 手机号格式错误 + */ + ERROR_PHONE_FORMAT (400003, "手机号格式错误"), + + /** + * 超过每日发送次数限制 + */ + SEND_MSG_OVERLIMIT (400004, "超过每日发送次数限制"), + + /** + * 无效的区划 + */ + INVALID_REGION (400005, "无效的区划"), + + /** + * 参数类型不匹配 + */ + PARA_TYPE_MISMATCH (400006, "参数类型不匹配"), + + /** + * 账号已停用,登录失败 + */ + USER_DISABLE (400007, "账号已停用,登录失败"), + + /** + * 请求参数超过最大限制 + */ + INVALID_PARA_MAX_SIZE (4000008,"请求参数超过最大限制"), + + + /** + * 手机号已经被注册 + */ + PHONE_ALREADY_REGISTER (4000009,"手机号已经被注册"), + + + /** + * 用户不存在 + */ + USER_NOT_EXIST (4000010,"用户不存在"), + + + /** + * 解密失败 + */ + DECODE_FAIL (4000011,"解密失败"), + + + /** + * 用户身份不合法 + */ + USER_IDENTITY_ILLEGAL (4000012,"用户身份不合法"), + + + /** + * 用户状态不合法 + */ + USER_STATUS_ILLEGAL (4000013,"用户状态不合法"), + + + + + /** + * 密码格式不通过 + */ + PASSWORD_FORMAT_INVALID (4000015,"密码格式不通过"), + + + //401 + + /** + * 令牌不能为空 + */ + TOKEN_EMPTY (401000, "令牌不能为空"), + + /** + * 令牌已过期或验证不正确! + */ + TOKEN_INVALID (401001, "令牌已过期或验证不正确!"), + + /** + * 令牌已过期! + */ + TOKEN_OVERTIME (401002, "令牌已过期!"), + + /** + * 登录状态已过期! + */ + LOGIN_STATUS_OVERTIME (401003, "登录状态已过期!"), + + /** + * 令牌验证失败! + */ + TOKEN_CHECK_FAILED (401004, "令牌验证失败!"), + + // 403 + NOT_PERMISSION (403001,"您没有权限!"), + + //404 + + /** + * 服务未找到! + */ + SERVICE_NOT_FOUND (404000, "服务未找到"), + + URL_NOT_FOUND (404001, "url未找到"), + + //405 + + /** + * 请求方法不支持! + */ + REQUEST_METNHOD_NOT_SUPPORTED (405000, "请求方法不支持"), + + + //---------------------5xx + + /** + * 服务繁忙请稍后重试! + */ + ERROR (500000, "服务繁忙请稍后重试"), + + /** + * 操作失败 + */ + FAILED (500001, "操作失败"), + + + + + //---------------------枚举占位 + /** + * 占位专用 + */ + RESERVED (99999999, "占位专用"); + + /** + * 响应码 + */ + private final int code; + + /** + * 响应消息 + */ + private final String msg; +} \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/SecurityConstants.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/SecurityConstants.java new file mode 100644 index 0000000..367e241 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/SecurityConstants.java @@ -0,0 +1,38 @@ +package yuxingshi.ssm.common.core.constants; + +/** + * 安全相关的常量类 + */ +public class SecurityConstants { + + /** + * 用户标识 + */ + public static final String USER_KEY = "user_key"; + + /** + * 用户ID + */ + public static final String USER_ID = "user_id"; + + /** + * 用户来源 + */ + public static final String USER_FROM = "user_from"; + + + /** + * 用户昵称 + */ + public static final String USERNAME = "username"; + + + /** + * 用户身份 + */ + public static final String USER_IDENTITY = "user_identity"; + /** + * 授权信息字段 + */ + public static final String AUTHENTICATION = "authorization"; +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/TokenConstants.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/TokenConstants.java new file mode 100644 index 0000000..be9f6f2 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/constants/TokenConstants.java @@ -0,0 +1,29 @@ +package yuxingshi.ssm.common.core.constants; + +/** + * 存放token相关的常量 + */ +public class TokenConstants { + /** + * 令牌密钥 + */ + public static final String SECRET = "jyuxingshiabcdefghijklmnopqrstuvwxyz"; + + /** + * 令牌前缀 + */ + public static final String PREFIX = "Bearer "; + + /** + * 缓存的token key + */ + public static final String LOGIN_TOKEN_KEY = "logintoken:"; + + + /** + * 缓存信息分割符号 + */ + + public static final String LOGIN_CACHE_INGO_SPLIT = "-"; + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageDTO.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageDTO.java new file mode 100644 index 0000000..1f36d6d --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageDTO.java @@ -0,0 +1,41 @@ +package yuxingshi.ssm.common.core.domain.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 响应结果分页报文 + */ +@Data +public class BasePageDTO { + /** + * 查询结果总数 + */ + Integer totals; + + /** + * 总页数 + */ + Integer totalPages; + + /** + * 数据列表 + */ + List list; + + /** + * 计算总页数 + * + * @param totals 总数量 + * @param pageSize 页大小 + * @return 页数 + */ + public static int calculateTotalPages(long totals, int pageSize) { + if (pageSize <= 0) { + throw new IllegalArgumentException("Page size must be greater than 0."); + } + return (int) Math.ceil((double) totals / pageSize); + } + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageReqDTO.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageReqDTO.java new file mode 100644 index 0000000..dadfac2 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/BasePageReqDTO.java @@ -0,0 +1,32 @@ +package yuxingshi.ssm.common.core.domain.dto; + +import jakarta.validation.constraints.Max; +import lombok.Data; + +/** + * 分页查询基类DTO + */ +@Data +public class BasePageReqDTO { + + /** + * 分页编码 + */ + private Integer pageNo = 1; + + /** + * 分页数量 + */ + @Max(20) + private Integer pageSize = 10; + + /** + * 获取偏移 + * + * @return 偏移信息 + */ + public Integer getOffset() { + return (pageNo - 1) * pageSize; + } + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/LoginUserDTO.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/LoginUserDTO.java new file mode 100644 index 0000000..76e7409 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/dto/LoginUserDTO.java @@ -0,0 +1,54 @@ +package yuxingshi.ssm.common.core.domain.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * 登录用户信息 + */ +@Getter +@Setter +@ToString +public class LoginUserDTO { + /** + * 用户标识 + */ + private String userKey; + + + /** + * 用户Id + */ + private String userId; + + /** + * 用户来源 + */ + private String userFrom; + + + /** + * 用户名 + */ + private String userName; + + + /** + * 用户身份 + */ + private Integer userIdentity; + + + + /** + * 登录时间 + */ + private Long loginTime; + + + /** + * 过期时间 + */ + private Long expireTime; +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/vo/R.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/vo/R.java new file mode 100644 index 0000000..498ff09 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/domain/vo/R.java @@ -0,0 +1,141 @@ +package yuxingshi.ssm.common.core.domain.vo; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import yuxingshi.ssm.common.core.constants.ResultCode; + +/** + * 响应报文封装 + * + * @param 响应数据 + */ +@Getter +@Setter +@ToString +public class R { + + /** + * 响应码 + */ + private int code; + + /** + * 消息 + */ + private String msg; + + /** + * 数据 + */ + private T data; + + /** + * 成功响应 + * + * @return 响应报文 + * @param 数据类型 + */ + public static R ok() { + return restResult(null, ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg()); + } + + /** + * 成功响应 + * @param data 响应数据 + * @return 响应报文 + * @param 数据类型 + */ + public static R ok(T data) { + return restResult(data, ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg()); + } + + /** + * 成功响应 + * @param data 响应数据 + * @param msg 响应消息 + * @return 响应报文 + * @param 数据类型 + */ + public static R ok(T data, String msg) { + return restResult(data, ResultCode.SUCCESS.getCode(), msg); + } + + /** + * 失败响应 + * @return 响应报文 + * @param 数据类型 + */ + public static R fail() { + return restResult(null, ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg()); + } + + /** + * 失败响应 + * @param msg 响应消息 + * @return 响应报文 + * @param 数据类型 + */ + public static R fail(String msg) { + return restResult(null, ResultCode.ERROR.getCode(), msg); + } + + /** + * 失败响应 + * @param code 响应码 + * @param msg 响应消息 + * @return 响应报文 + * @param 数据类型 + */ + public static R fail(Integer code,String msg) { + return restResult(null, code, msg); + } + + /** + * 失败响应 + * @param data 响应数据 + * @return 响应报文 + * @param 数据类型 + */ + public static R fail(T data) { + return restResult(data, ResultCode.ERROR.getCode(), ResultCode.ERROR.getMsg()); + } + + /** + * 失败响应 + * @param data 响应数据 + * @param msg 响应消息 + * @return 响应报文 + * @param 数据类型 + */ + public static R fail(T data, String msg) { + return restResult(data, ResultCode.ERROR.getCode(), msg); + } + + /** + * 失败响应 + * @param code 响应编码 + * @param msg 响应消息 + * @return 响应报文 + * @param 数据类型 + */ + public static R fail(int code, String msg) { + return restResult(null, code, msg); + } + + /** + * 响应结果 + * @param data 响应数据 + * @param code 响应编码 + * @param msg 响应消息 + * @return 响应报文 + * @param 数据类型 + */ + private static R restResult(T data, int code, String msg) { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/JsonUtil.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/JsonUtil.java new file mode 100644 index 0000000..1ea7f9d --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/JsonUtil.java @@ -0,0 +1,240 @@ +package yuxingshi.ssm.common.core.utils; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +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.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import yuxingshi.ssm.common.core.constants.CommonConstants; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * JSON工具类 + * 提供: + * 1. 对象转JSON + * 2. JSON转对象 + */ +@Slf4j +public class JsonUtil { + + /** + * 对象转换器 + */ + private static ObjectMapper OBJECT_MAPPER; + + /** + * init... + * 1. .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + * 在反序列化时,当遇到 JSON 数据中存在 Java 对象对应类中没有定义的属性时,默认情况下 + * Jackson 会抛出异常。通过将这个配置项设置为 false,表示允许在反序列化时忽略那些未知的属 + * 性,不会因为出现额外的属性而导致反序列化失败,增强了对不同来源 JSON 数据的兼容性。 + * 例如: Student:name, 如果传进来的JSON为Student:name,sex,但是sex并不存在,那么设置为false将忽略sex + *
+ * 2. .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + * 在序列化时,如果Java 对象中包含日期类型默认情况下 Jackson 可能会将日期转换为时间戳来表示。 + * 将此配置设置为false,意味着不采用时间戳的方式来表示日期,而是会按照后续配置的其他日期相关格式进行序列化, + * 以便生成更具可读性的日期格式表示在 JSON 数据中。 + *
+ * 3. .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + * 在序列化时,Java对象中没有任何属性值(为空),默认情况下 Jackson 可能会抛出异常。设置 + * 此项为 false 后,允许对这样的空对象进行序列化,即便对象没有实质内容也能生成对应的 JSON + * 表示(通常可能是一个空的 JSON 对象 {}),避免了因对象为空导致序列化失败的情况。 + *
+ * 4. .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false) + * 在反序列化时,如果 JSON 数据中指定的类型信息与期望的 Java 类型层次结构不匹配(例如类型 + * 标识错误等情况),默认会抛出异常。将这个配置设为 false,可以放宽这种限制,使得在遇到类 + * 型不太准确但仍有可能处理的情况下,尝试继续进行反序列化而不是直接失败,提高对可能存在错 + * 误类型标识的 JSON 数据的容错性。 + *
+ * 5. .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) + * 在序列化时,如果 Java 对象中有以日期类型作为键(比如在 Map 中,键是 Date 类型等情况)的 + * 结构,如果设置为true会将日期键转换为时间戳形式。设置为 false 则按照其他日期相关配置来处 + * 理,保证日期键也以期望的格式进行序列化,使得整个 JSON 结构中日期相关内容都能保持统一的 + * 格式呈现。(默认值为false) + *
+ * 6. .configure(MapperFeature.USE_ANNOTATIONS, false) + * Jackson 支持通过在 Java 类的属性或方法上添加各种注解来定制序列化和反序列化行为。将此配 + * 置设为 false,表示不依赖这些注解来进行相关操作,而是更多地依据全局配置(如上述其他配置 + * 项)来控制序列化和反序列化过程,适用于希望统一管理 JSON 处理规则,减少因注解使用不当或 + * 过多导致的复杂性的场景 + * 7. addModule(new JavaTimeModule()) + * 这是序列化LocalDateTIme和LocalDate的必要配置,由Jackson-data-JSR310实现 + *
+ * 8. .defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT)) + * 所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss + *
+ * 9. .addModule(new SimpleModule().addSerializer(LocalDateTime.class, new + * LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) + * //序列时起作用 + * .addDeserializer(LocalDateTime.class, new + * LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd + * HH:mm:ss"))) //反序列时起作用) + * 对于LocalDateTIme和LocalDate时间类型序列列化时转换为json字符串时可以按照指定格式转换, + * 反序列化时可以也可以将指定的时间字符串类型转换为LocalDateTIme和LocalDate。 + */ + static { + OBJECT_MAPPER = JsonMapper.builder().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false) + // 不适用默认的dateTime进行序列化,使用JSR310的LocalDateTimeSerializer + .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) + .configure(MapperFeature.USE_ANNOTATIONS, true) + // 重点,这是序列化LocalDateTIme和LocalDate的必要配置,由Jackson-data-JSR310实现 + .addModule(new JavaTimeModule()) + .addModule(new SimpleModule() + .addSerializer(LocalDateTime.class, + new LocalDateTimeSerializer( + DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT))) + .addDeserializer(LocalDateTime.class, + new LocalDateTimeDeserializer( + DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT)))) + // 所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss + .defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT)) + // 只针对非空的值进行序列化 + .serializationInclusion(JsonInclude.Include.NON_NULL) + .build(); + } + + /** + * 禁用构造 + */ + private JsonUtil() { + } + + /** + * 对象转Json格式字符串 + * + * @param obj 对象 + * @return Json格式字符串 + * @param 对象类型 + */ + public static String obj2String(T obj) { + if (obj == null) { + return null; + } + try { + return obj instanceof String ? (String) obj : OBJECT_MAPPER.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.warn("Parse Object to String error : {}", e.getMessage()); + return null; + } + } + + /** + * 对象转Json格式字符串(格式化的Json字符串) + * + * @param obj 对象 + * @return 美化的Json格式字符串 + * @param 对象类型 + */ + public static String obj2StringPretty(T obj) { + if (obj == null) { + return null; + } + try { + return obj instanceof String ? (String) obj + : OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.warn("Parse Object to String error : {}", e.getMessage()); + return null; + } + } + + /** + * 字符串转换为自定义对象 + * + * @param str 要转换的字符串 + * @param clazz 自定义对象的class对象 + * @return 自定义对象 + * @param 对象类型 + */ + public static T string2Obj(String str, Class clazz) { + if (str == null || str.isEmpty() || clazz == null) { + return null; + } + try { + return clazz.equals(String.class) ? (T) str : OBJECT_MAPPER.readValue(str, clazz); + } catch (Exception e) { + log.warn("Parse String to Object error : {}", e.getMessage()); + return null; + } + } + + /** + * 字符串转换为自定义对象,支持复杂的泛型嵌套 + * + * @param str json字符串 + * @param valueTypeRef 对象模板信息 + * 使用如 new TypeReference>>() {} + * @return 对象类对应的对象 + * @param 对象类 + */ + public static T string2Obj(String str, TypeReference valueTypeRef) { + if (StringUtils.isEmpty(str) || valueTypeRef == null) { + return null; + } + try { + return OBJECT_MAPPER.readValue(str, valueTypeRef); + } catch (Exception e) { + log.warn("Parse String to Object error : {}", e.getMessage()); + return null; + } + } + + /** + * 字符串转换为自定义字段转为list,支持List嵌套简单对象 + * + * @param str json字符串 + * @param clazz 对象类 + * @return 对象列表 + * @param 对象类型 + */ + public static List string2List(String str, Class clazz) { + if (str == null || str.isEmpty() || clazz == null) { + return null; + } + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz); + try { + return OBJECT_MAPPER.readValue(str, javaType); + } catch (IOException e) { + log.warn("Parse String to List error : {}", e.getMessage()); + return null; + } + } + + /** + * 字符串转换为自定义字段转为map,支持Map嵌套简单对象 + * + * @param str str 字符串信息 + * @param valueClass valueClass value的类别 + * @return map对象 + * @param value 的类型 + */ + public static Map string2Map(String str, Class valueClass) { + if (str == null || str.isEmpty() || valueClass == null) { + return null; + } + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructMapType(LinkedHashMap.class, + String.class, valueClass); + try { + return OBJECT_MAPPER.readValue(str, javaType); + } catch (JsonProcessingException e) { + log.warn("Parse String to Map error : {}", e.getMessage()); + return null; + } + } +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/ServletUtil.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/ServletUtil.java new file mode 100644 index 0000000..6efd2ea --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/ServletUtil.java @@ -0,0 +1,157 @@ +package yuxingshi.ssm.common.core.utils; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import reactor.core.publisher.Mono; +import yuxingshi.ssm.common.core.constants.CommonConstants; +import yuxingshi.ssm.common.core.domain.vo.R; + + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * Servlet工具类 + */ +public class ServletUtil { + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) { + try { + return URLEncoder.encode(str, CommonConstants.UTF8); + } catch (UnsupportedEncodingException e) { + return StringUtils.EMPTY; + } + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param code 响应状态码 + * @param value 响应内容 + * @return 无 + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, Object value, int code) { + return webFluxResponseWriter(response, HttpStatus.OK, value, code); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param status http状态码 + * @param code 响应状态码 + * @param value 响应内容 + * @return 无 + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code) { + //修改了 + return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code); + } + + /** + * 设置webflux模型响应 + * + * @param response ServerHttpResponse + * @param contentType content-type + * @param status http状态码 + * @param code 响应状态码 + * @param value 响应内容 + * @return 无 + */ + public static Mono webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code) { + response.setStatusCode(status); + response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType); + R result = R.fail(code, value.toString()); + DataBuffer dataBuffer = response.bufferFactory().wrap(JsonUtil.obj2String(result).getBytes()); + return response.writeWith(Mono.just(dataBuffer)); + } + + /** + * 获取request + * + * @return request对象 + */ + public static HttpServletRequest getRequest() { + try { + return getRequestAttributes().getRequest(); + } catch (Exception e) { + return null; + } + } + + /** + * 获取request属性信息 + * + * @return request属性 + */ + public static ServletRequestAttributes getRequestAttributes() { + try { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } catch (Exception e) { + return null; + } + } + + + /** + * 请求里面添加Header + * @param mutate 请求 + * @param name 键 + * @param value 值 + */ + public static void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { + if(value == null) { + return; + } + String valueStr = value.toString(); + String valueEncode = urlEncode(valueStr); + mutate.header(name,valueEncode); + } + + /** + * 获取请求IP: + * 用户的真实IP不能使用request.getRemoteAddr() + * 这是因为可能会使用一些代理软件,这样ip获取就不准确了 + * 此外我们如果使用了多级(LVS/Nginx)反向代理的话,ip需要从X-Forwarded-For中获得第一个非unknown的IP才是用户的有效ip。 + * @return + */ + public static String getRequestIp() { + HttpServletRequest request = getRequest(); + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } + + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/StringUtil.java b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/StringUtil.java new file mode 100644 index 0000000..9f98c60 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-core/src/main/java/yuxingshi/ssm/common/core/utils/StringUtil.java @@ -0,0 +1,56 @@ +package yuxingshi.ssm.common.core.utils; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.AntPathMatcher; + +import java.util.List; + +/** + * 字符串工具类 + */ +public class StringUtil extends StringUtils{ + /** + * 判断指定字符串是否与指定匹配规则链表中的任意一个匹配规则匹配 + * + * @param str 指定字符串 + * @param patternList 匹配规则链表 + * @return 是否匹配 + */ + public static boolean matches(String str, List patternList) + { + if (StringUtils.isEmpty(str) || CollectionUtils.isEmpty(patternList)) + { + return false; + } + for (String pattern : patternList) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + /** + * 判断url是否与规则匹配 + * + * 匹配规则: + * 精确匹配 + * 匹配规则中包含 ? 表示任意单个字符; + * 匹配规则中包含 * 表示一层路径内的任意字符串,不可跨层级; + * 匹配规则中包含 ** 表示任意层路径的任意字符,可跨层级 + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return 是否匹配 + */ + public static boolean isMatch(String pattern, String url) + { + if (StringUtils.isEmpty(url) || StringUtils.isEmpty(pattern)) { + return false; + } + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/pom.xml b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/pom.xml new file mode 100644 index 0000000..1b291c8 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm-common + 1.0-SNAPSHOT + + + yuxingshi.ssm.common.intercepter + yuxingshi-ssm-intercepter + + + 17 + 17 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + yuxingshi.ssm.common.core + yuxingshi-ssm-core + 1.0-SNAPSHOT + + + yuxingshi.ssm.common.security + yuxingshi-ssm-security + 1.0-SNAPSHOT + compile + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/config/WebConfig.java b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/config/WebConfig.java new file mode 100644 index 0000000..05e268d --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/config/WebConfig.java @@ -0,0 +1,22 @@ +package yuxingshi.ssm.common.intercepter.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import yuxingshi.ssm.common.intercepter.filer.AuthInterceptor; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Autowired + private AuthInterceptor authInterceptor; + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/**") + .excludePathPatterns("/**/login/**"); + } +} \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/filer/AuthInterceptor.java b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/filer/AuthInterceptor.java new file mode 100644 index 0000000..a6f72b6 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/java/yuxingshi/ssm/common/intercepter/filer/AuthInterceptor.java @@ -0,0 +1,37 @@ +package yuxingshi.ssm.common.intercepter.filer; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import yuxingshi.ssm.common.security.service.TokenService; +import yuxingshi.ssm.common.security.utils.SecurityUtil; + +/** + * 用户身份认证拦截器 + */ +@Slf4j +@Component +public class AuthInterceptor implements HandlerInterceptor { + + + @Autowired + private TokenService tokenService; + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) { + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception ex) { + } + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..c7918ad --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-intercepter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +yuxingshi.ssm.common.intercepter.config.WebConfig +yuxingshi.ssm.common.intercepter.filer.AuthInterceptor \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/pom.xml b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/pom.xml new file mode 100644 index 0000000..3400d52 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm-common + 1.0-SNAPSHOT + + + yuxingshi.ssm.common.mybatisplus + yuxingshi-ssm-mybatisplus + + + 17 + 17 + UTF-8 + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + yuxingshi.ssm.common.core + yuxingshi-ssm-core + 1.0-SNAPSHOT + + + com.a3test.component + idworker-snowflake + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/config/MyBatisPlusConfig.java b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..91fa9c0 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/config/MyBatisPlusConfig.java @@ -0,0 +1,25 @@ +package yuxingshi.ssm.common.mybatisplus.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MyBatisPlus配置类 + */ +@Configuration +public class MyBatisPlusConfig { + + /** + * MyBatisPlus拦截器(用于分页) + */ + @Bean + public MybatisPlusInterceptor paginationInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + //添加MySQL的分页拦截器 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/domain/BaseEntity.java b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/domain/BaseEntity.java new file mode 100644 index 0000000..6096f49 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/domain/BaseEntity.java @@ -0,0 +1,21 @@ +package yuxingshi.ssm.common.mybatisplus.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class BaseEntity { + + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdTime; + + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedTime; +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/handler/MyMetaObjectHandler.java b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/handler/MyMetaObjectHandler.java new file mode 100644 index 0000000..ac48278 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/java/yuxingshi/ssm/common/mybatisplus/handler/MyMetaObjectHandler.java @@ -0,0 +1,25 @@ +package yuxingshi.ssm.common.mybatisplus.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Slf4j +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { ; + + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject,"createdTime", LocalDateTime.class,LocalDateTime.now()); + this.strictInsertFill(metaObject,"updatedTime", LocalDateTime.class,LocalDateTime.now()); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.strictInsertFill(metaObject,"updatedTime", LocalDateTime.class,LocalDateTime.now()); + } + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..8a70168 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-mybatisplus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +yuxingshi.ssm.common.mybatisplus.config.MyBatisPlusConfig +yuxingshi.ssm.common.mybatisplus.handler.MyMetaObjectHandler \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-redis/pom.xml b/yuxingshi-ssm-common/yuxingshi-ssm-redis/pom.xml new file mode 100644 index 0000000..a7fe225 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-redis/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm-common + 1.0-SNAPSHOT + + + yuxingshi.ssm.common.redis + yuxingshi-ssm-redis + + + 17 + 17 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-json + + + + org.redisson + redisson-spring-boot-starter + + + org.projectlombok + lombok + + + yuxingshi.ssm.common.core + yuxingshi-ssm-core + 1.0-SNAPSHOT + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/config/RedisConfig.java b/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/config/RedisConfig.java new file mode 100644 index 0000000..ab5406a --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/config/RedisConfig.java @@ -0,0 +1,118 @@ +package yuxingshi.ssm.common.redis.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import yuxingshi.ssm.common.core.constants.CommonConstants; + +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Configuration +public class RedisConfig { + + private static final Logger log = LoggerFactory.getLogger(RedisConfig.class); + + /** + * 配置RedisTemplate并定义序列化规则 + * + * @param redisConnectionFactory redis连接工厂(由Spring Boot自动注入) + * @return 自定义序列化规则的Redis操作模板 + * + * 关键配置说明: + * 1. setKeySerializer/setHashKeySerializer: + * - 使用StringRedisSerializer序列化String类型的键和Hash的字段名 + * - 确保键可读且兼容所有Redis命令 + * 2. setValueSerializer: + * - 值使用GenericJackson2JsonRedisSerializer进行JSON序列化 + * - 支持复杂对象存储并保留类型信息 + * 3. setHashValueSerializer: + * - Hash类型的值同样使用JSON序列化器 + * 4. afterPropertiesSet(): + * - 初始化模板确保配置生效 + * + * + */ + @Bean(name = "customRedisTemplate") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + log.info("Configure Redis serialization"); + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = + createJacksonSerializer(); + redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); + redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + + + /** + * 创建自定义Jackson序列化器 + * + * @return 配置了安全策略的JSON序列化器 + * + * ObjectMapper关键配置详解: + * 1. FAIL_ON_UNKNOWN_PROPERTIES(false): + * - 反序列化时忽略JSON中的未知字段,增强兼容性 + * 2. WRITE_DATES_AS_TIMESTAMPS(false): + * - 禁用日期转时间戳,使用格式化字符串输出日期 + * 3. FAIL_ON_EMPTY_BEANS(false): + * - 允许序列化空Bean对象而不抛异常 + * 4. FAIL_ON_INVALID_SUBTYPE(false): + * - 忽略无效子类型错误,提高容错性 + * 5. WRITE_DATE_KEYS_AS_TIMESTAMPS(false): + * - Map中的日期键也使用格式化字符串 + * 6. defaultDateFormat(): + * - 统一日期格式为"yyyy-MM-dd HH:mm:ss"标准格式 + * 7. USE_ANNOTATIONS(false): + * - 禁用Jackson注解处理(根据需求可选) + * 8. JavaTimeModule(): + * - 注册Java8时间模块(LocalDateTime等支持) + * 9. serializationInclusion(NON_NULL): + * - 序列化时自动忽略null值字段 + */ + public GenericJackson2JsonRedisSerializer createJacksonSerializer() { + // 创建自定义日期格式 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT); + + // 创建并配置 JavaTimeModule + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); + + ObjectMapper objectMapper = + JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false) + .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false) + .defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT)) + .configure(MapperFeature.USE_ANNOTATIONS, false) + // 注册自定义时间模块 + .addModule(javaTimeModule) + .serializationInclusion(JsonInclude.Include.NON_NULL) + .build(); + + return new GenericJackson2JsonRedisSerializer(objectMapper); + } +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/server/RedisService.java b/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/server/RedisService.java new file mode 100644 index 0000000..2cfe67e --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/java/yuxingshi/ssm/common/redis/server/RedisService.java @@ -0,0 +1,1002 @@ +package yuxingshi.ssm.common.redis.server; + +import com.fasterxml.jackson.core.type.TypeReference; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import yuxingshi.ssm.common.core.utils.JsonUtil; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service +public class RedisService { + + @Qualifier("customRedisTemplate") + @Autowired + private RedisTemplate redisTemplate; + + // ******************基本操作****************** + + /** + * 设置有效时间 (时间单位默认秒) + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 (可指定时间单位) + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true=存在;false=不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 删除单个数据 + * + * @param key 缓存的键值 + * @return 是否成功 true=删除成功;false=删除失败 + */ + + /** + * 根据提供的键模式查找 Redis 中匹配的键 + * + * @param pattern 要查找的键的模式 + * @return 键列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 重命名key + * + * @param oldKey 原来key + * @param newKey 新key + */ + public void renameKey(String oldKey, String newKey) { + redisTemplate.rename(oldKey, newKey); + } + + /** + * 删除单个数据 + * + * @param key 缓存的键值 + * @return 是否成功 true=删除成功;false=删除失败 + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除多个数据 + * + * @param collection 多个数据对应的缓存的键值 + * @return 是否删除了对象 true=删除成功;false=删除失败 + */ + public boolean deleteObject(final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 删除多个数据(重载方法) + * + * @param keys 多个数据对应的缓存的键值列表 + * @return 删除的数量 + */ + public long deleteObjects(final List keys) { + if (keys == null || keys.isEmpty()) { + return 0; + } + return redisTemplate.delete(keys); + } + + // ******************操作String****************** + + /** + * 缓存存储数据,值存储JSON数据 + * + * @param key 键 + * @param value 值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 批量缓存存储数据,值存储JSON数据 + * + * @param keyValueMap 键值对映射 + */ + public void setCacheObjectBatch(final Map keyValueMap, long timeout, TimeUnit unit) { + if (keyValueMap == null || keyValueMap.isEmpty()) { + return; // 空映射直接返回,避免无效操作 + } + + // 使用Pipeline批量操作提升性能 + redisTemplate.executePipelined(new SessionCallback() { + @Override + public Object execute(RedisOperations operations) throws DataAccessException { + ValueOperations valueOps = operations.opsForValue(); + for (Map.Entry entry : keyValueMap.entrySet()) { + // 设置键值对并指定过期时间 + valueOps.set(entry.getKey(), entry.getValue(), timeout, unit); + } + return null; + } + }); + } + + /** + * 缓存存储数据,值存储JSON数据,设置过期时间 + * + * @param key 键 + * @param value 值 + * @param timeout 过期时间 + * @param timeUnit 过期时间单位 + */ + public void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 缓存存储数据,值存储JSON数据,设置过期时间, 如果键存在则不存储 + * + * @param key 键 + * @param value 值 + * @param timeout 过期时间 + * @param timeUnit 过期时间单位 + * @return 设置成功 or 失败 + */ + public Boolean setCacheObjectIfAbsent(final String key, final T value, final Long timeout, + final TimeUnit timeUnit) { + return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, + timeUnit); + } + + /** + * 获得缓存的数据(将缓存的数据反序列化为指定类型返回) + * + * @param key 键 + * @param clazz 类型 + * @return 对象 + */ + public T getCacheObject(final String key, Class clazz) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + T t = valueOperations.get(key); + if (t == null) { + return null; + } + return JsonUtil.string2Obj(JsonUtil.obj2String(t), clazz); + } + + /** + * 批量获取数据 + * + * @param keys + * @param clazz + * @return + * @param + */ + public Map getCacheObjects(final Collection keys, Class clazz) { + List values = redisTemplate.opsForValue().multiGet(keys); + if (values == null) { + return Collections.emptyMap(); + } + Map resultMap = new HashMap<>(keys.size()); + Iterator keysIterator = keys.iterator(); + Iterator valuesIterator = values.iterator(); + while (keysIterator.hasNext() && valuesIterator.hasNext()) { + String key = keysIterator.next(); + T value = valuesIterator.next(); + if (value != null) { + resultMap.put(key, JsonUtil.string2Obj(JsonUtil.obj2String(value), clazz)); + } else { + resultMap.put(key, null); + } + } + return resultMap; + } + + /** + * 获得缓存的数据 (将缓存的数据反序列化为指定类型返回,支持复杂的泛型) + * + * @param key 键 + * @param typeReference 类型模版 + * @return 对象 + */ + public T getCacheObject(final String key, TypeReference typeReference) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + T t = valueOperations.get(key); + if (t == null) { + return null; + } + return JsonUtil.string2Obj(JsonUtil.obj2String(t), typeReference); + } + + /** + * 扫描删除符合条件的Key + * + * @param pattern 条件 + * @param batchSize 批量遍历数量 + */ + public void scanAndDelete(String pattern, int batchSize) { + // 1. 创建扫描选项 + ScanOptions options = ScanOptions.scanOptions() + .match(pattern) + .count(batchSize) // 控制每次扫描数量 + .build(); + + // 2. 使用游标迭代扫描 + try (Cursor cursor = redisTemplate.scan(options)) { + while (cursor.hasNext()) { + // 2.1 批量收集键 + List keysBatch = new ArrayList<>(batchSize); + for (int i = 0; i < batchSize && cursor.hasNext(); i++) { + keysBatch.add(cursor.next()); + } + + // 2.2 批量删除当前批次 + if (!keysBatch.isEmpty()) { + deleteWithPipeline(keysBatch); + } + } + + } catch (Exception e) { + log.error("Scan and delete failed for pattern: {}", pattern, e); + throw new RuntimeException("Scan and delete failed", e); + } + } + + /** + * 使用管道批量删除(高性能) + */ + private int deleteWithPipeline(List keys) { + if (CollectionUtils.isEmpty(keys)) + return 0; + + return redisTemplate.executePipelined((RedisCallback) connection -> { + for (String key : keys) { + connection.del(key.getBytes()); + } + return null; + }).size(); + } + + /** + * 对缓存中的数值进行自增操作 + * + * @param key 键 + * @param delta 自增步长(可以为负数表示自减) + * @return 自增后的结果 + */ + public Long incr(final String key, final long delta) { + Long result = redisTemplate.opsForValue().increment(key, delta); + return result; + } + // ******************操作List****************** + + /** + * 缓存list数据 + * + * @param key 键 + * @param dataList list数据 + * @return 添加redis列表的长度 + * @param 对象类型 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 缓存list数据 + * + * @param key 键 + * @param dataList list数据 + * @return 添加redis列表的长度 + * @param 对象类型 + */ + public long setCacheList(final String key, final List dataList, Long exp, TimeUnit timeUnit) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + redisTemplate.expire(key, exp, timeUnit); + return count == null ? 0 : count; + } + + /** + * list头插 + * + * @param key 键 + * @param value 值 + * @param 值的类型 + */ + public void leftPushForList(final String key, final T value) { + redisTemplate.opsForList().leftPush(key, value); + } + + /** + * list尾插 + * + * @param key 键 + * @param value 值 + * @param 值的类型 + */ + public void rightPushForList(final String key, final T value) { + redisTemplate.opsForList().rightPush(key, value); + } + + /** + * 删除左侧第一个元素(头删) + * + * @param key 键 + */ + public void leftPopForList(String key) { + redisTemplate.opsForList().leftPop(key); + } + + /** + * 删除右侧第一个元素(尾删) + * + * @param key 键 + */ + public void rightPopForList(String key) { + redisTemplate.opsForList().rightPop(key); + } + + /** + * 移除redis List第一个匹配的元素 + * + * @param key 键 + * @param value 值 + * @param 类型信息 + */ + public void removeForList(final String key, T value) { + redisTemplate.opsForList().remove(key, 1L, value); + } + + /** + * 移除redis List 所有匹配的元素 + * + * @param key 键 + * @param value 值 + * @param 类型信息 + */ + public void removeAllForList(final String key, T value) { + redisTemplate.opsForList().remove(key, 0, value); + } + + /** + * 移除redis List的所有元素 + * + * @param key 键 + * @param 类型信息 + */ + public void removeForAllList(final String key) { + redisTemplate.opsForList().trim(key, -1, 0); + } + + /** + * 修改指定下标的元素 + * + * @param key 键 + * @param index 下标 + * @param value 新值 + * @param 类型信息 + */ + public void setElementAtIndex(final String key, final int index, final T value) { + redisTemplate.opsForList().set(key, index, value); + } + + /** + * 获取缓存的List对象 + * + * @param key 键 + * @param clazz 类型 + * @return List + * @param 类型信息 + */ + public List getCacheList(final String key, Class clazz) { + List list = redisTemplate.opsForList().range(key, 0, -1); + return JsonUtil.string2List(JsonUtil.obj2String(list), clazz); + } + + /** + * 获取缓存的List对象,支持复杂的泛型嵌套 + * + * @param key 键 + * @param typeReference 类型模版 + * @return List + * @param 类型信息 + */ + public List getCacheList(final String key, TypeReference> typeReference) { + List list = redisTemplate.opsForList().range(key, 0, -1); + List res = JsonUtil.string2Obj(JsonUtil.obj2String(list), + typeReference); + return res; + } + + /** + * 根据范围获取List + * + * @param key key + * @param start 开始位置 + * @param end 结束位置 + * @param clazz 类信息 + * @return List列表 + * @param 类型 + */ + public List getCacheListByRange(final String key, long start, long end, Class clazz) { + List range = redisTemplate.opsForList().range(key, start, end); + return JsonUtil.string2List(JsonUtil.obj2String(range), clazz); + } + + /** + * 根据范围获取List(支持复杂的泛型嵌套 ) + * + * @param key key + * @param start 开始 + * @param end 结果 + * @param typeReference 类型模板 + * @return list列表 + * @param 类型信息 + */ + public List getCacheListByRange(final String key, long start, long end, + TypeReference> typeReference) { + List range = redisTemplate.opsForList().range(key, start, end); + return JsonUtil.string2Obj(JsonUtil.obj2String(range), typeReference); + } + + /** + * 获取指定列表长度 + * + * @param key key信息 + * @return 列表长度 + */ + public long getCacheListSize(final String key) { + Long size = redisTemplate.opsForList().size(key); + return size == null ? 0L : size; + } + + /** + * 获取元素在列表中的位置 + * + * @param key 键 + * @param value 值 + * @return 位置 + * @param 类型信息 + */ + public Long indexOfForList(final String key, T value) { + return redisTemplate.opsForList().indexOf(key, value); + } + + // ******************操作Set****************** + + /** + * set添加元素 + * + * @param key 键 + * @param member 元素信息 + */ + public void addMember(final String key, Object... member) { + redisTemplate.opsForSet().add(key, member); + } + + /** + * 删除元素 + * + * @param key 键 + * @param member 元素信息 + */ + public void deleteMember(final String key, Object... member) { + redisTemplate.opsForSet().remove(key, member); + } + + /** + * 获取set数据(所有成员) + * + * @param key 键 + * @return set数据 + */ + public Set getSetMembers(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 设置 set 数据 + * + * @param key 键 + * @param values 值集合 + */ + public void setSetMembers(final String key, Set values) { + if (values != null && !values.isEmpty()) { + redisTemplate.opsForSet().add(key, values.toArray(new String[0])); + } + } + + // ******************操作ZSet****************** + + /** + * 添加元素到有序集合 + * + * @param key 键 + * @param member 成员 + * @param score 分数 + */ + public void addZSetMember(final String key, Object member, double score) { + redisTemplate.opsForZSet().add(key, member, score); + } + + public void trimZset(final String key, final long start, long end) { + + } + + /** + * 批量添加元素到有序集合 + * + * @param key 键 + * @param members 成员集合(Map结构,key为成员对象,value为对应的分数) + */ + public void batchAddZSetMembers(final String key, Map members) { + if (members == null || members.isEmpty()) { + return; // 空集合直接返回,避免无效操作 + } + + // 转换为Redis所需的TypedTuple集合 + Set> tuples = new HashSet<>(members.size()); + for (Map.Entry entry : members.entrySet()) { + tuples.add(new DefaultTypedTuple<>(entry.getKey(), entry.getValue())); + } + + // 批量添加 + redisTemplate.opsForZSet().add(key, tuples); + } + + /** + * 按分数范围获取元素(正序,不带分页) + * + * @param key 键 + * @param min 最小分数 + * @param max 最大分数 + * @param typeReference 类型模板 + * @return 元素集合 + */ + public Set getZSetByScoreRange(final String key, double min, double max, + TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().rangeByScore(key, min, max); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 按分数范围获取元素(正序,带分页) + * + * @param key 键 + * @param min 最小分数 + * @param max 最大分数 + * @param offset 偏移量 + * @param count 数量 + * @param typeReference 类型模板 + * @return 元素集合 + */ + public Set getZSetByScoreRange(final String key, double min, double max, long offset, long count, + TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 按分数范围获取元素(倒序,不带分页) + * + * @param key 键 + * @param min 最小分数 + * @param max 最大分数 + * @param typeReference 类型模板 + * @return 元素集合 + */ + public Set getZSetByScoreRangeDesc(final String key, double min, double max, + TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().reverseRangeByScore(key, min, max); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 按分数范围获取元素(倒序,带分页) + * + * @param key 键 + * @param min 最小分数 + * @param max 最大分数 + * @param offset 偏移量 + * @param count 数量 + * @param typeReference 类型模板 + * @return 元素集合 + */ + public Set getZSetByScoreRangeDesc(final String key, double min, double max, long offset, long count, + TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, offset, count); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + +// /** +// * 按分数范围获取元素(倒序,带分页) - 直接返回指定类型,避免二次JSON转换 +// * 适用于存储了复杂对象(如包含多态类型)的场景 +// * +// * @param key 键 +// * @param min 最小分数 +// * @param max 最大分数 +// * @param offset 偏移量 +// * @param count 数量 +// * @return 元素集合 +// */ +// public Set getCacheZSetByScoreRangeDesc(final String key, double min, double max, long offset, long count) { +// Set data = redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, offset, count); +// Set res = new HashSet<>(); +// for(Object o : data) { +// String strData = JsonUtil.obj2String(o); +// res.add(strData); +// } +// return res; +// } + + /** + * 按索引范围获取元素(正序) + * + * @param key 键 + * @param start 开始索引 + * @param end 结束索引 + * @param typeReference 类型模板 + * @return 元素集合 + */ + public Set getZSetByIndexRange(final String key, long start, long end, TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().range(key, start, end); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 按索引范围获取元素(倒序) + * + * @param key 键 + * @param start 开始索引 + * @param end 结束索引 + * @param typeReference 类型模板 + * @return 元素集合 + */ + public Set getZSetByIndexRangeDesc(final String key, long start, long end, + TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().reverseRange(key, start, end); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 获取元素分数 + * + * @param key 键 + * @param member 成员 + * @return 分数 + */ + public Double getZSetScore(final String key, Object member) { + return redisTemplate.opsForZSet().score(key, member); + } + + /** + * 统计分数范围内元素数量 + * + * @param key 键 + * @param min 最小分数 + * @param max 最大分数 + * @return 元素数量 + */ + public Long getZSetSizeByScoreRange(final String key, double min, double max) { + return redisTemplate.opsForZSet().count(key, min, max); + } + + /** + * 获取有序集合大小 + * + * @param key 键 + * @return 集合大小 + */ + public Long getZSetSize(final String key) { + return redisTemplate.opsForZSet().size(key); + } + + /** + * 获取有序集合中指定分数范围内的元素数量 + * + * @param key 键 + * @param min 最小分数 + * @param max 最大分数 + * @return 分数范围内的元素数量 + */ + public Long countZSetByScoreRange(final String key, final double min, final double max) { + return redisTemplate.opsForZSet().count(key, min, max); + } + + /** + * 增加元素分数 + * + * @param key 键 + * @param member 成员 + * @param delta 增量 + * @return 新分数 + */ + public Double incrementZSetScore(final String key, Object member, double delta) { + return redisTemplate.opsForZSet().incrementScore(key, member, delta); + } + + /** + * 获取元素排名(正序) + * + * @param key 键 + * @param member 成员 + * @return 排名(从0开始) + */ + public Long getZSetRank(final String key, Object member) { + return redisTemplate.opsForZSet().rank(key, member); + } + + /** + * 获取元素排名(倒序) + * + * @param key 键 + * @param member 成员 + * @return 排名(从0开始) + */ + public Long getZSetReverseRank(final String key, Object member) { + return redisTemplate.opsForZSet().reverseRank(key, member); + } + + /** + * 根据排名范围删除元素 + * + * @param key 键 + * @param start 开始排名 + * @param end 结束排名 + * @return 删除数量 + */ + public Long removeZSetRangeByRank(final String key, long start, long end) { + return redisTemplate.opsForZSet().removeRange(key, start, end); + } + + /** + * 获取Zset全部 + * + * @param key 键 + * @param typeReference 类型模版 + * @return + */ + public Set getCacheZSet(String key, TypeReference> typeReference) { + Set data = redisTemplate.opsForZSet().range(key, 0, -1); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 是否有某个值 + * + * @param key + * @return + */ + public boolean zsetContainsKey(String key) { + return redisTemplate.opsForZSet().getOperations().hasKey(key); + } + // ******************操作Hash****************** + + /** + * 缓存map数据 + * + * @param key 键 + * @param dataMap map数据 + * @param 类型信息 + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 批量设置 Hash 数据(重载方法) + * + * @param key 键 + * @param dataMap map数据 + * @param 类型信息 + */ + public void setHashAll(final String key, final Map dataMap) { + if (dataMap != null && !dataMap.isEmpty()) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获取 Hash 中的单个数据(支持泛型) + * + * @param key Redis键 + * @param hKey Hash键 + * @param clazz 类型 + * @return Hash中的对象 + * @param 对象类型 + */ + public T getHashValue(final String key, final String hKey, Class clazz) { + Object value = redisTemplate.opsForHash().get(key, hKey); + if (value == null) { + return null; + } + return JsonUtil.string2Obj(JsonUtil.obj2String(value), clazz); + } + + /** + * 删除 Hash 中的某个字段 + * + * @param key 键 + * @param hKey Hash键 + * @return 结果 + */ + public Long deleteHashField(final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey); + } + + /** + * 往Hash中插入单个数据 + * + * @param key 键 + * @param hKey hash键 + * @param value 值 + * @param 类型信息 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 删除Hash中的某条数据 + * + * @param key 键 + * @param hKey Hash键 + * @return 结果 + */ + public void deleteCacheMapValue(final String key, final String hKey) { + redisTemplate.opsForHash().delete(key, hKey); + } + + /** + * 获取缓存的map数据(支持复杂的泛型嵌套) + * + * @param key key + * @param typeReference 类型模板 + * @return hash对应的map + * @param 对象类型 + */ + public Map getCacheMap(final String key, TypeReference> typeReference) { + Map data = redisTemplate.opsForHash().entries(key); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 获取Hash中的单个数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + * @param 对象类型 + */ + public T getCacheMapValue(final String key, final String hKey, Class clazz) { + Object value = redisTemplate.opsForHash().get(key, hKey); + if (value == null) + return null; + return JsonUtil.string2Obj(JsonUtil.obj2String(value), clazz); + } + + /** + * 获取Hash中的多个数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @param typeReference 对象模板 + * @return 获取的多个数据的集合 + * @param 对象类型 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys, + TypeReference> typeReference) { + List data = redisTemplate.opsForHash().multiGet(key, hKeys); + return JsonUtil.string2Obj(JsonUtil.obj2String(data), typeReference); + } + + /** + * 执行Pipeline操作(高性能批量操作) + * + * @param callback Pipeline操作回调 + * @return 执行结果列表 + */ + public List executePipelined(RedisCallback callback) { + return redisTemplate.executePipelined(callback); + } + + /** + * 对哈希表中指定字段的数值进行自增 + * + * @param key 哈希表的键 + * @param hashKey 哈希表中的字段 + * @param delta 自增步长(可以为负数表示自减) + * @return 自增后的结果 + */ + public Long hincrHashField(final String key, final String hashKey, final long delta) { + return redisTemplate.opsForHash().increment(key, hashKey, delta); + } + + /** + * 删除指定值对应的 Redis 中的键值(compare and delete) + * + * @param key 缓存key + * @param value value + * @return 是否完成了比较并删除 + */ + public boolean cad(String key, String value) { + if (key.contains(StringUtils.SPACE) || value.contains(StringUtils.SPACE)) { + return false; + } + String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; + // 通过lua脚本原子验证令牌和删除令牌 + Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), + Collections.singletonList(key), + value); + return !Objects.equals(result, 0L); + } + + public static class ScanResult { + private final Map data; + private final String nextCursor; + + public ScanResult(Map data, String nextCursor) { + this.data = data; + this.nextCursor = nextCursor; + } + + public Map getData() { + return data; + } + + public String getNextCursor() { + return nextCursor; + } + + public boolean isFinished() { + return "0".equals(nextCursor); + } + } +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..c952c52 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +yuxingshi.ssm.common.redis.config.RedisConfig +yuxingshi.ssm.common.redis.server.RedisService \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/pom.xml b/yuxingshi-ssm-common/yuxingshi-ssm-security/pom.xml new file mode 100644 index 0000000..b793582 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm-common + 1.0-SNAPSHOT + + + yuxingshi.ssm.common.security + yuxingshi-ssm-security + + + 17 + 17 + UTF-8 + + + + + org.springframework + spring-web + + + yuxingshi.ssm.common.core + yuxingshi-ssm-core + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-validation + + + yuxingshi.ssm.common.redis + yuxingshi-ssm-redis + 1.0-SNAPSHOT + + + javax.xml.bind + jaxb-api + + + io.jsonwebtoken + jjwt + + + org.apache.tomcat.embed + tomcat-embed-core + + + \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueIn.java b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueIn.java new file mode 100644 index 0000000..11a1213 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueIn.java @@ -0,0 +1,21 @@ +package yuxingshi.ssm.common.security.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = ValueInValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValueIn { + String message() default "值不在指定范围内"; + + Class[] groups() default {}; + Class[] payload() default {}; + + // 枚举类 + Class> enumClass(); + +} \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueInValidator.java b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueInValidator.java new file mode 100644 index 0000000..7b1a533 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/annotation/ValueInValidator.java @@ -0,0 +1,57 @@ +package yuxingshi.ssm.common.security.annotation; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +public class ValueInValidator implements ConstraintValidator { + + private ValueIn constraint; + private Set validValues = new HashSet<>(); + private Class valueType; + private Method getValueMethod; + + @Override + public void initialize(ValueIn constraint) { + this.constraint = constraint; + Class> enumClass = constraint.enumClass(); + // 获取枚举值 + Enum[] enumConstants = enumClass.getEnumConstants(); + if (enumConstants == null || enumConstants.length == 0) { + throw new IllegalArgumentException("枚举类 " + enumClass.getName() + " 不包含任何值"); + } + + try { + // 强制要求必须存在 getValue() 方法 + this.getValueMethod = enumClass.getMethod("getValue"); + this.valueType = getValueMethod.getReturnType(); + + // 收集所有有效的 value 值 + for (Enum e : enumConstants) { + Object value = getValueMethod.invoke(e); + validValues.add(value); + + } + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("枚举类 " + enumClass.getName() + " 必须包含 getValue() 方法", e); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("无法访问枚举的 getValue() 方法", e); + } + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (value == null) { + return true; + } + if(value.getClass() != valueType){ + return false; + } + return validValues.contains(value); + } + +} \ No newline at end of file diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/dto/TokenDto.java b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/dto/TokenDto.java new file mode 100644 index 0000000..98c5245 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/dto/TokenDto.java @@ -0,0 +1,28 @@ +package yuxingshi.ssm.common.security.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * 存放令牌信息 + */ +@Getter +@Setter +@ToString +public class TokenDto { + + /** + * 访问的令牌 + */ + private String accessToken; + + + /** + * 令牌的过期时间 + */ + private Long expires; + + + private Integer role; +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/service/TokenService.java b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/service/TokenService.java new file mode 100644 index 0000000..3be80cd --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/service/TokenService.java @@ -0,0 +1,171 @@ +package yuxingshi.ssm.common.security.service; + + +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import yuxingshi.ssm.common.core.constants.CacheConstants; +import yuxingshi.ssm.common.core.constants.SecurityConstants; +import yuxingshi.ssm.common.core.constants.TokenConstants; +import yuxingshi.ssm.common.core.domain.dto.LoginUserDTO; +import yuxingshi.ssm.common.core.utils.ServletUtil; +import yuxingshi.ssm.common.redis.server.RedisService; +import yuxingshi.ssm.common.security.dto.TokenDto; +import yuxingshi.ssm.common.security.utils.JwtUtil; +import yuxingshi.ssm.common.security.utils.SecurityUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * token服务类 + */ +@Component +public class TokenService { + + + /** + * 登录用户过期时间(s) + */ + private static final Long LOGIN_USER_EXP = CacheConstants.LOGIN_USER_EXPIRATION * 60 * 1000; + @Autowired + private RedisService redisService; + /** + * 创建token + * @param loginUserDto 登录信息 + * @return token信息 + */ + public TokenDto createToken(LoginUserDTO loginUserDto) { + String userKey = UUID.randomUUID().toString().replaceAll("-", ""); + loginUserDto.setUserKey(userKey); + refreshToken(loginUserDto); + Map claimMap = new HashMap<>(); + claimMap.put(SecurityConstants.USER_KEY,userKey); + claimMap.put(SecurityConstants.USER_ID,loginUserDto.getUserId()); + claimMap.put(SecurityConstants.USER_FROM,loginUserDto.getUserFrom()); + claimMap.put(SecurityConstants.USER_IDENTITY,loginUserDto.getUserIdentity()); + TokenDto tokenDto = new TokenDto(); + tokenDto.setAccessToken(JwtUtil.createToken(claimMap)); + tokenDto.setExpires(LOGIN_USER_EXP); + refreshToken(loginUserDto); + return tokenDto; + } + + + /** + * 根据令牌获取用户信息 + * @param token 令牌 + * @return 用户信息 + */ + public LoginUserDTO getLoginUserDto(String token) { + LoginUserDTO loginUserDto = null; + // 解析令牌 + try { + if(StringUtils.isNoneBlank(token)) { + Claims claims = JwtUtil.parseToken(token); + String userKey = JwtUtil.getUserKey(claims); + String userId = JwtUtil.getUserId(claims); + String userFrom = JwtUtil.getUserFrom(claims); + loginUserDto = redisService.getCacheObject(getCacheKey(userKey,userId,userFrom), LoginUserDTO.class); + } + return loginUserDto; + }catch (Exception e) {} + return loginUserDto; + } + + /** + * 通过请求信息获取用户信息 + * @param request 请求信息 + * @return 用户信息 + */ + public LoginUserDTO getLoginUserDto(HttpServletRequest request) { + String token = SecurityUtil.getToken(request); + return getLoginUserDto(token); + } + + + /** + * 获取用户信息 + * @return 用户信息 + */ + public LoginUserDTO getLoginUserDto() { + return getLoginUserDto(ServletUtil.getRequest()); + } + + + /** + * 删除用户登录状态 + */ + public void removeLoginStatus(String token) { + if(StringUtils.isNoneBlank(token)) { + Claims claims = JwtUtil.parseToken(token); + String userKey = JwtUtil.getUserKey(claims); + String userId = JwtUtil.getUserId(claims); + String userFrom = JwtUtil.getUserFrom(claims); + String cacheKey = getCacheKey(userKey,userId,userFrom); + redisService.deleteObject(cacheKey); + } + } + + /** + * 允许管理员删除别人的用户登录状态 + * @param userId 用户Id + * @param userFrom 用户来源 + */ + public void removeLoginStatus(Long userId,String userFrom) { + String split = TokenConstants.LOGIN_CACHE_INGO_SPLIT; + String pattern = TokenConstants.LOGIN_TOKEN_KEY + split + userId + split + userFrom +split + "*"; + redisService.scanAndDelete(pattern,100); + } + + + /** + * 允许管理员设置用户登录状态 + * @param loginUserDto 用户信息 + */ + public void setLoginUser(LoginUserDTO loginUserDto) { + if(loginUserDto != null && StringUtils.isNoneBlank(loginUserDto.getUserKey())) { + refreshToken(loginUserDto); + } + } + /** + * 延期令牌有效时间 不到120分钟,自动刷新 + * @param loginUserDto 用户数据 + */ + public void extendToken(LoginUserDTO loginUserDto) { + long expTime = loginUserDto.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if(currentTime - expTime <= CacheConstants.LOGIN_USER_EXP_CRITICAL_TIME * 60) { + loginUserDto.setLoginTime(currentTime); + loginUserDto.setExpireTime(currentTime + LOGIN_USER_EXP); + refreshToken(loginUserDto); + } + } + + /** + * 登录用户缓存 + * @param loginUserDto 登录用户信息 + */ + private void refreshToken(LoginUserDTO loginUserDto) { + loginUserDto.setLoginTime(System.currentTimeMillis()); + loginUserDto.setExpireTime(loginUserDto.getLoginTime() + LOGIN_USER_EXP); + String cacheKey = getCacheKey(loginUserDto.getUserKey(), loginUserDto.getUserId(),loginUserDto.getUserFrom()); + redisService.setCacheObject(cacheKey,loginUserDto,LOGIN_USER_EXP, TimeUnit.MILLISECONDS); + } + + + /** + * 获取缓存键 + * @param userKey 用户标识 + * @return 缓存键 + */ + public String getCacheKey(String userKey,String userId,String userFrom) { + String split = TokenConstants.LOGIN_CACHE_INGO_SPLIT; + return TokenConstants.LOGIN_TOKEN_KEY + ":" + userId + split + userFrom +split+ userKey; + } + +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/JwtUtil.java b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/JwtUtil.java new file mode 100644 index 0000000..3acde5d --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/JwtUtil.java @@ -0,0 +1,156 @@ +package yuxingshi.ssm.common.security.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import yuxingshi.ssm.common.core.constants.SecurityConstants; +import yuxingshi.ssm.common.core.constants.TokenConstants; + +import java.util.Map; + +/** + * 令牌工具类 + */ +public class JwtUtil { + + private static final Logger log = LoggerFactory.getLogger(JwtUtil.class); + public static String secret = TokenConstants.SECRET; + + + /** + * 根据原始的数据生成令牌 + * @param claims 原始数据 + * @return 令牌 + */ + public static String createToken(Map claims) { + return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); + } + + + /** + * 根据令牌获取原始数据 + * @param token 令牌 + * @return 原始数据 + */ + public static Claims parseToken(String token) { + try { + return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + }catch (Exception e) { + log.warn("[JwtUtil.parseToken]"); + return null; + } + } + + /** + * 从令牌中获取用户标识 + * @param token 令牌 + * @return 用户标识 + */ + public static String getUserKey(String token) { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USER_KEY); + } + + /** + * 根据数据声明获取用户Id + * @param claims 数据声明 + * @return 用户id + */ + public static String getUserId(Claims claims) { + return getValue(claims, SecurityConstants.USER_ID); + } + + /** + * 从令牌中获取用户id + * @param token 令牌 + * @return 用户id + */ + public static String getUserId(String token) { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USER_ID); + } + + /** + * 根据数据声明获取用户名称 + * @param claims 数据声明 + * @return 用户名称 + */ + public static String getUserName(Claims claims) { + return getValue(claims, SecurityConstants.USERNAME); + } + + /** + * 从令牌中获取用户身份 + * @param token 令牌 + * @return 用户身份 + */ + public static String getUserIdentity(String token) { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USER_IDENTITY); + } + + /** + * 根据数据声明获取用户身份 + * @param claims 数据声明 + * @return 用户身份 + */ + public static String getUserIdentity(Claims claims) { + return getValue(claims, SecurityConstants.USER_IDENTITY); + } + + + /** + * 从令牌中获取用户标识 + * @param token 令牌 + * @return 用户标识 + */ + public static String getUserFrom(String token) { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USER_FROM); + } + + + /** + * 根据数据声明获取用户来源 + * @param claims 数据声明 + * @return 用户来源 + */ + public static String getUserFrom(Claims claims) { + return getValue(claims, SecurityConstants.USER_FROM); + } + + /** + * 从令牌中获取用户来源 + * @param token 令牌 + * @return 用户来源 + */ + public static String getUserName(String token) { + Claims claims = parseToken(token); + return getValue(claims, SecurityConstants.USERNAME); + } + + /** + * 根据数据声明获取用户标识 + * @param claims 数据声明 + * @return 用户标识 + */ + public static String getUserKey(Claims claims) { + return getValue(claims, SecurityConstants.USER_KEY); + } + + /** + * 获取数据声明来获取数据 + * @param claims 数据声明 + * @param key 键 + * @return 数据 + */ + public static String getValue(Claims claims,String key) { + Object value = claims.get(key); + if(value == null) { + return ""; + } + return value.toString(); + } +} diff --git a/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/SecurityUtil.java b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/SecurityUtil.java new file mode 100644 index 0000000..dbdaac2 --- /dev/null +++ b/yuxingshi-ssm-common/yuxingshi-ssm-security/src/main/java/yuxingshi/ssm/common/security/utils/SecurityUtil.java @@ -0,0 +1,53 @@ +package yuxingshi.ssm.common.security.utils; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.server.reactive.ServerHttpRequest; +import yuxingshi.ssm.common.core.constants.SecurityConstants; +import yuxingshi.ssm.common.core.constants.TokenConstants; +import yuxingshi.ssm.common.core.utils.ServletUtil; + +/** + * 安全工具类 + */ +public class SecurityUtil { + + /** + * 获取请求token + * @return token信息 + */ + public static String getToken() { + return getToken(ServletUtil.getRequest()); + } + + /** + * 从请求头中获取token + * @param request 请求信息 + * @return token + */ + public static String getToken(HttpServletRequest request) { + return replaceTokenPrefix(request.getHeader(SecurityConstants.AUTHENTICATION)); + } + + + /** + * 从请求头中获取token + * @param request 请求信息 + * @return token + */ + public static String getToken(ServerHttpRequest request) { + return replaceTokenPrefix(request.getHeaders().getFirst(SecurityConstants.AUTHENTICATION)); + } + + /** + * 裁剪token前缀 + * @param token 令牌 + * @return 裁剪后的token + */ + public static String replaceTokenPrefix(String token) { + if(StringUtils.isNoneBlank(token) && token.startsWith(TokenConstants.PREFIX)){ + token.replaceFirst(TokenConstants.PREFIX,""); + } + return token; + } +} diff --git a/yuxingshi-ssm-server/pom.xml b/yuxingshi-ssm-server/pom.xml new file mode 100644 index 0000000..7a24617 --- /dev/null +++ b/yuxingshi-ssm-server/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + yuxingshi + yuxingshi-ssm + 1.0-SNAPSHOT + + + yuxingshi.ssm.server + yuxingshi-ssm-server + + + 17 + 17 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web + + + org.projectlombok + lombok + + + + com.mysql + mysql-connector-j + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + \ No newline at end of file diff --git a/yuxingshi-ssm-server/src/main/java/yuxingshi/ssm/server/Main.java b/yuxingshi-ssm-server/src/main/java/yuxingshi/ssm/server/Main.java new file mode 100644 index 0000000..09a0076 --- /dev/null +++ b/yuxingshi-ssm-server/src/main/java/yuxingshi/ssm/server/Main.java @@ -0,0 +1,7 @@ +package yuxingshi.ssm.server; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/yuxingshi-ssm-server/src/main/resources/application.yaml b/yuxingshi-ssm-server/src/main/resources/application.yaml new file mode 100644 index 0000000..e69de29