From 3ff0bbdeaf841d5b545dcdc00219d96066557502 Mon Sep 17 00:00:00 2001
From: click33 <2393584716@qq.com>
Date: Sat, 13 Apr 2024 16:39:17 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=92=E4=BB=B6=EF=BC=9Asa?=
=?UTF-8?q?-token-hutool-timed-cache=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=95=B4?=
=?UTF-8?q?=E5=90=88=20Hutool=20=E7=BC=93=E5=AD=98=E6=8F=92=E4=BB=B6=20Tim?=
=?UTF-8?q?edCache?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
mvn clean.bat | 4 +
.../sa-token-demo-hutool-timed-cache/pom.xml | 59 ++++++
.../java/com/pj/SaTokenDemoApplication.java | 21 ++
.../java/com/pj/current/GlobalException.java | 57 +++++
.../java/com/pj/current/NotFoundHandle.java | 27 +++
.../java/com/pj/satoken/SaTokenConfigure.java | 69 ++++++
.../java/com/pj/satoken/StpInterfaceImpl.java | 44 ++++
.../java/com/pj/test/LoginController.java | 48 +++++
.../com/pj/test/StressTestController.java | 59 ++++++
.../main/java/com/pj/test/TestController.java | 30 +++
.../src/main/java/com/pj/util/AjaxJson.java | 162 ++++++++++++++
.../src/main/java/com/pj/util/Ttime.java | 63 ++++++
.../src/main/resources/application.yml | 49 +++++
sa-token-plugin/pom.xml | 1 +
.../sa-token-hutool-timed-cache/pom.xml | 35 +++
.../dao/SaTokenDaoForHutoolTimedCache.java | 200 ++++++++++++++++++
.../main/resources/META-INF/spring.factories | 1 +
...ot.autoconfigure.AutoConfiguration.imports | 1 +
18 files changed, 930 insertions(+)
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/SaTokenDemoApplication.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/GlobalException.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/NotFoundHandle.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/SaTokenConfigure.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/StpInterfaceImpl.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/LoginController.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/StressTestController.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/TestController.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/AjaxJson.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/Ttime.java
create mode 100644 sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/resources/application.yml
create mode 100644 sa-token-plugin/sa-token-hutool-timed-cache/pom.xml
create mode 100644 sa-token-plugin/sa-token-hutool-timed-cache/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForHutoolTimedCache.java
create mode 100644 sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring.factories
create mode 100644 sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/mvn clean.bat b/mvn clean.bat
index bdd7d4dd..baf093d2 100644
--- a/mvn clean.bat
+++ b/mvn clean.bat
@@ -25,6 +25,10 @@ cd sa-token-demo-websocket & call mvn clean & cd ..
cd sa-token-demo-websocket-spring & call mvn clean & cd ..
cd sa-token-demo-bom-import & call mvn clean & cd ..
+cd sa-token-demo-hutool-timed-cache & call mvn clean & cd ..
+
+
+
cd sa-token-demo-sso
cd sa-token-demo-sso-server & call mvn clean & cd ..
cd sa-token-demo-sso1-client & call mvn clean & cd ..
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml b/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml
new file mode 100644
index 00000000..10250bae
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/pom.xml
@@ -0,0 +1,59 @@
+
+ 4.0.0
+ cn.dev33
+ sa-token-demo-hutool-timed-cache
+ 0.0.1-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.14
+
+
+
+
+
+
+ 1.37.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot-starter
+ ${sa-token.version}
+
+
+
+
+ cn.dev33
+ sa-token-hutool-timed-cache
+ ${sa-token.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/SaTokenDemoApplication.java
new file mode 100644
index 00000000..19f19d42
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/SaTokenDemoApplication.java
@@ -0,0 +1,21 @@
+package com.pj;
+
+import cn.dev33.satoken.SaManager;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Sa-Token 整合 Hutool-TimedCache 示例
+ * @author click33
+ *
+ */
+@SpringBootApplication
+public class SaTokenDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SaTokenDemoApplication.class, args);
+ System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
+ System.out.println(SaManager.getSaTokenDao());
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/GlobalException.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/GlobalException.java
new file mode 100644
index 00000000..1d6ebb1c
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/GlobalException.java
@@ -0,0 +1,57 @@
+package com.pj.current;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import com.pj.util.AjaxJson;
+
+import cn.dev33.satoken.exception.DisableServiceException;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.exception.NotRoleException;
+
+/**
+ * 全局异常处理
+ */
+@RestControllerAdvice
+public class GlobalException {
+
+ // 全局异常拦截(拦截项目中的所有异常)
+ @ExceptionHandler
+ public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
+ throws Exception {
+
+ // 打印堆栈,以供调试
+ System.out.println("全局异常---------------");
+ e.printStackTrace();
+
+ // 不同异常返回不同状态码
+ AjaxJson aj = null;
+ if (e instanceof NotLoginException) { // 如果是未登录异常
+ NotLoginException ee = (NotLoginException) e;
+ aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
+ }
+ else if(e instanceof NotRoleException) { // 如果是角色异常
+ NotRoleException ee = (NotRoleException) e;
+ aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
+ }
+ else if(e instanceof NotPermissionException) { // 如果是权限异常
+ NotPermissionException ee = (NotPermissionException) e;
+ aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
+ }
+ else if(e instanceof DisableServiceException) { // 如果是被封禁异常
+ DisableServiceException ee = (DisableServiceException) e;
+ aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + "):" + ee.getDisableTime() + "秒后解封");
+ }
+ else { // 普通异常, 输出:500 + 异常信息
+ aj = AjaxJson.getError(e.getMessage());
+ }
+
+ // 返回给前端
+ return aj;
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/NotFoundHandle.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/NotFoundHandle.java
new file mode 100644
index 00000000..81ff22e8
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/current/NotFoundHandle.java
@@ -0,0 +1,27 @@
+package com.pj.current;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 处理 404
+ * @author click33
+ */
+@RestController
+public class NotFoundHandle implements ErrorController {
+
+ @RequestMapping("/error")
+ public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ response.setStatus(200);
+ return SaResult.get(404, "not found", null);
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/SaTokenConfigure.java
new file mode 100644
index 00000000..9dd25cd9
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/SaTokenConfigure.java
@@ -0,0 +1,69 @@
+package com.pj.satoken;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.filter.SaServletFilter;
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.util.SaResult;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+
+/**
+ * [Sa-Token 权限认证] 配置类
+ * @author click33
+ *
+ */
+@Configuration
+public class SaTokenConfigure implements WebMvcConfigurer {
+
+ /**
+ * 注册 Sa-Token 拦截器打开注解鉴权功能
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 注册 Sa-Token 拦截器打开注解鉴权功能
+ registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
+ }
+
+ /**
+ * 注册 [Sa-Token 全局过滤器]
+ */
+ @Bean
+ public SaServletFilter getSaServletFilter() {
+ return new SaServletFilter()
+
+ // 指定 [拦截路由] 与 [放行路由]
+ .addInclude("/**")// .addExclude("/favicon.ico")
+
+ // 认证函数: 每次请求执行
+ .setAuth(obj -> {
+ // SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
+
+ })
+
+ // 异常处理函数:每次认证函数发生异常时执行此函数
+ .setError(e -> {
+ System.out.println("---------- sa全局异常 ");
+ return SaResult.error(e.getMessage());
+ })
+
+ // 前置函数:在每次认证函数之前执行 (BeforeAuth不受 includeList 与 excludeList 的限制,所有请求都会进入)
+ .setBeforeAuth(r -> {
+ // ---------- 设置一些安全响应头 ----------
+ SaHolder.getResponse()
+ // 服务器名称
+ .setServer("sa-server")
+ // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
+ .setHeader("X-Frame-Options", "SAMEORIGIN")
+ // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
+ .setHeader("X-XSS-Protection", "1; mode=block")
+ // 禁用浏览器内容嗅探
+ .setHeader("X-Content-Type-Options", "nosniff")
+ ;
+ })
+ ;
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/StpInterfaceImpl.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/StpInterfaceImpl.java
new file mode 100644
index 00000000..b6cc79f7
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/satoken/StpInterfaceImpl.java
@@ -0,0 +1,44 @@
+package com.pj.satoken;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.stereotype.Component;
+
+import cn.dev33.satoken.stp.StpInterface;
+
+/**
+ * 自定义权限验证接口扩展
+ */
+@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
+public class StpInterfaceImpl implements StpInterface {
+
+ /**
+ * 返回一个账号所拥有的权限码集合
+ */
+ @Override
+ public List getPermissionList(Object loginId, String loginType) {
+ // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
+ List list = new ArrayList();
+ list.add("101");
+ list.add("user-add");
+ list.add("user-delete");
+ list.add("user-update");
+ list.add("user-get");
+ list.add("article-get");
+ return list;
+ }
+
+ /**
+ * 返回一个账号所拥有的角色标识集合
+ */
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
+ List list = new ArrayList();
+ list.add("admin");
+ list.add("super-admin");
+ return list;
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/LoginController.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/LoginController.java
new file mode 100644
index 00000000..34eea9a1
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/LoginController.java
@@ -0,0 +1,48 @@
+package com.pj.test;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 登录测试
+ * @author click33
+ *
+ */
+@RestController
+@RequestMapping("/acc/")
+public class LoginController {
+
+ // 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
+ @RequestMapping("doLogin")
+ public SaResult doLogin(String name, String pwd) {
+ // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
+ if("zhang".equals(name) && "123456".equals(pwd)) {
+ StpUtil.login(10001);
+ return SaResult.ok("登录成功");
+ }
+ return SaResult.error("登录失败");
+ }
+
+ // 查询登录状态 ---- http://localhost:8081/acc/isLogin
+ @RequestMapping("isLogin")
+ public SaResult isLogin() {
+ return SaResult.ok("是否登录:" + StpUtil.isLogin());
+ }
+
+ // 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
+ @RequestMapping("tokenInfo")
+ public SaResult tokenInfo() {
+ return SaResult.data(StpUtil.getTokenInfo());
+ }
+
+ // 测试注销 ---- http://localhost:8081/acc/logout
+ @RequestMapping("logout")
+ public SaResult logout() {
+ StpUtil.logout();
+ return SaResult.ok();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/StressTestController.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/StressTestController.java
new file mode 100644
index 00000000..493134c9
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/StressTestController.java
@@ -0,0 +1,59 @@
+package com.pj.test;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.util.SaResult;
+import com.pj.util.Ttime;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 压力测试
+ * @author click33
+ *
+ */
+@RestController
+@RequestMapping("/s-test/")
+public class StressTestController {
+
+ // 测试 浏览器访问: http://localhost:8081/s-test/login
+ // 测试前,请先将 is-read-cookie 配置为 false
+ @RequestMapping("login")
+ public SaResult login() {
+// StpUtil.getTokenSession().logout();
+// StpUtil.logoutByLoginId(10001);
+
+ int count = 10; // 循环多少轮
+ int loginCount = 10000; // 每轮循环多少次
+
+ // 循环10次 取平均时间
+ List list = new ArrayList<>();
+ for (int i = 1; i <= count; i++) {
+ System.out.println("\n---------------------第" + i + "轮---------------------");
+ Ttime t = new Ttime().start();
+ // 每次登录的次数
+ for (int j = 1; j <= loginCount; j++) {
+ StpUtil.login("1000" + j, "PC-" + j);
+ if(j % 1000 == 0) {
+ System.out.println("已登录:" + j);
+ }
+ }
+ t.end();
+ list.add((t.returnMs() + 0.0) / 1000);
+ System.out.println("第" + i + "轮" + "用时:" + t.toString());
+ }
+// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
+
+ System.out.println("\n---------------------测试结果---------------------");
+ System.out.println(list.size() + "次测试: " + list);
+ double ss = 0;
+ for (int i = 0; i < list.size(); i++) {
+ ss += list.get(i);
+ }
+ System.out.println("平均用时: " + ss / list.size());
+ return SaResult.ok();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/TestController.java
new file mode 100644
index 00000000..9838063e
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/test/TestController.java
@@ -0,0 +1,30 @@
+package com.pj.test;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.dev33.satoken.util.SaResult;
+
+/**
+ * 测试专用Controller
+ * @author click33
+ *
+ */
+@RestController
+@RequestMapping("/test/")
+public class TestController {
+
+ // 测试 浏览器访问: http://localhost:8081/test/test
+ @RequestMapping("test")
+ public SaResult test() {
+ System.out.println("------------进来了");
+ return SaResult.ok();
+ }
+
+ // 测试 浏览器访问: http://localhost:8081/test/test2
+ @RequestMapping("test2")
+ public SaResult test2() {
+ return SaResult.ok();
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/AjaxJson.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/AjaxJson.java
new file mode 100644
index 00000000..768d0578
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/AjaxJson.java
@@ -0,0 +1,162 @@
+package com.pj.util;
+
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * ajax请求返回Json格式数据的封装
+ */
+public class AjaxJson implements Serializable{
+
+ private static final long serialVersionUID = 1L; // 序列化版本号
+
+ public static final int CODE_SUCCESS = 200; // 成功状态码
+ public static final int CODE_ERROR = 500; // 错误状态码
+ public static final int CODE_WARNING = 501; // 警告状态码
+ public static final int CODE_NOT_JUR = 403; // 无权限状态码
+ public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
+ public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
+
+ public int code; // 状态码
+ public String msg; // 描述信息
+ public Object data; // 携带对象
+ public Long dataCount; // 数据总数,用于分页
+
+ /**
+ * 返回code
+ * @return
+ */
+ public int getCode() {
+ return this.code;
+ }
+
+ /**
+ * 给msg赋值,连缀风格
+ */
+ public AjaxJson setMsg(String msg) {
+ this.msg = msg;
+ return this;
+ }
+ public String getMsg() {
+ return this.msg;
+ }
+
+ /**
+ * 给data赋值,连缀风格
+ */
+ public AjaxJson setData(Object data) {
+ this.data = data;
+ return this;
+ }
+
+ /**
+ * 将data还原为指定类型并返回
+ */
+ @SuppressWarnings("unchecked")
+ public T getData(Class cs) {
+ return (T) data;
+ }
+
+ // ============================ 构建 ==================================
+
+ public AjaxJson(int code, String msg, Object data, Long dataCount) {
+ this.code = code;
+ this.msg = msg;
+ this.data = data;
+ this.dataCount = dataCount;
+ }
+
+ // 返回成功
+ public static AjaxJson getSuccess() {
+ return new AjaxJson(CODE_SUCCESS, "ok", null, null);
+ }
+ public static AjaxJson getSuccess(String msg) {
+ return new AjaxJson(CODE_SUCCESS, msg, null, null);
+ }
+ public static AjaxJson getSuccess(String msg, Object data) {
+ return new AjaxJson(CODE_SUCCESS, msg, data, null);
+ }
+ public static AjaxJson getSuccessData(Object data) {
+ return new AjaxJson(CODE_SUCCESS, "ok", data, null);
+ }
+ public static AjaxJson getSuccessArray(Object... data) {
+ return new AjaxJson(CODE_SUCCESS, "ok", data, null);
+ }
+
+ // 返回失败
+ public static AjaxJson getError() {
+ return new AjaxJson(CODE_ERROR, "error", null, null);
+ }
+ public static AjaxJson getError(String msg) {
+ return new AjaxJson(CODE_ERROR, msg, null, null);
+ }
+
+ // 返回警告
+ public static AjaxJson getWarning() {
+ return new AjaxJson(CODE_ERROR, "warning", null, null);
+ }
+ public static AjaxJson getWarning(String msg) {
+ return new AjaxJson(CODE_WARNING, msg, null, null);
+ }
+
+ // 返回未登录
+ public static AjaxJson getNotLogin() {
+ return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
+ }
+
+ // 返回没有权限的
+ public static AjaxJson getNotJur(String msg) {
+ return new AjaxJson(CODE_NOT_JUR, msg, null, null);
+ }
+
+ // 返回一个自定义状态码的
+ public static AjaxJson get(int code, String msg){
+ return new AjaxJson(code, msg, null, null);
+ }
+
+ // 返回分页和数据的
+ public static AjaxJson getPageData(Long dataCount, Object data){
+ return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
+ }
+
+ // 返回,根据受影响行数的(大于0=ok,小于0=error)
+ public static AjaxJson getByLine(int line){
+ if(line > 0){
+ return getSuccess("ok", line);
+ }
+ return getError("error").setData(line);
+ }
+
+ // 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
+ public static AjaxJson getByBoolean(boolean b){
+ return b ? getSuccess("ok") : getError("error");
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @SuppressWarnings("rawtypes")
+ @Override
+ public String toString() {
+ String data_string = null;
+ if(data == null){
+
+ } else if(data instanceof List){
+ data_string = "List(length=" + ((List)data).size() + ")";
+ } else {
+ data_string = data.toString();
+ }
+ return "{"
+ + "\"code\": " + this.getCode()
+ + ", \"msg\": \"" + this.getMsg() + "\""
+ + ", \"data\": " + data_string
+ + ", \"dataCount\": " + dataCount
+ + "}";
+ }
+
+
+
+
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/Ttime.java b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/Ttime.java
new file mode 100644
index 00000000..dec4f1cb
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/java/com/pj/util/Ttime.java
@@ -0,0 +1,63 @@
+package com.pj.util;
+
+
+/**
+ * 用于测试用时
+ * @author click33
+ *
+ */
+public class Ttime {
+
+ private long start=0; //开始时间
+ private long end=0; //结束时间
+
+ public static Ttime t = new Ttime(); //static快捷使用
+
+ /**
+ * 开始计时
+ * @return
+ */
+ public Ttime start() {
+ start=System.currentTimeMillis();
+ return this;
+ }
+
+
+ /**
+ * 结束计时
+ */
+ public Ttime end() {
+ end=System.currentTimeMillis();
+ return this;
+ }
+
+
+ /**
+ * 返回所用毫秒数
+ */
+ public long returnMs() {
+ return end-start;
+ }
+
+ /**
+ * 格式化输出结果
+ */
+ public void outTime() {
+ System.out.println(this.toString());
+ }
+
+ /**
+ * 结束并格式化输出结果
+ */
+ public void endOutTime() {
+ this.end().outTime();
+ }
+
+ @Override
+ public String toString() {
+ return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
+ }
+
+
+
+}
diff --git a/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/resources/application.yml
new file mode 100644
index 00000000..3a2d6f57
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-hutool-timed-cache/src/main/resources/application.yml
@@ -0,0 +1,49 @@
+# 端口
+server:
+ port: 8081
+
+# sa-token 配置
+sa-token:
+ # token 名称 (同时也是 cookie 名称)
+ token-name: satoken
+ # token 有效期(单位:秒) 默认30天,-1 代表永久有效
+ timeout: 2592000
+ # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
+ active-timeout: -1
+ # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
+ is-concurrent: true
+ # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
+ is-share: true
+ # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+ token-style: uuid
+ # 是否输出操作日志
+ is-log: true
+
+spring:
+ # redis配置
+ redis:
+ # Redis数据库索引(默认为0)
+ database: 1
+ # Redis服务器地址
+ host: 127.0.0.1
+ # Redis服务器连接端口
+ port: 6379
+ # Redis服务器连接密码(默认为空)
+ password:
+ # 连接超时时间
+ timeout: 10s
+ lettuce:
+ pool:
+ # 连接池最大连接数
+ max-active: 200
+ # 连接池最大阻塞等待时间(使用负值表示没有限制)
+ max-wait: -1ms
+ # 连接池中的最大空闲连接
+ max-idle: 10
+ # 连接池中的最小空闲连接
+ min-idle: 0
+
+
+
+
+
\ No newline at end of file
diff --git a/sa-token-plugin/pom.xml b/sa-token-plugin/pom.xml
index 29999471..f314bd16 100644
--- a/sa-token-plugin/pom.xml
+++ b/sa-token-plugin/pom.xml
@@ -26,6 +26,7 @@
sa-token-redisson-jackson2
sa-token-redisx
sa-token-alone-redis
+ sa-token-hutool-timed-cache
sa-token-dialect-thymeleaf
sa-token-sso
sa-token-oauth2
diff --git a/sa-token-plugin/sa-token-hutool-timed-cache/pom.xml b/sa-token-plugin/sa-token-hutool-timed-cache/pom.xml
new file mode 100644
index 00000000..a1e3e4fb
--- /dev/null
+++ b/sa-token-plugin/sa-token-hutool-timed-cache/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+
+ cn.dev33
+ sa-token-plugin
+ ${revision}
+ ../pom.xml
+
+ jar
+
+ sa-token-hutool-timed-cache
+ sa-token-hutool-timed-cache
+ sa-token integrate hutool-TimedCache
+
+
+
+
+ cn.dev33
+ sa-token-core
+
+
+
+ cn.hutool
+ hutool-cache
+ 5.8.27
+
+
+
+
+
+
diff --git a/sa-token-plugin/sa-token-hutool-timed-cache/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForHutoolTimedCache.java b/sa-token-plugin/sa-token-hutool-timed-cache/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForHutoolTimedCache.java
new file mode 100644
index 00000000..fab80c01
--- /dev/null
+++ b/sa-token-plugin/sa-token-hutool-timed-cache/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForHutoolTimedCache.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.dao;
+
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.util.SaFoxUtil;
+import cn.hutool.cache.CacheUtil;
+import cn.hutool.cache.impl.CacheObj;
+import cn.hutool.cache.impl.TimedCache;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Sa-Token 持久层接口(基于 Hutool-TimedCache,系统重启后数据丢失)
+ *
+ * @author click33
+ * @since 1.38.0
+ */
+public class SaTokenDaoForHutoolTimedCache implements SaTokenDao {
+
+ //
+ /**
+ * 底层缓存对象:
+ * 参数填1000,代表默认ttl为1000毫秒,实际上此参数意义不大,因为后续每个值都会单独设置自己的ttl值
+ */
+ TimedCache timedCache = CacheUtil.newTimedCache(1000);
+
+
+ // ------------------------ String 读写操作
+
+ @Override
+ public String get(String key) {
+ return (String) getObject(key);
+ }
+
+ @Override
+ public void set(String key, String value, long timeout) {
+ setObject(key, value, timeout);
+ }
+
+ @Override
+ public void update(String key, String value) {
+ updateObject(key, value);
+ }
+
+ @Override
+ public void delete(String key) {
+ deleteObject(key);
+ }
+
+ @Override
+ public long getTimeout(String key) {
+ return getObjectTimeout(key);
+ }
+
+ @Override
+ public void updateTimeout(String key, long timeout) {
+ updateObjectTimeout(key, timeout);
+ }
+
+
+ // ------------------------ Object 读写操作
+
+ @Override
+ public Object getObject(String key) {
+ // 第二个参数代表:是否刷新最后访问时间
+ // 设置为false,因为我们不需要刷新最后访问时间,只需要取值即可
+ return timedCache.get(key, false);
+ }
+
+ @Override
+ public void setObject(String key, Object object, long timeout) {
+ if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
+ return;
+ }
+ // 如果为永不过期
+ // 在 sa-token 中,-1 代表永不过期
+ // 在 hutool-TimedCache 中,0 代表永不过期
+ // 为了适应 hutool-TimedCache 规范,这里将 -1 转换为 0
+ if(timeout == SaTokenDao.NEVER_EXPIRE) {
+ timedCache.put(key, object, 0);
+ return;
+ }
+ // 正常情况
+ timedCache.put(key, object, timeout * 1000);
+ }
+
+ @Override
+ public void updateObject(String key, Object object) {
+ long expire = getObjectTimeout(key);
+ // -2 = 无此键
+ if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
+ return;
+ }
+ this.setObject(key, object, expire);
+ }
+
+ @Override
+ public void deleteObject(String key) {
+ timedCache.remove(key);
+ }
+
+ @Override
+ public long getObjectTimeout(String key) {
+ return getKeyTimeout(key);
+ }
+
+ @Override
+ public void updateObjectTimeout(String key, long timeout) {
+ // $$待优化:对一个不存在的key进行修改timeout操作时,可能会造成一些意外数据,待进一步测试
+ this.setObject(key, this.getObject(key), timeout);
+ }
+
+
+ // ------------------------ Session 读写操作
+ // 使用接口默认实现
+
+
+ // --------- 会话管理
+
+ @Override
+ public List searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+ return SaFoxUtil.searchList(timedCache.keySet(), prefix, keyword, start, size, sortType);
+ }
+
+
+
+ // --------- 过期时间相关操作
+
+ /**
+ * 获取指定 key 的剩余存活时间 (单位:秒)
+ * @param key 指定 key
+ * @return 这个 key 的剩余存活时间,返回-1=永不过期,返回-2=无此键
+ */
+ long getKeyTimeout(String key) {
+ final Iterator> values = timedCache.cacheObjIterator();
+ CacheObj co;
+ while (values.hasNext()) {
+ co = values.next();
+ if(co.getKey().equals(key)) {
+ long ttl = co.getTtl();
+ // 在 Hutool-TimedCache 中,ttl=0 (或<0) 代表永不过期,统一返回 Sa-Token 可以理解的 -1
+ if(ttl <= 0) {
+ return NEVER_EXPIRE;
+ }
+ // 不为 0,那就计算一下剩余有效期
+ // 单位:毫秒
+ long timeout = ttl - (System.currentTimeMillis() - co.getLastAccess());
+ if(timeout < 0) {
+ timeout = 0;
+ }
+ // 转秒返回
+ return timeout / 1000;
+ }
+ }
+ // 代码至此,说明缓存中没有这个值
+ return NOT_VALUE_EXPIRE;
+ }
+
+ // --------- 定时清理过期数据
+
+ /**
+ * 组件被安装时,开始刷新数据线程
+ */
+ @Override
+ public void init() {
+ // 定时清理间隔
+ int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
+ // 配置为<=0代表不启用定时清理
+ if(dataRefreshPeriod <= 0) {
+ return;
+ }
+ // 启用定时清理(转毫秒)
+ timedCache.schedulePrune(dataRefreshPeriod * 1000L);
+ }
+
+ /**
+ * 组件被卸载时,结束定时任务,不再定时清理过期数据
+ */
+ @Override
+ public void destroy() {
+ timedCache.cancelPruneSchedule();
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring.factories b/sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..80dfde47
--- /dev/null
+++ b/sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoForHutoolTimedCache
\ No newline at end of file
diff --git a/sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000..9fa612e9
--- /dev/null
+++ b/sa-token-plugin/sa-token-hutool-timed-cache/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.dev33.satoken.dao.SaTokenDaoForHutoolTimedCache
\ No newline at end of file