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