学习笔记--基于Sa-Token 实现Java项目单点登录+同端互斥检测
目录
同端互斥登录
单点登录SSO
架构选型
模式二: URL重定向传播
前后端分离
整体流程
准备工作
搭建客户端
搭建认证中心SSO Server
环境配置
开放认证接口
启动类
跨域处理
同端互斥登录
同端互斥登陆 模块
同端互斥登录指:同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线。比如 QQ 可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号。具体步骤如下
sa-token:# token 名称(同时也是 cookie 名称)token-name: praxisAI# token 有效期(单位:秒) 默认30天,-1 代表永久有效timeout: 2592000# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结active-timeout: -1# 是否开启同端互斥登录 (false开启)is-concurrent: false # 在多人登录同一账号时,是否共用一个 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
-
在配置文件中,将
isConcurrent
配置为false -
DeviceUtils
工具类:判断前端请求传来的设备信息,比如pc,后续设置到Sa-Token
登录态中
//设备工具类
public class DeviceUtils {//根据请求获取设备信息public static String getRequestDevice(HttpServletRequest request) {String userAgentStr = request.getHeader(Header.USER_AGENT.toString());// 使用 Hutool 解析 UserAgentUserAgent userAgent = UserAgentUtil.parse(userAgentStr);ThrowUtils.throwIf(userAgent == null, ErrorCode.OPERATION_ERROR, "非法请求");// 默认值是 PCString device = "pc";// 是否为小程序if (isMiniProgram(userAgentStr)) {device = "miniProgram";} else if (isPad(userAgentStr)) {// 是否为 Paddevice = "pad";} else if (userAgent.isMobile()) {// 是否为手机device = "mobile";}return device;}/*** 判断是否是小程序* 一般通过 User-Agent 字符串中的 "MicroMessenger" 来判断是否是微信小程序**/private static boolean isMiniProgram(String userAgentStr) {// 判断 User-Agent 是否包含 "MicroMessenger" 表示是微信环境return StrUtil.containsIgnoreCase(userAgentStr, "MicroMessenger")&& StrUtil.containsIgnoreCase(userAgentStr, "MiniProgram");}/*** 判断是否为平板设备* 支持 iOS(如 iPad)和 Android 平板的检测**/private static boolean isPad(String userAgentStr) {// 检查 iPad 的 User-Agent 标志boolean isIpad = StrUtil.containsIgnoreCase(userAgentStr, "iPad");// 检查 Android 平板(包含 "Android" 且不包含 "Mobile")boolean isAndroidTablet = StrUtil.containsIgnoreCase(userAgentStr, "Android")&& !StrUtil.containsIgnoreCase(userAgentStr, "Mobile");// 如果是 iPad 或 Android 平板,则返回 truereturn isIpad || isAndroidTablet;}
}
3. 登录时传入参数
// 使用 Sa-Token 登录,并指定设备,同端登录互斥
StpUtil.login(user.getId(), DeviceUtils.getRequestDevice(request));//例如 调用此方法登录后,同设备的会被顶下线(不同设备不受影响)
StpUtil.login(10001, "PC");
Sa-Token 在背后做了大量的工作,包括
-
检查此账号是否之前已有登录,为账号生成
Token
(uuid 风格) 凭证与Session
会话 -
记录 Token 活跃时间;
-
通知全局侦听器,xx 账号登录成功;
-
将
Token
注入到请求上下文返回给前端 等等其它工作……(利用cookie自动注入特性)-
Cookie 可以从后端控制往浏览器中写入 token 值。
-
Cookie 会在前端每次发起请求时自动提交 token 值。
-
JWT token模式
//1、登录成功后:后端将 token 返回到前端 SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); //包括tokenName和tokenValue return SaResult.data(tokenInfo); //返回给前端 //2.前端保存到localstorge中,下次请求带上 //3.后端Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权
记住我模式
原理:调用
StpUtil.login(10001, true)
,在浏览器写入一个持久Cookie
储存 Token,此时用户即使重启浏览器 Token 依然有效。
Sa-Token的登录授权,默认就是[记住我]
模式,为了实现[非记住我]
模式,你需要在登录时如下设置
// 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录 StpUtil.login(10001, false);
注销模块
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`:代表当前会话暂未登录 StpUtil.checkLogin(); // 移除登录态 StpUtil.logout();
获取当前登录用户
// 获取当前会话账号id, 如果未登录,则返回 null Object loginUserId = StpUtil.getLoginIdDefaultNull(); if (loginUserId == null) {throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); } User currentUser = this.getById((String) loginUserId); if (currentUser == null) {throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); } return currentUser;
单点登录SSO
Sa-Token
单点登录举个例子理解
假设系统被切割为多个部分:商城、论坛、直播、社交…… 如果用户每访问一个模块都要登录一次,那么这样就很麻烦了, 为了优化用户体验,需要一套机制将这N个系统的认证授权互通共享,让用户在一个系统登录之后,可以畅通无阻的访问其它所有系统。
凡是稍微上点规模的系统,统一认证中心都是绕不过去的槛。
架构选型
系统架构 | 采用模式 | 简介 | 文档链接 |
---|---|---|---|
前端同域 + 后端同 Redis | 模式一 | 共享 Cookie 同步会话 | 文档、示例 |
前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | 文档、示例 |
前端不同域 + 后端不同 Redis | 模式三 | Http请求获取会话 | 文档、示例 |
-
前端同域:指多个系统可以部署在同一个主域名之下,比如:
c1.domain.com
、c2.domain.com
、c3.domain.com
。 -
后端同Redis:指多个系统可以连接同一个Redis,PS:这里并不需要把所有项目的数据都放在同一个Redis中,Sa-Token提供了
[权限缓存与业务缓存分离]
的解决方案,详情戳: Alone独立Redis插件。 -
模式三:Http请求获取会话(Sa-Token对SSO提供了完整的封装,只需要复制几段代码便可以轻松集成)
根据本项目场景,后端同Redis,所以选择模式二+ 前后端分离架构
SaToken SSO优势
-
API 简单易用,且官方文档介绍很详细。
-
支持三种模式,是否跨域、是否共享Redis、是否前后端分离 等场景很轻松解决
-
内置域名校验、密钥校验、
Token
防窃取,安全性很高 -
不丢参数,相比其他单点登录框架,
Sa-Token-SSO
内有专门的算法保证了参数不丢失,登录成功之后原路返回页面。
模式二: URL重定向传播
如果多个系统部署在不同的域名之下,但是后端可以连接同一个Redis,那么便可以使用 [URL重定向传播会话]
的方式做到单点登录
首先我们再次复习一下,多个系统之间为什么无法同步登录状态?
前端的
Token
无法在多个系统下共享。后端的
Session
无法在多个系统间共享。第二点官方已使用 Alone独立Redis插件 做到权限缓存直连 SSO-Redis 数据中心,不再赘述。
未登录,客户端页面显示“登录”链接。
点击登录,携带当前页面
back
参数(包含客户端host),跳转到当前客户端的/sso/login
接口
未登录时,重定向到SSO服务器的登录页
用户在SSO登录页登录成功后,SSO服务器返回
ticket
,重定向回指定客户端客户端通过
SaSsoClientProcessor
向SSO服务器验证ticket
的有效性,获取用户ID使用
StpUtil.login(userId)
在本地登录用户
在跨域模式下, "共享Cookie方案" 的失效,所以必须采用一种新的方案来传递Token。
前后端分离
首先理解对于一个前后端分离项目,即我们的系统,整体流程是这样的
前端==>后端==>重定向到SSO认证中心(展示页面,所以SSO服务器代码是前后端不分离的,主要作用就是:展示登录页,校验登录和重定向)
这种情况要考虑跨域,即客户端与 SSO 服务器部署在不同域,缺点:前端需主动调用 /sso/getSsoAuthUrl
获取登录地址,并处理重定向逻辑
整体流程
-
前端页面点击
[登录]
后触发调用登录函数,调用后端接口/sso/login
,并携带back
参数( 包含客户端host )-
形如:
http://{sso-client}/sso/login?back=xxx
-
-
若子系统检测到此用户尚未登录,则直接重定向到SSO认证中心,并携带
redirect
参数(记录子系统的登录页URL)-
形如:
http://{sso-server}/sso/auth?redirect=xxx?back=xxx
-
-
用户进入 SSO认证中心 的登录页面,开始登录。
-
用户 输入账号密码 并 登录成功,SSO认证中心下放
ticket
码参数。重定向回客户端的登录接口/sso/login
-
形如:
http://{sso-client}/sso/login?back=xxx&ticket=xxxxxxxxx
-
-
客户端通过
SaSsoClientProcessor
向SSO服务器验证ticket
的有效性,获取用户ID,使用StpUtil.login(userId)
在本地登录用户 -
其他客户端尝试登录后,前面步骤一样,到了请求发到
SSO
服务器,会通过sso/auth
判断是否已经登录(已登录内部是通过redis查看用户id来判断的)然后直接返回Ticket进行登录即可
整个过程,除了第四步用户在SSO认证中心登录时会被打断,其余过程均是自动化的
当用户在另一个子系统再次点击[登录]
按钮,由于此用户在SSO认证中心已有会话存在, 所以第四步也将自动化,也就是单点登录的最终目的 一次登录,处处通行。
流程如下
准备工作
首先修改hosts文件(C:\Windows\System32\drivers\etc\hosts)
,添加以下IP映射,方便测试:
127.0.0.1 sa-sso-server.com #ip映射,用于测试多个客户端单点登录
127.0.0.1 sa-sso-client1.com
127.0.0.1 sa-sso-client2.com
搭建客户端
客户端就是我们的后端服务器,考虑到前后分离架构,比如用VUE3
前端,下面是一个模拟的前端例子
<!-- 项目首页 -->
<template><h2> Sa-Token SSO-Client 应用端(前后端分离版-Vue3) </h2><p>当前是否登录:<b>{{isLogin}}</b></p><p><router-link :to="loginUrl">登录</router-link> <!--点击登录后携带back参数请求后端sso/login接口--></p>
</template><script setup>
import { ref } from 'vue'
import {baseUrl, ajax} from './method-util.js'// 单点登录地址
const loginUrl = '/api/sso/login?back=' + encodeURIComponent(location.href);
// 是否登录
const isLogin = ref(false);// 1.查询当前会话是否登录
ajax('/api/sso/isLogin', {}, function (res) {console.log('/isLogin 返回数据:', res);isLogin.value = res.data;
})
</script>
<!-- Sa-Token-SSO-Client端-登录页 -->
<template>
</template>
<script setup>
import {onMounted} from "vue";
import {ajax, getParam} from './method-util.js';
import router from '../router';// 获取参数
const back = getParam('back') || router.currentRoute.value.query.back;
const ticket = getParam('ticket') || router.currentRoute.value.query.ticket;console.log('获取 back 参数:', back)
console.log('获取 ticket 参数:', ticket)// 页面加载后触发
onMounted(() => {if(ticket) { //2.如果ticket存在,则尝试通过ticket登录doLoginByTicket(ticket);} else {goSsoAuthUrl(); //3.不存在,则重定向至认证中心}
})// 重定向至认证中心方法
function goSsoAuthUrl() {ajax('/api/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {console.log('/api/sso/getSsoAuthUrl 返回数据', res);location.href = res.data;})
}// 根据ticket值登录 方法
function doLoginByTicket(ticket) {ajax('/api/sso/doLoginByTicket', {ticket: ticket}, function(res) {console.log('/api/sso/doLoginByTicket 返回数据', res);if(res.code === 200) {localStorage.setItem('satoken', res.data);location.href = decodeURIComponent(back);} else {alert(res.msg);}})
}
</script>
搭建后端
环境配置
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.40.0</version></dependency><!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis-jackson</artifactId><version>1.40.0</version></dependency><!-- 提供Redis连接池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!-- Sa-Token 插件:整合SSO --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-sso</artifactId><version>1.40.0</version></dependency><!-- Sa-Token插件:权限缓存与业务缓存分离 --><dependency><groupId>cn.dev33</groupId><artifactId>sa-token-alone-redis</artifactId><version>1.40.0</version></dependency>
配置文件中整合sso-client
相关配置+API密钥+redis
存ticket
sa-token:# token 名称(同时也是 cookie 名称)token-name: praxisAI# token 有效期 2天(单位:秒) 默认30天,-1 代表永久有效timeout: 172800# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结active-timeout: -1# 是否开启同端互斥登录 (false开启)is-concurrent: false# 在多人登录同一账号时,是否共用一个 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# SSO-相关配置sso-client:# SSO-Server 端主机地址server-url: http://sa-sso-server.com:9000sign:# API 接口调用秘钥,确保和服务端一致,sa-token官方文档会给secret-key: xxxx# 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)alone-redis:# Redis数据库索引 (默认为0)database: 1# Redis服务器地址host: xxx# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: xxxtimeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0
forest:# 关闭 forest 请求日志打印log-enabled: false
新建
Controller
作为客户端接口,用于重定向到SSO服务器和接收服务器参数
//前后台分离架构下集成SSO所需的代码 (SSO-Client端)
@RestController
public class H5Controller {// 当前是否登录@GetMapping("/sso/isLogin")public Object isLogin() {return SaResult.data(StpUtil.isLogin());}// 返回SSO认证中心登录地址 @GetMapping("/sso/getSsoAuthUrl")public SaResult getSsoAuthUrl(String clientLoginUrl) {String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");return SaResult.data(serverAuthUrl);}// 根据ticket进行登录@PostMapping("/sso/doLoginByTicket")public SaResult doLoginByTicket(String ticket) {SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/api/sso/doLoginByTicket");StpUtil.login(ctr.loginId, ctr.remainSessionTimeout);return SaResult.data(StpUtil.getTokenValue());}// 全局异常拦截 @ExceptionHandlerpublic SaResult handlerException(Exception e) {e.printStackTrace(); return SaResult.error(e.getMessage());}
}
搭建认证中心SSO Server
新建一个springboot项目作为认证中心,本项目后端系统算做一个客户端client
,后续引入多个后端系统,也可以按照客户端方式构建
环境配置
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.40.0</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-sso</artifactId><version>1.40.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-redis-jackson</artifactId><version>1.40.0</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
<!-- redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><!-- 除此之外还需要引入mysql数据库的一些依赖,因为要在认证中心实现登录校验 -->
# 端口
server:port: 9000
# Sa-Token 配置
sa-token:# ------- SSO-模式二相关配置 sso-server:# Ticket有效期 (单位: 秒),默认五分钟 ticket-timeout: 300# 所有允许的授权回调地址allow-url: "http://sa-sso-client1/api/sso/login,http://sa-sso-client2/api/sso/login"sign:# API 接口调用秘钥secret-key: xxx
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://xxx/xxxusername: xxxpassword: xxx# Redis配置 (SSO模式一和模式二使用Redis来同步会话)redis:# Redis数据库索引(默认为0)database: 1# Redis服务器地址host: xxx# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: xxx# 连接超时时间timeout: 10slettuce:pool:# 连接池最大连接数max-active: 200# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0forest: # 关闭 forest 请求日志打印log-enabled: false
开放认证接口
新建 SsoServerController
,用于对外开放接口,先直接拉官方server-demo
,在修改
//SSO Server端 Controller
@RestController
public class SsoServerController {//处理所有SSO相关请求:拆分式路由// SSO-Server:统一认证地址,接受参数:redirect=授权重定向地址// 作用: 用户未登录,重定向到登陆页面// ** 已登录,生成Ticket重定向回客户端**(已登录内部是通过redis查看用户id来判断的)@RequestMapping("/sso/auth")public Object ssoAuth() {return SaSsoServerProcessor.instance.ssoAuth();}// SSO-Server:RestAPI 登录接口,账号密码登录接口,接受参数:name、pwd// 作用: 处理登录表单提交,调用doLoginHandle进行验证// 验证成功后生成SSO票据并返回给客户端@RequestMapping("/sso/doLogin")public Object ssoDoLogin() {return SaSsoServerProcessor.instance.ssoDoLogin();}/*** 盐值,混淆密码*/public static final String SALT = "kk";@Resourceprivate UserService userService;// 配置SSO相关参数 @Autowiredprivate void configSso(SaSsoServerConfig ssoServer) {// 自定义API地址,用于修改统一认证中心的地址//SaSsoServerProcessor.instance.ssoServerTemplate.apiName.ssoAuth = "/sso/auth2";// 配置:未登录时返回的ViewssoServer.notLoginView = () -> {return new ModelAndView("sa-login.html");};// 配置:登录处理函数 参数:账号密码ssoServer.doLoginHandle = (name, pwd) -> {// 1. 校验if (StringUtils.isAnyBlank(name, pwd)) {return SaResult.error("参数为空");}if (name.length() < 4) {return SaResult.error( "账号错误");}if (pwd.length() < 8) {return SaResult.error("密码错误");}// 2. 加密String encryptPassword = DigestUtils.md5DigestAsHex((SALT + pwd).getBytes());// 查询用户是否存在QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userAccount", name);queryWrapper.eq("userPassword", encryptPassword);User user = userService.getUserInfo(queryWrapper);// 用户不存在if (user == null) {// 登录失败,重定向到 /sso/auth 并携带错误参数return "redirect:/sso/auth?error=用户不存在或密码错误";}// 获取当前请求HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();String device = DeviceUtils.getRequestDevice(request);// 使用 Sa-Token 登录,并指定设备,同端登录互斥StpUtil.login(user.getId(), device);StpUtil.getSession().set("user_login", user);return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());};}// 在 SsoServerController 或全局配置中添加@Beanpublic SaServletFilter saServletFilter() {return new SaServletFilter().setBeforeAuth(obj -> {// 设置响应头防止缓存SaHolder.getResponse().setHeader("Cache-Control", "no-cache, no-store, must-revalidate");SaHolder.getResponse().setHeader("Pragma", "no-cache");SaHolder.getResponse().setHeader("Expires", "0");});}
}
启动类
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}System.out.println();System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------");System.out.println("配置信息:" + SaSsoManager.getServerConfig());System.out.println();
}
跨域处理
配置项 sa-token.sso-server.allow-url=*
意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功
但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被Ticket
劫持的风险
假设攻击者根据模仿我们的授权地址,巧妙的构造一个URL
http://sa-sso-server.com:9000/sso/auth?redirect=https://www.baidu.com/
当不知情的小白被诱导访问了这个URL时,它将被重定向至百度首页,代表着用户身份的Ticket
码也显现到了URL之中
防范处理
对redirect参数
进行校验,如果其不在指定的URL列表中时,拒绝下放ticket
#SSO服务器端进行配置 sa-token: sso-server: # 配置允许单点登录的 url allow-url: "http://localhost:8101/sso/login" #配置到详细地址
为什么不直接回传 Token,而是先回传 Ticket,再用 Ticket 去查询对应的账号id?
Token 作为长时间有效的会话凭证,任何时候都不应该直接暴露在 URL 之中(虽然 Token 很安全,但会直接暴露为很多漏洞提供可乘之机)
为了让系统绝对安全,选择先回传 Ticket
,再由Ticket
获取账号id,而且 Ticket 一次性用完即废,提高安全性。
相关文章:
学习笔记--基于Sa-Token 实现Java项目单点登录+同端互斥检测
目录 同端互斥登录 单点登录SSO 架构选型 模式二: URL重定向传播 前后端分离 整体流程 准备工作 搭建客户端 搭建认证中心SSO Server 环境配置 开放认证接口 启动类 跨域处理 同端互斥登录 同端互斥登陆 模块 同端互斥登录指:同一类型设备上只允许单地…...
Can通信流程
下面给出一个更详细的 CAN 发送报文的程序流程说明,结合 HAL 库的使用及代码示例,帮助你了解每一步的具体操作和内部原理。 一、系统与外设初始化 1.1 HAL 库初始化 在 main() 函数开头,首先调用 HAL 库初始化函数: HAL_Init()…...
基于BClinux8部署Ceph 19.2(squid)集群
#作者:闫乾苓 文章目录 1.版本选择Ceph版本发布历史目前官方在维护的版本 2.部署方法3.服务器规划4.前置配置4.1系统更新4.2配置hosts cat >> /etc/hosts << EOFssh-keygenssh-copy-id ceph01ssh-copy-id ceph02ssh-copy-id ceph034.5 Python34.6 Syst…...
Workerman5.0如何实现一对一聊天
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
c#难点整理2
1.对象池的使用 就是先定义一系列的对象,用一个,调一个。 public class ObjectPool<T> where T : new(){private Queue<T> pool; // 用于存储对象的队列private int maxSize; // 对象池的最大容量// 构造函数public ObjectPool(int maxSi…...
STM32基础教程——定时器
前言 TIM定时器(Timer):STM32的TIM定时器是一种功能强大的外设模块,通过时基单元(包含预分频器、计数器和自动重载寄存器)实现精准定时和计数功能。其核心原理是:内部时钟(CK_INT)或…...
How to share files with Windows via samba in Linux mint 22
概述 Windows是大家日常使用最多的操作系统,在Windows主机之间,可以共享文件,那么如何在Windows主机与Linux主机之间共享文件呢? 要在Windows主机与Linux主机之间共享文件,我们可以借助Samba协议完成。借助Samba协议…...
[AI速读]如何构建高效的AMBA协议检查器(Checker IP)
在芯片验证过程中,检查器(Checker)是确保设计符合协议规范的关键工具。本文基于一篇技术论文,分享如何为AMBA协议(如AXI、AHB)构建可重用的检查器IP(Checker IP,简称CIP),并简化其核心思路,帮助工程师快速上手。 一、什么是Checker IP? Checker IP是一组用SystemVe…...
VBA-Excel
VBA 一、数据类型与变量 常用数据类型: Byte:字节型,0~255。Integer:整数型,用于存储整数值,范围 -32768 到 32767。Long:长整型,可存储更大范围的整数,范围 -214748364…...
网络华为HCIA+HCIP IPv6
目录 IPv4现状 IPv6基本报头 IPv6扩展报头 IPv6地址 IPv6地址缩写规范 编辑 IPv6地址分配 IPv6单播地址分配 IPv6单播地址接口标识 IPv6常见单播地址 - GUA (2 / 3 开头) IPv6常见单播地址 - ULA IPv6常见单播地址 - LLA IPv6组播地…...
LeetCode 每日一题 2025/3/17-2025/3/23
记录了初步解题思路 以及本地实现代码;并不一定为最优 也希望大家能一起探讨 一起进步 目录 3/17 1963. 使字符串平衡的最小交换次数3/18 2614. 对角线上的质数3/19 2610. 转换二维数组3/20 2612. 最少翻转操作数3/21 2680. 最大或值3/22 2643. 一最多的行3/23 2116…...
git tag以及git
git tag 以及git 一、先说收获吧 1. git bash 在windows上 类似于linux的bash提供的shell命令行窗口,可以执行很多linux命令,cd pwd ls vim cat touch mkdir,还可以用正则匹配查看标签。相当于在windows上装了一个小的linux。git init myproj…...
Android 自定义变形 MD5 算法
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ MD5是一种哈希函数,用于将任意长度的数据映射为一个固定长度的哈希值。它由 Ron Rivest 在 1991 年设计,是继 MD4 之后的改进版本。 M…...
【SpringBoot】MorningBox小程序的完整后端接口文档
以下是「晨光宅配」小程序的完整接口文档,涵盖了所有12个表的接口。 每个接口包括请求方法、URL、请求参数、响应格式和示例 接口文档 1. 用户模块 1.1 获取用户信息 URL: /user/{userId}方法: GET请求参数: userId (路径参数): 用户ID响应格式:{"userId": 1,&qu…...
2025年01月03日微创网络(杭州银行外包)前端面试
目录 html 块级元素和行内元素有哪些阴影的几个属性垂直水平居中的实现方式定位的几种方式盒子模型的方式js的数组方法有哪些vue2 vue3 区别vuex哈希路由和浏览器路由的区别浏览器缓存的几个方式react hooks的优势react 组件传值vue 组件传值如何进行性能优化前端监控get post…...
工单分类总结
微调BERT-base模型,构建层次化分类器,Top-3准确率达97.2%,并自动识别出问题的关键类别 1. 具体微调的BERT-base模型是什么模型? BERT-base模型是一个预训练的Transformer模型,包含12个Transformer块、12个自注意头和隐藏大小为768。该模型在大规模文本数据上进行了预训练…...
2025年了,5G还有三个新变化
最近舆论开始讨论5G为什么不火了?5G是不是停滞发展了。 实际上,5G不仅在发展,还迎来了它的升级版5G-A。 在今年西班牙举行的世界移动通信大会上,5G-A就是焦点之一。 被誉为全球通信领域风向标的MWC,汇聚了华为、中兴通…...
区块链交易所平台开发全解析
在数字化飞速发展的今天,区块链技术已成为金融领域的核心驱动力之一。作为数字货币交易的关键平台,区块链交易所的开发不仅涉及复杂的技术环节,还需要兼顾用户体验、安全性、合规性等多个方面。本文将深入探讨区块链交易所平台的开发流程、关…...
hexo+butterfly博客功能完善和美化(四)---博客上传
hexobutterfly博客功能完善和美化(四)—博客上传 这是最后一期讲美化和功能完善了,笔者会陆续把csdn上面的博客转移到我的博客网站上面,大家可以来访问 Darlingの妙妙屋 文章目录 hexobutterfly博客功能完善和美化(…...
源码分析之Leaflet中dom模块DomEvent.DoubleTap的实现原理
概述 DomEvent.DoubleTap模块是Leaflet中用于模拟双击(dbclick)事件的模块,主要解决移动端浏览器对双击事件支持不完善或延迟的问题,同时避免与标签(<label>)关联的表单元素误触发。 源码分析 源码实现如下 DomEvent.DoubleTap的源码实现如下&…...
记录一次,rabbitmq开启stomp插件之后,还是连不上15674端口的问题
原因是装在docker 里面的rabbitmq 没有映射15674端口,需重新删除容器之后重新运行 docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -p 15674:15674 -p 1883:1883 -p 15675:15675 rabbitmq:版本号 进入docker容器开启插件 docker exec -it rabbitm…...
git clone项目报错fatal: fetch-pack: invalid index-pack output问题
前情回顾:git项目放在公司服务器上面,克隆等操作需要连接VPN才能操作。由于项目比较大,网速比较慢,克隆项目经常出现fetch-pack: invalid index-pack output。在网上查找各种解决方法。也就这一种有点效果。仅供参考,不…...
【access开发】导入excel 并生成表
hi,大家好呀! 最近天气越来越暖了,在这个春暖花开的季节了,每天心情应该都是美美的,正所谓一年之计在于春,在这个美好的季节,大家一起努力学习学习吧!那我们来看看今天学点啥呢&…...
利用knn算法实现手写数字分类
利用knn算法实现手写数字分类 1.作者介绍2.KNN算法2.1KNN(K-Nearest Neighbors)算法核心思想2.2KNN算法的工作流程2.3优缺点2.4 KNN算法图示介绍 3.实验过程3.1安装所需库3.2 MNIST数据集3.3 导入手写数字图像进行分类3.4 完整代码3.5 实验结果 1.作者介…...
从零开始实现 C++ TinyWebServer 处理请求 HttpRequest类详解
文章目录 HTTP 请求报文HttpRequest 类实现 Init() 函数实现 ParseRequestLine() 函数实现 ParseHeader() 函数实现 ParsePath() 函数实现 ParseBody() 函数实现 ParsePost() 函数实现 ParseFromUrlEncoded() 函数实现 UserVerify() 函数实现 Parse() 函数HttpRequest 代码Http…...
Android开发layer-list
Android开发layer-list 它的用处可以在drawable上进行多图拼接,比如启动页,不想图片被拉伸就这么做。还有做某些线突出来。 示例代码: <?xml version"1.0" encoding"utf-8"?> <layer-list xmlns:android&q…...
如何提升库存系统的高并发和稳定性:算法与设计模式
库存系统是企业运营的核心模块,尤其是在电商、零售和供应链管理中,系统的高并发和稳定性直接影响订单处理的准确性和效率。面对海量订单、复杂的库存管理需求,如何在高并发环境下确保库存数据的准确性和系统的稳定性?本文将从架构…...
关于CNN,RNN,GAN,GNN,DQN,Transformer,LSTM,DBN你了解多少
以下是神经网络中常见的几种模型的简要介绍: 1. CNN (Convolutional Neural Network, 卷积神经网络) 用途: 主要用于图像处理和计算机视觉任务。特点: 通过卷积核提取局部特征,具有平移不变性,能够有效处理高维数据(如图像…...
设计模式之装饰器模式
装饰器模式(Decorator)依然是我们设计模式中的结构型模式,其中的构造思想仍然是对多个类进行组合使用,以达成系统调用实现指定功能的设计模式。装饰器模式不论在我们日常开发过程中还是在我们提升技术阅读源码过程中都是比较常见的,但是整体学…...
Mistral AI发布开源多模态模型Mistral Small 3.1:240亿参数实现超越GPT-4o Mini的性能
法国人工智能初创公司Mistral AI于2025年3月正式推出新一代开源模型Mistral Small 3.1 ,该模型凭借240亿参数的轻量级设计,在多项基准测试中表现优异,甚至超越了Google的Gemma 3和OpenAI的GPT-4o Mini等主流专有模型。 1、核心特性与优势 多…...
SpringBoot3实战(SpringBoot3+Vue3基本增删改查、前后端通信交互、配置后端跨域请求、数据批量删除(超详细))(3)
目录 一、从0快速搭建SpringBoot3工程、SpringBoot3集成MyBatis、PageHelper分页查询的详细教程。(博客链接) 二、实现前端与后端通信对接数据。(axios工具) (1)安装axios。(vue工程目录) (2)封装请求工具类。(request.js) <1&…...
LabVIEW发电平台数据采集系统
本文详细介绍了基于LabVIEW的摇臂式波浪发电平台数据采集系统的设计与实现。通过整合LabVIEW软件与多种传感器技术,本系统能够有效提升数据采集的准确性和效率,为波浪能的利用和发电设备的优化提供科学依据。 项目背景 随着全球能源需求增长和环境保…...
人工智能(AI)系统化学习路线
一、为什么需要系统化学习AI? 人工智能技术正在重塑各行各业,但许多初学者容易陷入误区: ❌ 盲目跟风:直接学习TensorFlow/PyTorch,忽视数学与算法基础。 ❌ 纸上谈兵:只看理论不写代码,无法解…...
oracle事务的组成
1)数据库事务由以下的部分组成: 一个或多个DML 语句 ; 一个 DDL(Data Definition Language – 数据定义语言) 语句; 一个 DCL(Data Control Language – 数据控制语言)语句; 2)事务的执行开始: 以第一个 DML 语句的执行作为开始 ,…...
第二十八篇 数据获取与数据分析:数仓体系下的专业化分工与协同
声明:文章内容仅供参考,需仔细甄别。文中技术名称属相关方商标,仅作技术描述;代码示例为交流学习用途,部分参考开源文档(Apache 2.0/GPLv3);案例数据已脱敏,技术推荐保持…...
SpringSecurity——前后端分离登录认证
SpringSecurity——前后端分离登录认证的整个过程 前端: 使用Axios向后端发送请求 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>登录</title><script src"https://cdn…...
【AI】Orin Nano+ubuntu22.04上移植YoloV11,并使用DeepStream测试成功
1、准备工作 使用 sdk-manager 烧写 OrinNano, JetPack版本为6.0 DP,对应操作系统为:Ubuntu22.04 参见博客:【NVIDIA】Jetson Orin Nano系列:烧写Ubuntu22.04 2、安装 PyTorch 2.1 下载依赖 1)安装onnx pip install onnx -i https://pypi.tuna.tsinghua.edu.cn/sim…...
RHCE 使用nginx搭建网站
一。准备工作 Windows dns映射 创建目录网页 vim 编辑内容 添加如下 重启nginx服务,在Windows浏览器进行测试...
arm linux下的读写信号量rw_semphore的实现
本文基于arm linux 5.10来介绍内核中使用的读写信号量rw remphore的实现代码。 内核中信号量结构体struct rw_semaphore的定义在include/linux/rwsem.h 32位architectures下,结构体struct rw_semaphore中的count的使用如下: 先来看信号量的定义和初始化…...
搭建主从DNS、nfs、nginx
任务需求: 客户端通过访问 www.nihao.com 后,能够通过 dns 域名解析,访问到 nginx 服务中由 nfs 共享的首页文件,内容为:Very good, you have successfully set up the system. 各个主机能够实现时间同步,…...
Qt6+QML实现Windows屏幕录制
前言 Qt6提供了更丰富的多媒体支持类,使用Qt6 QMediaCaptureSession、QScreenCapture、QMediaRecorder,来实现一个屏幕录制的demo,其中QScreenCapture 最低版本 Qt6.5。支持录制的清晰度设置,选择视频保存位置,UI使用…...
python二级每日十题(1)
\ 第一题,在Python中,变量名的命名规则:以字母或下划线开头,后面跟字母、下划线和数字;不能以数字开头.故选c项,博主正确 缩进:在逻辑行首的空白(空格和制表符)用来决定逻辑行的缩进层次&…...
记录一次truncate导致MySQL夯住的故障
目录 环境信息: 故障描述: 处理过程: 原理分析: show processlist结果中的system lock含义: truncate原理: 1. TRUNCATE 的执行流程 2、TRUNCATE 表导致数据库夯住的原因 3、 TRUNCATE 表导致…...
Java Web应用程序实现用户登录、学生信息管理和验证码验证以及页面跳转等基本功能(IDEA)含(Ajax、JSTL)
一、具体框架以及代码功能的展示: 1. 文件结构 web03: 项目根目录。 src: 包含Java源代码。 cn.lvb: 主包。 bean: 包含实体类,如 Book 和 Student。 controller: 包含处理HTTP请求的Servlet类,如 DoLogin, Index, StuList1, VerifyCode。 …...
【Mybatis-plus】在mybatis-plus中 if test标签如何判断 list不为空
博主介绍:✌全网粉丝22W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
MySQL 事务(Transaction)详解
MySQL 事务(Transaction)详解 1. 什么是事务? 事务(Transaction)是一组要么全部执行,要么全部回滚的 SQL 语句,用于保证数据一致性。事务一般用于银行转账、订单支付等操作,确保多个…...
Redis 知识点梳理
第一章 NoSQL数据库发展历史简介 1、 Web的历史发展历程 web1.0时代简介 web 1.0是以编辑为特征,网站提供给用户的内容是网站编辑进行编辑处理后提供的,用户阅读网站提供的内容这个过程是网站到用户的单向行为web1.0时代的代表站点为新浪,…...
github上传操作简单说明
前期准备 0.下载git(如果已经有了就不用了) 1.在GitHub上新建一个存储库 2.先在本地创建一个目录作为本地库目录,在目录里打开git bash进行上传 上传过程 echo "# Garbled_repair" >> README.md 作用:创建一个…...
在 ASP .NET Core 9.0 中使用 Scalar 创建漂亮的 API 文档
示例代码:https://download.csdn.net/download/hefeng_aspnet/90407900 Scalar 是一款可帮助我们为 API 创建精美文档的工具。与感觉有些过时的默认 Swagger 文档不同,Scalar 为 API 文档提供了全新而现代的 UI。其简洁的设计让开发人员可以轻松找到测试…...
针对 pdf.mjs 文件因 MIME 类型错误导致的 Failed to load module script 问题解决方案
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of “application/octet-stream”. Strict MIME type checking is enforced for module scripts per HTML spec. pdf.mjs 这种问题该如何处理 nginx 针对 pdf.…...