ruoyi若依框架SpringSecurity实现分析
系列文章
ruoyi若依框架学习笔记-01
ruoyi若依框架分页实现分析
ruoyi若依框架SpringSecurity实现分析
文章目录
- 系列文章
- 前言
- 具体分析
- 一、项目中的SpringSecurity版本
- 二、登录认证流程分析
- 三、权限鉴定
- 四、退出登录
- 五、SpringSecurity配置类
- 总结
前言
在ruoyi-vue若依框架中使用到了SpringSecurity作为认证授权的技术栈。今天,来分析一下若依中是如何实现认证授权的。
具体分析
一、项目中的SpringSecurity版本

可见,当前springsecurity的版本还是相对比较旧的security5。所以我们在自己的项目中可以对此进行重构,升级成security6的版本。但目前只是先分析一下它的实现原理。
二、登录认证流程分析

首先看一下登录控制器
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@PostMapping("/login")public AjaxResult login(@RequestBody LoginBody loginBody){AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),loginBody.getUuid());ajax.put(Constants.TOKEN, token);return ajax;}
内容比较简单,我们去看一下login方法
public String login(String username, String password, String code, String uuid){// 验证码校验validateCaptcha(username, code, uuid);// 登录前置校验loginPreCheck(username, password);// 用户验证Authentication authentication = null;try{//UsernamePasswordAuthenticationToken是Authenticatiion的实现类UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);//这里设置成上下文的意图是在身份验证过程中,//其他组件或方法可以获取到该对象,以便进行相关的操作,//比如记录登录日志、获取用户信息等。AuthenticationContextHolder.setContext(authenticationToken);// 该方法会去调用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(authenticationToken);}catch (Exception e){if (e instanceof BadCredentialsException){//这里去使用日志记录相关错误AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();}else{AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}}finally{//最后要在对应操作完成之后,清理ContextAuthenticationContextHolder.clearContext();}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));//这里就是拿到自定义封装的用户信息LoginUser loginUser = (LoginUser) authentication.getPrincipal();//将登录信息存入到数据库recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser);}
然后我们去看一下loadUserByUsername方法
com.ruoyi.framework.web.service.UserDetailsServiceImpl
@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{//从数据库中查询相关用户数据SysUser user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)){log.info("登录用户:{} 不存在.", username);throw new ServiceException(MessageUtils.message("user.not.exists"));}else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())){log.info("登录用户:{} 已被删除.", username);throw new ServiceException(MessageUtils.message("user.password.delete"));}else if (UserStatus.DISABLE.getCode().equals(user.getStatus())){log.info("登录用户:{} 已被停用.", username);throw new ServiceException(MessageUtils.message("user.blocked"));}//密码校验,这里主要是看用户在短时间内输错了多少次密码,防止过分重复登录passwordService.validate(user);//将用户信息封装成我们自定义的LoginUser对象,他是UserDetails的实现类return createLoginUser(user);}public UserDetails createLoginUser(SysUser user){return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));}
由于框架本身的UserDetails对象无法满足我们的需求,所以需要自定义一个实现类。
这里是UserDetails的实现类com.ruoyi.common.core.domain.model.LoginUser#serialVersionUID
public class LoginUser implements UserDetails
{private static final long serialVersionUID = 1L;/*** 用户ID*/private Long userId;/*** 部门ID*/private Long deptId;/*** 用户唯一标识*/private String token;/*** 登录时间*/private Long loginTime;/*** 过期时间*/private Long expireTime;/*** 登录IP地址*/private String ipaddr;/*** 登录地点*/private String loginLocation;/*** 浏览器类型*/private String browser;/*** 操作系统*/private String os;/*** 权限列表*/private Set<String> permissions;/*** 用户信息*/private SysUser user;public LoginUser(){}public LoginUser(SysUser user, Set<String> permissions){this.user = user;this.permissions = permissions;}public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions){this.userId = userId;this.deptId = deptId;this.user = user;this.permissions = permissions;}public Long getUserId(){return userId;}public void setUserId(Long userId){this.userId = userId;}public Long getDeptId(){return deptId;}public void setDeptId(Long deptId){this.deptId = deptId;}public String getToken(){return token;}public void setToken(String token){this.token = token;}@JSONField(serialize = false)@Overridepublic String getPassword(){return user.getPassword();}@Overridepublic String getUsername(){return user.getUserName();}/*** 账户是否未过期,过期无法验证*/@JSONField(serialize = false)@Overridepublic boolean isAccountNonExpired(){return true;}/*** 指定用户是否解锁,锁定的用户无法进行身份验证* * @return*/@JSONField(serialize = false)@Overridepublic boolean isAccountNonLocked(){return true;}/*** 指示是否已过期的用户的凭据(密码),过期的凭据防止认证* * @return*/@JSONField(serialize = false)@Overridepublic boolean isCredentialsNonExpired(){return true;}/*** 是否可用 ,禁用的用户不能身份验证* * @return*/@JSONField(serialize = false)@Overridepublic boolean isEnabled(){return true;}public Long getLoginTime(){return loginTime;}public void setLoginTime(Long loginTime){this.loginTime = loginTime;}public String getIpaddr(){return ipaddr;}public void setIpaddr(String ipaddr){this.ipaddr = ipaddr;}public String getLoginLocation(){return loginLocation;}public void setLoginLocation(String loginLocation){this.loginLocation = loginLocation;}public String getBrowser(){return browser;}public void setBrowser(String browser){this.browser = browser;}public String getOs(){return os;}public void setOs(String os){this.os = os;}public Long getExpireTime(){return expireTime;}public void setExpireTime(Long expireTime){this.expireTime = expireTime;}public Set<String> getPermissions(){return permissions;}public void setPermissions(Set<String> permissions){this.permissions = permissions;}public SysUser getUser(){return user;}public void setUser(SysUser user){this.user = user;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities(){return null;}
}
其实我觉得最后应该也要重写getAuthorities()方法内部,如下
if (ObjectUtils.isEmpty(authorities)) {authorities = new ArrayList<>();permissions.forEach(permission -> authorities.add(()->permission));}return authorities;
还望大佬发表看法。
在ruoyi-vue中有对认证失败的处理类
com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl
在其中直接将错误信息渲染返回给前端
@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)throws IOException{int code = HttpStatus.UNAUTHORIZED;String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));}
当用户需要访问其他接口的时候,我们需要验证请求头中是否携带合法的token,并将用户信息存入Authentication中。
在com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter中有token过滤器,验证token有效性。
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{@Autowiredprivate TokenService tokenService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException{//从token中获取用户信息,封装成LoginUser对象LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){//验证token是否合法tokenService.verifyToken(loginUser);//封装成authentication对象UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//设置详细信息可以获取到请求的详细信息,例如请求的IP地址、请求的会话ID等。//设置详细信息可以帮助记录日志、进行安全审计和监控等操作authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}chain.doFilter(request, response);}
}
三、权限鉴定
我们先看一下controller层方法的上面的注解

"@ss.hasPermi(‘system:menu:list’)"的意思是调用ss这个容器下的hasPermi方法
可以看到,ruoyi是自定义的权限校验方法。那我们来看一下这个容器
com.ruoyi.framework.web.service.PermissionService
@Service("ss")
public class PermissionService
{/*** 验证用户是否具备某权限* * @param permission 权限字符串* @return 用户是否具备某权限*/public boolean hasPermi(String permission){if (StringUtils.isEmpty(permission)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){return false;}PermissionContextHolder.setContext(permission);return hasPermissions(loginUser.getPermissions(), permission);}/*** 验证用户是否不具备某权限,与 hasPermi逻辑相反** @param permission 权限字符串* @return 用户是否不具备某权限*/public boolean lacksPermi(String permission){return hasPermi(permission) != true;}/*** 验证用户是否具有以下任意一个权限** @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表* @return 用户是否具有以下任意一个权限*/public boolean hasAnyPermi(String permissions){if (StringUtils.isEmpty(permissions)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){return false;}PermissionContextHolder.setContext(permissions);Set<String> authorities = loginUser.getPermissions();for (String permission : permissions.split(Constants.PERMISSION_DELIMETER)){if (permission != null && hasPermissions(authorities, permission)){return true;}}return false;}/*** 判断用户是否拥有某个角色* * @param role 角色字符串* @return 用户是否具备某角色*/public boolean hasRole(String role){if (StringUtils.isEmpty(role)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){return false;}for (SysRole sysRole : loginUser.getUser().getRoles()){String roleKey = sysRole.getRoleKey();if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))){return true;}}return false;}/*** 验证用户是否不具备某角色,与 isRole逻辑相反。** @param role 角色名称* @return 用户是否不具备某角色*/public boolean lacksRole(String role){return hasRole(role) != true;}/*** 验证用户是否具有以下任意一个角色** @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表* @return 用户是否具有以下任意一个角色*/public boolean hasAnyRoles(String roles){if (StringUtils.isEmpty(roles)){return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){return false;}for (String role : roles.split(Constants.ROLE_DELIMETER)){if (hasRole(role)){return true;}}return false;}/*** 判断是否包含权限* * @param permissions 权限列表* @param permission 权限字符串* @return 用户是否具备某权限*/private boolean hasPermissions(Set<String> permissions, String permission){return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));}
}
别忘了在配置类中加上注解 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
这里就不具体一个一个分析了,因为比较简单,都是很容易看懂的。
四、退出登录
若依中还自定义了退出登录处理类com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{@Autowiredprivate TokenService tokenService;/*** 退出处理* * @return*/@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException{LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser)){String userName = loginUser.getUsername();// 删除用户缓存记录tokenService.delLoginUser(loginUser.getToken());// 记录用户退出日志AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));}//返回信息交给前端渲染ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));}
}
五、SpringSecurity配置类
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 认证失败处理类*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出处理类*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token认证过滤器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域过滤器*/@Autowiredprivate CorsFilter corsFilter;/*** 允许匿名访问的地址*///这里我先不分析,以后补上@Autowiredprivate PermitAllUrlProperties permitAllUrl;/*** 解决 无法直接注入 AuthenticationManager** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception{return super.authenticationManagerBean();}/*** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问* hasRole | 如果有参数,参数表示角色,则其角色可以访问* permitAll | 用户可以任意访问* rememberMe | 允许通过remember-me登录的用户访问* authenticated | 用户登录后可访问*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception{// 注解标记允许匿名访问的urlExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());//我主要看这部分httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 禁用HTTP响应标头.headers().cacheControl().disable().and()// 认证失败处理类.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 注册register 验证码captchaImage 允许匿名访问.antMatchers("/login", "/register", "/captchaImage").permitAll()// 静态资源,可匿名访问.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();// 添加Logout filterhttpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);// 添加JWT filterhttpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加CORS filterhttpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 身份认证接口*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}
}
总结
其实ruoyi-vue中对SpringSecurity的使用非常时候我们用来复习SpringSecurity,以及学习它的编码风格,绝对收益不浅。
相关文章:
ruoyi若依框架SpringSecurity实现分析
系列文章 ruoyi若依框架学习笔记-01 ruoyi若依框架分页实现分析 ruoyi若依框架SpringSecurity实现分析 文章目录 系列文章前言具体分析一、项目中的SpringSecurity版本二、登录认证流程分析三、权限鉴定四、退出登录五、SpringSecurity配置类 总结 前言 在ruoyi-vue若依框…...
Habitat环境学习四:Habitat-sim基础用于导航——使用导航网格NavMesh
如何使用导航网格NavMesh 官方教程1、NavMesh基础定义1.1 使用NavMesh的原因1.2 什么是NavMesh 2、NavMesh的使用方法2.1 获取自上而下Top down view视角地图2.2 在NavMesh中进行查询以及随机产生可导航点2.3 查找最短路径2.4 场景加载NavMesh2.5 重新计算并生成NavMesh2.6 什么…...
python学习笔记 -- 字符串
目录 一、输出字符串的格式 二、字符串的一些函数 1、len函数:字符串长度 2、查找字符所在位置index 3、某字符在字符串中的个数count 4、字符切片 对字符串进行翻转 -- 利用步长 5、修改大小写字母: 6、判断开头和结尾 7、拆分字符串 一、输出…...
2024年GPT如何发展?
2023 年,人工智能领域最具影响的莫过于 GPT-4、ChatGPT 了。 ChatGPT 凭一己之力掀起了 AI 领域的热潮,火爆全球,似乎开启了第四次工业革命。 ChatGPT 入选《Nature》2023 年度十大人物(Nature’s 10),这…...
从REPR设计模式看 .NET的新生代类库FastEndpoints的威力
📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !序言 又到了一年年末,春节将至…...
前端入门:(五)JavaScript 续
10. 浏览器存储 10.1 Cookie的概念和使用 Cookie是一种存储在用户计算机上的小型文本文件,用于跟踪和识别用户。Cookie通常用于存储用户的偏好设置、会话信息等,可以通过JavaScript进行读取和设置。 // 示例:设置和读取Cookie document.co…...
研究多态恶意软件,探讨网络安全与AI
前言 近期ChatGPT火遍全球,AI技术被应用到了全球各行各业当中,国内外各大厂商也开始推出自己的ChatGPT,笔者所在公司在前段时间也推出了自研的安全GPT,AI技术在网络安全行业得到了很多的应用,不管是网络安全研究人员、…...
linux驱动工作原理
linux或者windows驱动是如何对上和对下工作的,请用中文回答 在Linux系统中,设备驱动程序通过在/dev目录下创建文件系统条目与硬件通信。应用程序通过打开这些文件来获取描述符,以此来与设备交互。驱动程序内部使用主次设备号来标识设备。而在…...
Rust语言入门(第3篇)
引用与借用 上一篇中,我们介绍了rust的所有权概念,若直接传递变量做函数参数,堆上的变量就会失去所有权,而栈上变量则由于复制,仍有所有权。 fn main(){let b 3;makes_copy(b);println!("after using a variab…...
电脑服务器离线安装.net framework 3.5解决方案(错误:0x8024402c )(如何确定当前系统是否安装NET Framework 3.5)
问题环境: 日常服务的搭建或多或少都会有需要到NET Framework 3.5的微软程序运行框架,本次介绍几种不同的安装方式主要解决运行在Windows 2012 以上的操作系统的服务。 NET Framework 3.5 是什么? .NET Framework是微软公司推出的程序运行框架…...
Three.js学习8:基础贴图
一、贴图 贴图(Texture Mapping),也翻译为纹理映射,“贴图”这个翻译更直观。 贴图,就是把图片贴在 3D 物体材质的表面,让它具有一定的纹理,来为 3D 物体添加细节的一种方法。这使我们能够添加…...
【Linux】进程学习(二):进程状态
目录 1.进程状态1.1 阻塞1.2 挂起 2. 进程状态2.1 运行状态-R进一步理解运行状态 2.2 睡眠状态-S2.3 休眠状态-D2.4 暂停状态-T2.5 僵尸状态-Z僵尸进程的危害 2.6 死亡状态-X2.7 孤儿进程 1.进程状态 1.1 阻塞 阻塞:进程因为等待某种条件就绪,而导致的…...
Spring Boot 笔记 003 Bean注册
使用Idea导入第三方jar包 在porn.xml种添加的第三方jar包依赖,并刷新 可以在启动类中尝试调用 以上放到启动类中,不推荐,建议创建一个专门定义的类 package com.geji.config;import cn.itcast.pojo.Country; import cn.itcast.pojo.Province;…...
PCIE 参考时钟架构
一、PCIe架构组件 首先先看下PCIE架构组件,下图中主要包括: ROOT COMPLEX (RC) (CPU); PCIE PCI/PCI-X Bridge; PCIE SWITCH; PCIE ENDPOINT (EP) (pcie设备); BUFFER; 各个器件的时钟来源都是由100MHz经过Buffer后提供。一个PCIE树上最多可以有256…...
【开源】JAVA+Vue.js实现在线课程教学系统
目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2.3 课时管理模块2.4 课程交互模块2.5 系统基础模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示4.1 管理后台4.2 用户网页 五、样例代码5.1 新增课程类型5.2 网站登录5.3 课…...
计算机网络(第六版)复习提纲29
第六章:应用层 SS6.1 域名系统DNS 1 DNS被设计为一个联机分布式数据库系统,并采用客户服务器方式(C/S) 2 域名的体系结构 3 域名服务器及其体系结构 A 域名服务器的分类 1 根域名服务器 2 顶级域名服务器(TLD服务器&a…...
有道ai写作,突破免费限制,无限制使用
预览效果 文末提供源码包及apk下载地址 有道ai写作python版 import hashlib import time import json import ssl import base64 import uuidfrom urllib.parse import quote import requests from requests_toolbelt.multipart.encoder import MultipartEncoder from Crypto…...
node.js 使用 elementtree 生成思维导图 Freemind 文件
请参阅: java : pdfbox 读取 PDF文件内书签 请注意:书的目录.txt 编码:UTF-8,推荐用 Notepad 转换编码。 npm install elementtree --save 编写 txt_etree_mm.js 如下 // 读目录.txt文件,使用 elementtree 生成思维导图 Free…...
Vue中路由守卫的详细应用
作为一名web前端开发者,我们肯定经常使用Vue框架来构建我们的项目。而在Vue中,路由是非常重要的一部分,它能够实现页面的跳转和导航,提供更好的用户体验。然而,有时我们需要在路由跳转前或跳转后执行一些特定的逻辑&am…...
Flink Checkpoint过程
Checkpoint 使用了 Chandy-Lamport 算法 流程 1. 正常流式处理(尚未Checkpoint) 如下图,Topic 有两个分区,并行度也为 2,根据奇偶数 我们假设任务从 Kafka 的某个 Topic 中读取数据,该Topic 有 2 个 Pa…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
Android屏幕刷新率与FPS(Frames Per Second) 120hz
Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数,单位是赫兹(Hz)。 60Hz 屏幕:每秒刷新 60 次,每次刷新间隔约 16.67ms 90Hz 屏幕:每秒刷新 90 次,…...
MySQL 数据库深度剖析:事务、SQL 优化、索引与 Buffer Pool
在当今数据驱动的时代,数据库作为数据存储与管理的核心,其性能与可靠性至关重要。MySQL 作为一款广泛使用的开源数据库,在众多应用场景中发挥着关键作用。在这篇博客中,我将围绕 MySQL 数据库的核心知识展开,涵盖事务及…...
JS的传统写法 vs 简写形式
一、条件判断与逻辑操作 三元运算符简化条件判断 // 传统写法 let result; if (someCondition) {result yes; } else {result no; }// 简写方式 const result someCondition ? yes : no;短路求值 // 传统写法 if (condition) {doSomething(); }// 简写方式 condition &…...
