mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-04-05 08:37:21 +08:00
feat: 新增设备锁登录示例
This commit is contained in:
parent
6c3dd1f222
commit
81727ec4cf
@ -11,6 +11,7 @@ cd sa-token-demo-alone-redis-cluster & call mvn clean & cd ..
|
||||
cd sa-token-demo-beetl & call mvn clean & cd ..
|
||||
cd sa-token-demo-bom-import & call mvn clean & cd ..
|
||||
cd sa-token-demo-case & call mvn clean & cd ..
|
||||
cd sa-token-demo-device-lock & call mvn clean & cd ..
|
||||
cd sa-token-demo-grpc & call mvn clean & cd ..
|
||||
cd sa-token-demo-hutool-timed-cache & call mvn clean & cd ..
|
||||
cd sa-token-demo-jwt & call mvn clean & cd ..
|
||||
|
@ -14,6 +14,7 @@
|
||||
<module>sa-token-demo-beetl</module>
|
||||
<module>sa-token-demo-bom-import</module>
|
||||
<module>sa-token-demo-case</module>
|
||||
<module>sa-token-demo-device-lock</module>
|
||||
<module>sa-token-demo-dubbo/sa-token-demo-dubbo-provider</module>
|
||||
<module>sa-token-demo-dubbo/sa-token-demo-dubbo-consumer</module>
|
||||
<module>sa-token-demo-dubbo/sa-token-demo-dubbo3-provider</module>
|
||||
|
69
sa-token-demo/sa-token-demo-device-lock-h5/common.js
Normal file
69
sa-token-demo/sa-token-demo-device-lock-h5/common.js
Normal file
@ -0,0 +1,69 @@
|
||||
// 服务器接口主机地址
|
||||
var baseUrl = "http://localhost:8081";
|
||||
|
||||
// 封装一下Ajax
|
||||
function ajax(path, data, successFn) {
|
||||
console.log(baseUrl + path);
|
||||
fetch(baseUrl + path, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'satoken': localStorage.getItem('satoken')
|
||||
},
|
||||
body: serializeToQueryString(data),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(res => {
|
||||
console.log('返回数据:', res);
|
||||
successFn(res);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('提交失败:', error);
|
||||
return alert("异常:" + JSON.stringify(error));
|
||||
});
|
||||
}
|
||||
|
||||
// 获取本地的 设备id
|
||||
function getLocalDeviceId() {
|
||||
let localDeviceId = localStorage.getItem('local-device-id');
|
||||
if(!localDeviceId) {
|
||||
localDeviceId = randomString(60);
|
||||
localStorage.setItem('local-device-id', localDeviceId);
|
||||
}
|
||||
return localDeviceId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------ 工具方法 ---------------
|
||||
|
||||
// 从url中查询到指定名称的参数值
|
||||
function getParam(name, defaultValue){
|
||||
var query = window.location.search.substring(1);
|
||||
var vars = query.split("&");
|
||||
for (var i=0;i<vars.length;i++) {
|
||||
var pair = vars[i].split("=");
|
||||
if(pair[0] == name){return pair[1];}
|
||||
}
|
||||
return(defaultValue == undefined ? null : defaultValue);
|
||||
}
|
||||
|
||||
// 将 json 对象序列化为kv字符串,形如:name=Joh&age=30&active=true
|
||||
function serializeToQueryString(obj) {
|
||||
return Object.entries(obj)
|
||||
.filter(([_, value]) => value != null) // 过滤 null 和 undefined
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
// 随机生成字符串
|
||||
function randomString(len) {
|
||||
len = len || 32;
|
||||
var $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
|
||||
var maxPos = $chars.length;
|
||||
var str = '';
|
||||
for (i = 0; i < len; i++) {
|
||||
str += $chars.charAt(Math.floor(Math.random() * maxPos));
|
||||
}
|
||||
return str;
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>设备锁测试-认证页</title>
|
||||
<style type="text/css">
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-box">
|
||||
<h2>设备锁测试-认证页</h2>
|
||||
<div style="color: red;">您正在一台新设备上登录此账号,需要进行身份验证</div>
|
||||
<div>您绑定的手机号为:<b class="phone"></b></div>
|
||||
<div>
|
||||
验证码:<input name="ck" >
|
||||
<button class="send-code" onclick="sendCode()">发送验证码</button>
|
||||
</div>
|
||||
<div><button onclick="checkCode()">确认</button></div>
|
||||
</div>
|
||||
<script src="common.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 获取手机号数据
|
||||
function getPhone() {
|
||||
ajax('/acc/getPhone', { deviceId: getLocalDeviceId() }, function(res) {
|
||||
if(res.code == 200) {
|
||||
document.querySelector('.phone').innerHTML = res.data;
|
||||
} else {
|
||||
alert('失败:' + res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
getPhone();
|
||||
|
||||
// 发送验证码
|
||||
function sendCode(){
|
||||
ajax('/acc/sendCode', { deviceId: getLocalDeviceId() }, function(res) {
|
||||
if(res.code == 200) {
|
||||
alert('验证码发送成功,请注意接收');
|
||||
document.querySelector('.send-code').disabled = true;
|
||||
}
|
||||
// 触发设备锁校验,需要进一步去认证
|
||||
else {
|
||||
alert('失败:' + res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
function checkCode(){
|
||||
ajax('/acc/checkCode', { deviceId: getLocalDeviceId(), code: document.querySelector('[name=ck]').value }, function(res) {
|
||||
if(res.code == 200) {
|
||||
alert('验证成功!');
|
||||
localStorage.setItem('satoken', res.token);
|
||||
location.href = 'index.html';
|
||||
}
|
||||
// 触发设备锁校验,需要进一步去认证
|
||||
else {
|
||||
alert('失败:' + res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
43
sa-token-demo/sa-token-demo-device-lock-h5/index.html
Normal file
43
sa-token-demo/sa-token-demo-device-lock-h5/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>设备锁测试-首页</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>设备锁测试-首页</h2>
|
||||
<p>当前是否登录:<b class="is-login"></b></p>
|
||||
<p>
|
||||
<a href="login.html">登录</a>
|
||||
<a href="javascript: doLogout(); ">注销</a>
|
||||
<!-- <a href="javascript: doLogout(); ">注销</a> <span style="color: #888;">(需要重新验证设备)</span>
|
||||
<a href="javascript: doLogout2(); ">注销2</a> <span style="color: #888;">(不需要重新验证设备)</span> -->
|
||||
</p>
|
||||
<script src="common.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 查询当前会话是否登录
|
||||
function isLogin(){
|
||||
ajax('/acc/isLogin', {}, function(res) {
|
||||
document.querySelector('.is-login').innerHTML = res.data;
|
||||
})
|
||||
}
|
||||
isLogin();
|
||||
|
||||
|
||||
// 注销
|
||||
function doLogout(){
|
||||
ajax('/acc/logout', {}, function(res) {
|
||||
isLogin();
|
||||
})
|
||||
}
|
||||
|
||||
// 注销2
|
||||
function doLogout2(){
|
||||
localStorage.removeItem('satoken');
|
||||
isLogin();
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
47
sa-token-demo/sa-token-demo-device-lock-h5/login.html
Normal file
47
sa-token-demo/sa-token-demo-device-lock-h5/login.html
Normal file
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>设备锁测试-登录页</title>
|
||||
<style type="text/css">
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-box">
|
||||
<h2>设备锁测试-登录页</h2>
|
||||
<div>用户:<input name="name" type="text"></div>
|
||||
<div>密码:<input name="pwd" type="password"></div>
|
||||
<div><button onclick="doLogin()">登录</button></div>
|
||||
</div>
|
||||
<script src="common.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 登录方法
|
||||
function doLogin() {
|
||||
const data = {
|
||||
name: document.querySelector('[name=name]').value,
|
||||
pwd: document.querySelector('[name=pwd]').value,
|
||||
deviceId: getLocalDeviceId()
|
||||
}
|
||||
ajax('/acc/doLogin', data, function(res) {
|
||||
console.log(res);
|
||||
if(res.code == 200) {
|
||||
alert('登录成功!');
|
||||
localStorage.setItem('satoken', res.token);
|
||||
location.href = 'index.html';
|
||||
}
|
||||
// 触发设备锁校验,需要进一步去认证
|
||||
else if(res.code == 421) {
|
||||
location.href = 'device-lock-auth.html';
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
55
sa-token-demo/sa-token-demo-device-lock/pom.xml
Normal file
55
sa-token-demo/sa-token-demo-device-lock/pom.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-device-lock</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.14</version>
|
||||
<!--<version>2.3.0.RELEASE</version>-->
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.40.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenDeviceLockApplication</java.run.main.class>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token整合 Redis -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -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 测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenDeviceLockApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenDeviceLockApplication.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.pj.current;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
@ExceptionHandler
|
||||
public SaResult handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) {
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
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.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
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 -> {
|
||||
// 输出 API 请求日志,方便调试代码
|
||||
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(obj -> {
|
||||
// ---------- 设置一些安全响应头 ----------
|
||||
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")
|
||||
|
||||
// ---------- 设置跨域响应头 ----------
|
||||
// 允许指定域访问跨域资源
|
||||
.setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*");
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginParameter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.pj.util.DeviceLockCheckUtil;
|
||||
import com.pj.util.PhoneCodeUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
@Autowired
|
||||
SysUserMockDao userMockDao;
|
||||
|
||||
// 账号密码登录
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd, String deviceId) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
long userId = userMockDao.getUserIdByName(name);
|
||||
|
||||
// 登录前,检测设备锁
|
||||
if ( ! StpUtil.isTrustDeviceId(userId, deviceId)) {
|
||||
DeviceLockCheckUtil.setDeviceIdToUserId(deviceId, 10001);
|
||||
// 与前端约定好,返回421表示此设备需要验证
|
||||
return SaResult.get(421, "新设备登录,需要验证设备", deviceId);
|
||||
}
|
||||
|
||||
// 登录
|
||||
return login(userId, deviceId);
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 查询登录状态
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.data(StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 注销登录
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 返回设备id绑定的 userId 的手机号,脱敏形式
|
||||
@RequestMapping("getPhone")
|
||||
public SaResult getPhone(String deviceId) {
|
||||
long userId = DeviceLockCheckUtil.getUserIdByDeviceId(deviceId);
|
||||
String phone = userMockDao.getPhoneByUserId(userId);
|
||||
return SaResult.data(phone.substring(0, 3) + "****" + phone.substring(7));
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
@RequestMapping("sendCode")
|
||||
public SaResult sendCode(String deviceId) {
|
||||
long userId = DeviceLockCheckUtil.getUserIdByDeviceId(deviceId);
|
||||
String phone = userMockDao.getPhoneByUserId(userId);
|
||||
PhoneCodeUtil.sendCode(phone);
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
@RequestMapping("checkCode")
|
||||
public SaResult checkCode(String deviceId, String code) {
|
||||
long userId = DeviceLockCheckUtil.getUserIdByDeviceId(deviceId);
|
||||
String phone = userMockDao.getPhoneByUserId(userId);
|
||||
PhoneCodeUtil.checkCode(phone, code);
|
||||
// 校验通过,开始登录
|
||||
return login(userId, deviceId);
|
||||
}
|
||||
|
||||
// 指定账号登录
|
||||
private SaResult login(long userId, String deviceId) {
|
||||
StpUtil.login(userId, new SaLoginParameter().setDeviceId(deviceId));
|
||||
return SaResult.ok("登录成功").set("token", StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 模拟数据库操作类
|
||||
*
|
||||
* @author click33
|
||||
* @since 2025/3/5
|
||||
*/
|
||||
@Service
|
||||
public class SysUserMockDao {
|
||||
|
||||
// 返回指定 userId 绑定的手机号
|
||||
public String getPhoneByUserId(long userId) {
|
||||
return "13112341234";
|
||||
}
|
||||
|
||||
// 返回指定用户名对应的 userId
|
||||
public long getUserIdByName(String name) {
|
||||
return 10001;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.pj.util;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* 设备锁操作工具类
|
||||
* @author click33
|
||||
* @since 2025/3/5
|
||||
*/
|
||||
public class DeviceLockCheckUtil {
|
||||
|
||||
/**
|
||||
* 保存设备id与用户id的映射关系
|
||||
* @param deviceId /
|
||||
* @param userId /
|
||||
*/
|
||||
public static void setDeviceIdToUserId(String deviceId, long userId) {
|
||||
if(SaFoxUtil.isEmpty(deviceId) || SaFoxUtil.isEmpty(userId)) {
|
||||
throw new RuntimeException("设备id或用户id不能为空");
|
||||
}
|
||||
SaManager.getSaTokenDao().set(saveKeyPrefix() + deviceId, String.valueOf(userId), 1200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回设备id绑定的用户id
|
||||
* @param deviceId /
|
||||
*/
|
||||
public static long getUserIdByDeviceId(String deviceId) {
|
||||
String userIdStr = SaManager.getSaTokenDao().get(saveKeyPrefix() + deviceId);
|
||||
if(userIdStr == null) {
|
||||
throw new RuntimeException("此设备id目前未绑定任何用户");
|
||||
}
|
||||
return Long.parseLong(userIdStr);
|
||||
}
|
||||
|
||||
// 返回数据保存时使用的前缀
|
||||
public static Object saveKeyPrefix() {
|
||||
return SaManager.getConfig().getTokenName() + ":device-to-userid:";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.pj.util;//package com.pj.oauth2.custom;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* 手机验证码工具类 (仅做逻辑模拟,不做真实发送)
|
||||
*
|
||||
* @author click33
|
||||
* @since 2024/8/23
|
||||
*/
|
||||
public class PhoneCodeUtil {
|
||||
|
||||
// 指定手机号发送验证码
|
||||
public static void sendCode(String phone) {
|
||||
String code = SaFoxUtil.getRandomNumber(100000, 999999) + "";
|
||||
SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5);
|
||||
System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功");
|
||||
}
|
||||
|
||||
// 校验验证码是否正确,不正确则抛出异常
|
||||
public static void checkCode(String phone, String code) {
|
||||
String oldCode = SaManager.getSaTokenDao().get("phone_code:" + phone);
|
||||
if( ! code.equals(oldCode) ) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
// 验证通过后,立即删除验证码
|
||||
SaManager.getSaTokenDao().delete("phone_code:" + phone);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||
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: false
|
||||
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user