后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权
目录
后端注册拦截器
实现对 WebMvcConfigurer 接口的类实现
静态变量
方法重写 注册 Spring Framework拦截器
Sa-Token中SaServletFilter拦截器
思考 为什么使用两个拦截器
1. Spring Framework 拦截器
2. SaServletFilter
为什么要注册两个拦截器?
总结
完整代码
后端注册权限验证接口扩展
实现 Satoken 的 StpInterface 接口
获取用户和权限码
在 Dao 层实现
第一张表是 菜单表
第二张表是 用户表
多表联查 SQL 语句
遍历获取权限 getPermissionList 方法
获取角色 getRoleList 方法
主要逻辑
权限与角色的关系
数据库查询
SaSession 和缓存
完整代码
后端自定义侦听器
doLogin — 用户登录时触发
总结
完整代码
后端注册拦截器
实现对 WebMvcConfigurer 接口的类实现
静态变量
一个是不需要鉴权的网址
一个是超时过期时间
方法重写 注册 Spring Framework拦截器
这段代码的目的是注册多个拦截器,在 Spring 应用中统一处理:
- 分页:处理分页相关的逻辑。
- 限流:通过 Redis 实现请求频率控制,防止滥用。
- 权限鉴权:使用 Sa-Token 框架进行请求的权限验证。
所有这些拦截器会在请求到达控制器之前执行,确保全局的功能逻辑一致性(如分页、限流和权限检查)。
@Override
public void addInterceptors(InterceptorRegistry registry) {// 注册分页拦截器registry.addInterceptor(new PageableInterceptor());// 注册Redis限流器registry.addInterceptor(accessLimitInterceptor);// 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
Sa-Token
中SaServletFilter
拦截器
根据文档里面的写法
@Beanpublic SaServletFilter getSaServletFilter() {return new SaServletFilter()// 拦截路径.addInclude("/**")// 放开路径.addExclude(EXCLUDE_PATH_PATTERNS)// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {SaHolder.getResponse()// 允许指定域访问跨域资源.setHeader("Access-Control-Allow-Origin", "*")// 允许所有请求方式.setHeader("Access-Control-Allow-Methods", "*")// 有效时间.setHeader("Access-Control-Max-Age", "3600")// 允许的header参数.setHeader("Access-Control-Allow-Headers", "*");// 如果是预检请求,则立即返回到前端SaRouter.match(SaHttpMethod.OPTIONS).free(r -> System.out.println("--------OPTIONS预检请求,不做处理")).back();})// 认证函数: 每次请求执行.setAuth(obj -> {// 检查是否登录SaRouter.match("/admin/**").check(r -> StpUtil.checkLogin());// 刷新token有效期if (StpUtil.getTokenTimeout() < timeout) {StpUtil.renewTimeout(1800);}// 输出 API 请求日志,方便调试代码SaManager.getLog().debug("请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());})// 异常处理函数:每次认证函数发生异常时执行此函数.setError(e -> {// 设置响应头SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if (e instanceof NotLoginException) {// todo 确实是这边有问题e.printStackTrace();return JSONUtil.toJsonStr(Result.fail(UNAUTHORIZED.getCode(), UNAUTHORIZED.getMsg()));}// TODO 服务器后端在这里无法捕获异常,仅仅将异常信息传给了前端e.printStackTrace();return SaResult.error(e.getMessage());});}
思考 为什么使用两个拦截器
1. Spring Framework 拦截器
Spring 框架中的拦截器通常是通过实现 HandlerInterceptor
接口,或通过继承 WebMvcConfigurer
类中的 addInterceptors
方法来注册的。这类拦截器一般用于以下目的:
- 日志记录:记录请求的日志。
- 权限检查:用于访问控制,判断用户是否有权限访问某些资源。
- 性能监控:统计接口响应时间等。
- 请求处理:在请求进入控制器前对请求进行预处理,或在请求完成后进行后处理。
Spring 的拦截器是基于 HandlerInterceptor
接口的,它是为 Spring MVC 控制器定制的,具有以下生命周期:
preHandle
:在请求到达控制器之前执行。postHandle
:在控制器方法执行后,渲染视图之前执行。afterCompletion
:视图渲染完毕后执行,通常用于清理资源。
在这段代码中,Spring 的拦截器被注册为:
- 分页拦截器:用于处理分页参数(如
page
和size
),确保分页逻辑的一致性。 - 限流拦截器:用于限制 API 请求的频率,防止过于频繁的请求对服务器造成压力。
- 权限拦截器:用于进行权限检查,确保用户请求的资源需要相应的权限。
2. SaServletFilter
SaServletFilter
是 Sa-Token 框架提供的一个过滤器,用于处理认证和权限管理。它作为一个 Servlet Filter
被引入到 Spring Web 应用中,并在请求进入控制器之前执行。SaServletFilter
的职责通常包括:
- 用户认证:检查请求是否携带有效的 token,判断用户是否登录。
- 跨域处理:配置跨域请求的响应头,确保前端能够正常访问接口。
- 权限控制:根据不同的 URL 路径,检查用户是否具有访问权限。
- 异常处理:在认证或权限检查失败时,返回统一的错误信息。
SaServletFilter
是一个全局过滤器,会拦截所有请求,处理权限相关的逻辑。它的作用是在用户访问接口时进行认证、权限检查、跨域处理等。
为什么要注册两个拦截器?
- 职责分离:
-
- Spring 拦截器 主要用于通用功能,如分页、限流、日志等,这些是应用中与业务逻辑和请求处理相关的通用功能。
SaServletFilter
主要负责安全相关的功能,如认证和权限检查。它是 Sa-Token 提供的专用过滤器,能够帮助应用实现基于 token 的权限控制。
- 功能互补:
-
SaServletFilter
是为了处理与用户认证、权限相关的安全需求,而 Spring 拦截器通常用于通用功能(如分页、限流)。这两者的作用并不冲突,反而可以互补,Spring 拦截器可以集中处理一些公共逻辑,SaServletFilter
则专注于用户认证和权限控制。
- 实现细粒度控制:
-
- 在复杂的应用中,你可能需要对某些路径进行分页和限流控制,但对于其他路径,你需要确保严格的权限检查。
SaServletFilter
提供了灵活的权限认证机制,而 Spring 的拦截器可以细分不同的逻辑(如分页、限流等),实现更精细的控制。
- 在复杂的应用中,你可能需要对某些路径进行分页和限流控制,但对于其他路径,你需要确保严格的权限检查。
- 统一认证与权限管理:
-
SaServletFilter
通过拦截所有请求并统一处理认证、跨域、权限等,确保每个请求都能经过安全检查。Spring 拦截器负责处理请求中的其他逻辑(如日志记录、分页等),这样可以将应用中的各个功能模块进行解耦,并且保证权限检查与认证操作的统一性。
总结
- Spring Framework 拦截器:主要负责处理与请求相关的公共逻辑,如分页、限流、日志记录等。
SaServletFilter
:主要负责安全相关的功能,执行认证、权限控制、跨域处理等操作。
通过同时注册这两个拦截器,应用能够既保持业务逻辑的清晰和分离,又能确保安全性(认证与权限管理)得到充分保障。两者各司其职,共同为应用提供完整的功能支持。
完整代码
package com.ican.satoken;import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
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.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import cn.hutool.json.JSONUtil;
import com.ican.interceptor.AccessLimitInterceptor;
import com.ican.interceptor.PageableInterceptor;
import com.ican.model.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import static com.ican.enums.StatusCodeEnum.UNAUTHORIZED;/*** SaToken配置** @author Dduo* @date 2024/11/28 22:12**/
@Slf4j
@Component
public class SaTokenConfig implements WebMvcConfigurer {@Autowiredprivate AccessLimitInterceptor accessLimitInterceptor;private final String[] EXCLUDE_PATH_PATTERNS = {"/swagger-resources","/webjars/**","/v2/api-docs","/doc.html","/favicon.ico","/login","/oauth/*",};private final long timeout = 600;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册分页拦截器registry.addInterceptor(new PageableInterceptor());// 注册Redis限流器registry.addInterceptor(accessLimitInterceptor);// 注册 Sa-Token 的注解拦截器,打开注解式鉴权功能registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");}@Beanpublic SaServletFilter getSaServletFilter() {return new SaServletFilter()// 拦截路径.addInclude("/**")// 放开路径.addExclude(EXCLUDE_PATH_PATTERNS)// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {SaHolder.getResponse()// 允许指定域访问跨域资源.setHeader("Access-Control-Allow-Origin", "*")// 允许所有请求方式.setHeader("Access-Control-Allow-Methods", "*")// 有效时间.setHeader("Access-Control-Max-Age", "3600")// 允许的header参数.setHeader("Access-Control-Allow-Headers", "*");// 如果是预检请求,则立即返回到前端SaRouter.match(SaHttpMethod.OPTIONS).free(r -> System.out.println("--------OPTIONS预检请求,不做处理")).back();})// 认证函数: 每次请求执行.setAuth(obj -> {// 检查是否登录SaRouter.match("/admin/**").check(r -> StpUtil.checkLogin());// 刷新token有效期if (StpUtil.getTokenTimeout() < timeout) {StpUtil.renewTimeout(1800);}// 输出 API 请求日志,方便调试代码SaManager.getLog().debug("请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());})// 异常处理函数:每次认证函数发生异常时执行此函数.setError(e -> {// 设置响应头SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if (e instanceof NotLoginException) {// todo 确实是这边有问题e.printStackTrace();return JSONUtil.toJsonStr(Result.fail(UNAUTHORIZED.getCode(), UNAUTHORIZED.getMsg()));}// TODO 服务器后端在这里无法捕获异常,仅仅将异常信息传给了前端e.printStackTrace();return SaResult.error(e.getMessage());});}}
后端注册权限验证接口扩展
实现 Satoken 的 StpInterface 接口
获取用户和权限码
根据文档里面的内容
每个用户 id 都对应一系列的权限码
每个账号都会拥有一组权限码集合,框架来校验这个集合中是否包含指定的权限码。
我们将权限码存到数据库里面
在 Dao 层实现
第一张表是 菜单表
第二张表是 用户表
多表联查 SQL 语句
<select id="selectPermissionByRoleId" resultType="java.lang.String">SELECT DISTINCT m.permsFROM t_menu mINNER JOIN t_role_menu rm ON m.id = rm.menu_idWHERE rm.role_id = #{roleId}AND m.is_disable = 0
</select>
遍历获取权限 getPermissionList
方法
/*** 返回一个账号所拥有的权限码集合** @param loginId 登录用户id* @param loginType 登录账号类型* @return 权限集合*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {// 声明权限码集合List<String> permissionList = new ArrayList<>();// 遍历角色列表,查询拥有的权限码for (String roleId : getRoleList(loginId, loginType)) {SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);List<String> list = roleSession.get("Permission_List", () -> menuMapper.selectPermissionByRoleId(roleId));permissionList.addAll(list);}// 返回权限码集合return permissionList;
}
- 角色与权限的查询:通过
getRoleList
获取用户角色,再通过getPermissionList
获取每个角色的权限。 - 会话缓存:使用
SaSession
缓存角色和权限信息,减少对数据库的查询。 - 自定义权限与角色管理:通过扩展
StpInterface
接口,灵活地实现了用户角色和权限的管理,适应应用中的业务需求。
获取角色 getRoleList
方法
@Override
public List<String> getRoleList(Object loginId, String loginType) {SaSession session = StpUtil.getSessionByLoginId(loginId);return session.get("Role_List", () -> roleMapper.selectRoleListByUserId(loginId));
}
- 功能:返回一个账号(
loginId
)所拥有的所有角色。 - 实现步骤:
-
- 通过
StpUtil.getSessionByLoginId(loginId)
获取当前登录用户的会话。 - 从会话中获取角色列表。如果会话中没有角色信息,则通过
roleMapper.selectRoleListByUserId(loginId)
查询数据库获取。 - 返回角色列表。
- 通过
主要逻辑
getRoleList
方法用于获取用户的角色列表。每个用户可以有多个角色,角色是权限的载体。getPermissionList
方法根据用户的角色列表,查询每个角色对应的权限。权限是基于角色的,角色是用户的身份标识。权限是用户访问特定资源的授权标识。
权限与角色的关系
- 在这个实现中,用户是通过角色来管理权限的。每个用户有一组角色,而每个角色又有一组权限。这种关系是典型的 角色权限控制(RBAC) 模式。
getRoleList
获取用户的角色列表,而getPermissionList
则是基于角色来获取相应的权限。
数据库查询
menuMapper.selectPermissionByRoleId(roleId)
:根据角色 ID 查询该角色所拥有的权限。权限通常与菜单或 API 请求相关。roleMapper.selectRoleListByUserId(loginId)
:根据用户 ID 查询该用户所拥有的角色。
SaSession
和缓存
SaSession
在这个实现中被用于缓存角色和权限信息。这样,避免每次请求都进行数据库查询。使用会话可以提高性能,避免重复查询数据库。
完整代码
package com.ican.satoken;import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.ican.mapper.MenuMapper;
import com.ican.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** 自定义权限验证接口扩展** @author Dduo*/
@Component
public class StpInterfaceImpl implements StpInterface {@Autowiredprivate MenuMapper menuMapper;@Autowiredprivate RoleMapper roleMapper;/*** 返回一个账号所拥有的权限码集合** @param loginId 登录用户id* @param loginType 登录账号类型* @return 权限集合*/@Overridepublic List<String> getPermissionList(Object loginId, String loginType) {// 声明权限码集合List<String> permissionList = new ArrayList<>();// 遍历角色列表,查询拥有的权限码for (String roleId : getRoleList(loginId, loginType)) {SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);List<String> list = roleSession.get("Permission_List", () -> menuMapper.selectPermissionByRoleId(roleId));permissionList.addAll(list);}// 返回权限码集合return permissionList;}/*** 返回一个账号所拥有的可用角色标识集合** @param loginId 登录用户id* @param loginType 登录账号类型* @return 角色集合*/@Overridepublic List<String> getRoleList(Object loginId, String loginType) {SaSession session = StpUtil.getSessionByLoginId(loginId);return session.get("Role_List", () -> roleMapper.selectRoleListByUserId(loginId));}}
后端自定义侦听器
doLogin
— 用户登录时触发
- 功能:每当用户成功登录时触发此方法。
- 主要步骤:
- 查询用户信息,包括头像和昵称。
- 解析用户的浏览器和操作系统信息(通过
UserAgentUtils.parseOsAndBrowser
)。 - 获取登录时的 IP 地址,并查询该 IP 的来源地(如城市名)。
- 获取当前的登录时间。
- 创建一个
OnlineUserResp
对象,包含用户的基本信息、登录 IP、操作系统和浏览器等信息,并将其保存到SaSession
中。 - 更新数据库中的用户信息(如 IP 地址、登录时间等)。
- 作用:此方法不仅处理了用户登录,还将用户的登录信息(如 IP 地址、设备信息等)存储到会话中,供后续使用。同时更新了数据库中的用户信息,以便后续管理。
总结
- 功能:
MySaTokenListener
实现了SaTokenListener
接口,并提供了用户登录、注销、踢下线等事件的自定义处理。
-
- 在登录时,记录用户的登录信息,包括设备、IP 地址、地理位置等,并将这些信息存储到 token 会话中。
- 在注销时,清除 token 会话中的用户信息。
- 扩展性:其他事件(如被踢下线、二级认证等)目前没有具体实现,但可以根据业务需求添加逻辑,比如发送通知、更新状态等。
- 依赖:该实现依赖于
UserMapper
(用于查询和更新用户信息)、IpUtils
(用于获取 IP 地址的来源)、UserAgentUtils
(用于解析用户的操作系统和浏览器信息)等工具类。
完整代码
/*** 每次登录时触发*/@Overridepublic void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {// 查询用户昵称User user = userMapper.selectOne(new LambdaQueryWrapper<User>().select(User::getAvatar, User::getNickname).eq(User::getId, loginId));// 解析browser和osMap<String, String> userAgentMap = UserAgentUtils.parseOsAndBrowser(request.getHeader("User-Agent"));// 获取登录ip地址String ipAddress = ServletUtil.getClientIP(request);// 获取登录地址String ipSource = IpUtils.getIpSource(ipAddress);// 获取登录时间LocalDateTime loginTime = LocalDateTime.now(ZoneId.of(SHANGHAI.getZone()));OnlineUserResp onlineUserResp = OnlineUserResp.builder().id((Integer) loginId).token(tokenValue).avatar(user.getAvatar()).nickname(user.getNickname()).ipAddress(ipAddress).ipSource(ipSource).os(userAgentMap.get("os")).browser(userAgentMap.get("browser")).loginTime(loginTime).build();// 更新用户登录信息User newUser = User.builder().id((Integer) loginId).ipAddress(ipAddress).ipSource(ipSource).loginTime(loginTime).build();userMapper.updateById(newUser);// 用户在线信息存入tokenSessionSaSession tokenSession = StpUtil.getTokenSessionByToken(tokenValue);tokenSession.set(ONLINE_USER, onlineUserResp);}
相关文章:

后端技术选型 sa-token校验学习 下 结合项目学习 后端鉴权
目录 后端注册拦截器 实现对 WebMvcConfigurer 接口的类实现 静态变量 方法重写 注册 Spring Framework拦截器 Sa-Token中SaServletFilter拦截器 思考 为什么使用两个拦截器 1. Spring Framework 拦截器 2. SaServletFilter 为什么要注册两个拦截器? 总结 …...

Vue.js组件开发-实现组件切换效果的两种方法 条件渲染、动态组件
在Vue.js中,实现组件切换效果通常依赖于条件渲染或动态组件。 方法一:条件渲染 条件渲染使用v-if、v-else-if和v-else指令来根据条件展示或隐藏组件。这种方法适用于需要在不同条件下展示不同组件的场景。 <template><div><button cli…...

primitive 的 Appearance编写着色器材质
import { nextTick, onMounted, ref } from vue import * as Cesium from cesium import gsap from gsaponMounted(() > { ... })// 1、创建矩形几何体,Cesium.RectangleGeometry:几何体,Rectangle:矩形 let rectGeometry new…...

Seata搭建
1.初识Seata Quick Start | Apache Seata 官网 2.准备nacos和 seata 启动nacos startup.cmd -m standalone账号nacos 密码nacos 搭建seata TC 这里下载的 1.4.2 seata-server-1.4.2 1.修改seata配置文件 registry.conf 这里我们使用nacos作为注册中心 和 配置中心 r…...

流浪猫流浪狗领养PHP网站源码
源码介绍 流浪猫流浪狗领养PHP网站源码,适合做猫狗宠物类的发信息发布。当然其他信息发布也是可以的。 导入数据库,修改数据库配置/application/database.php 设置TP伪静态,设置运行目录, 后台:/abcd.php/dashboard?…...

asammdf python 处理MF4文件库简介
asammdf 是一个功能强大的 Python 库,专门用于处理汽车行业常用的 MDF(Measured Data Format)文件。以下是 asammdf 的主要功能总结: 主要功能 读取和写入 MDF 文件: 支持 MDF 文件的版本 3.x 和 4.x。 能够读取和…...

【“软件工程”基础概念学习】
基础和相关概念 英文:Software Engineering 软:物体内部的组织疏松,受外力作用后容易改变形状软件: 计算机系统的组成部分,是指挥计算机进行计算、判断、处理信息的程序系统。通常分为系统软件和应用软件。借指某项活…...

省森林防火应急指挥系统
森林防火形势严峻 我国森林防火形势十分严峻,森林火灾具有季节性强、发现难、成灾迅速等特点,且扑救难度大、影响范围广、造成的损失重。因此,构建森林防火应急指挥系统显得尤为重要。 系统建设模式与架构 森林防火应急指挥系统采用大智慧…...

一键整理背包界面功能
一键整理功能 游戏《帕鲁》中的背包界面有一键整理的功能,就是玩家随意拖拽背包格子里的物品,然后导致背包界面看起来很凌乱,比如物品a在一个格子里数量为1,另一个格子里数量为3,或者还有空格杂夹在有物品的格子旁边,一键排序功能可以解决这个问题,(将相同物品整合到一…...

给DevOps加点料:融入安全性的DevSecOps
从前,安全防护只是特定团队的责任,在开发的最后阶段才会介入。当开发周期长达数月、甚至数年时,这样做没什么问题;但是现在,这种做法现在已经行不通了。 采用 DevOps 可以有效推进快速频繁的开发周期(有时…...

uniapp 使用 pinia 状态持久化
1.创建文件 stores -index.js -global.js2.对应文件内容 index.js 安装插件 npm i pinia-plugin-persistedstate import { createPinia } from pinia; import persist from pinia-plugin-persistedstate; const pinia createPinia(); pinia.use(persist); export default pi…...

HarmonyOS鸿蒙-@State@Prop装饰器限制条件
一、组件Components级别的状态管理: State组件内状态限制条件 1.State装饰的变量必须初始化,否则编译期会报错。 // 错误写法,编译报错 State count: number;// 正确写法 State count: number 10; 2.嵌套属性的赋值观察不到。 // 嵌套的…...

Java Web开发进阶——Spring Boot与Spring Data JPA
Spring Data JPA 是 Spring 提供的一种面向数据访问的持久化框架,它简化了 JPA 的实现,为开发者提供了一种快速操作数据库的方式。在结合 Spring Boot 使用时,开发者能够快速完成数据库访问层的开发。 1. 介绍Spring Data JPA 1.1 什么是Spr…...

Vue Router4
Vue Router 是 Vue.js 官方的路由管理器。Vue Router 基于路由和组件的映射关系,监听页面路径的变化,渲染对应的组件。 安装: npm install vue-router。 基本使用: // src/router/index.js import {createRouter, createWebHa…...

计算机网络之---应用层协议概述
应用层协议概述 应用层协议是OSI模型中的第7层(应用层)定义的一组规则,用于支持和管理不同应用程序之间的通信。应用层协议定义了数据交换的格式、规则和约定,使得不同的系统或应用能够互相理解并正确地交换数据。它直接面向用户并…...

html + css 顶部滚动通知栏示例
前言 在现代网页设计中,一个吸引人的顶部滚动通知栏不仅能够有效传达重要信息,还能提升用户体验。通过使用HTML和CSS,我们可以创建既美观又功能强大的组件,这些组件可以在不影响网站整体性能的情况下提供实时更新或紧急通知。 本…...

【Rust自学】11.6. 控制测试运行:并行和串行(连续执行)测试
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 11.6.1. 控制测试的运行方式 cargo test和cargo run一样,cargo test也会编译代码并生成一个二进制文件用于测试,…...

某漫画网站JS逆向反混淆流程分析
文章目录 1. 写在前面1. 接口分析2. 反混淆分析 【🏠作者主页】:吴秋霖 【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Pyth…...

React 中事件机制详细介绍:概念与执行流程如何更好的理解
React 的事件机制是一个非常重要的概念,它涉及到 React 如何处理用户的交互事件。React 的事件系统与传统的 DOM 事件系统有所不同,它在底层使用了事件委托和合成事件(Synthetic Events)来优化性能。下面,我们将从 Rea…...

Day04-后端Web基础(Maven基础)
目录 Maven课程内容1. Maven初识1.1 什么是Maven?1.2 Maven的作用1.2.1 依赖管理1.2.2 项目构建1.2.3 统一项目结构 2. Maven概述2.1 Maven介绍2.2 Maven模型2.3 Maven仓库2.4 Maven安装2.4.1 下载2.4.2 安装步骤 3. IDEA集成Maven3.1 配置Maven环境3.1.2 全局设置 3.2 Maven项…...

vue3模板语法+响应式基础
模板语法 1. disabled指令,可以用于禁用按钮 <button :disabled"isButtonDisabled">Button</button> //:disabled是一个指令,用于根据isButtonDisabled的值来动态控制按钮的禁用状态。 使用场景: 1.防止用户重复点击…...

【面试题】简单聊一下什么是云原生、什么是k8s、容器,容器与虚机相比优势
云原生(Cloud Native) 定义:云原生是一种构建和运行应用程序的方法,旨在充分利用云计算的优势。它涵盖了一系列技术和理念,包括容器化、微服务架构、自动化部署与管理等。特点:云原生应用程序被设计为可弹性…...

数据挖掘实训:天气数据分析与机器学习模型构建
随着气候变化对各行各业的影响日益加剧,精准的天气预测已经变得尤为重要。降雨预测在日常生活中尤其关键,例如农业、交通和灾害预警等领域。本文将通过机器学习方法,利用历史天气数据预测明天是否会下雨,具体内容包括数据预处理、…...

STM32如何使用内部晶振作为晶振
目录 前言 首先说明一下芯片内部并没有时钟, 而是内部振荡。使用内部振荡的好处是外部无需设计晶振电路 ,再说的简单点 ,不用外部晶振依然可以让单片机正常运转。 环境: 芯片:STM32F103C8T6 Keil:V5.24…...

【Maui】导航栏样式调整
前言 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移动和桌面应用。 使用 .NET MAUI,可从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。 .NET MAUI 是一款开放源代码应用,是 X…...

【黑马程序员三国疫情折线图——json+pyechart=数据可视化】
json数据在文末 将海量的数据处理成我们肉眼可以进行分析的形式,数据的可视化,可以分为两个步骤: 数据处理:利用三方网站厘清json层次格式化,再对文件的读取、检查是否符合JSON规范以及规范化、JSON格式的转化&#…...

如何实现多级缓存?
本文重点说一说在Java应用中,多级缓存如何实现。 多级缓存是比较常见的一种性能优化的手段,一般来说就是本地缓存分布式缓存。 本地缓存一般采用Caffeine和Guava,这两种是性能比较高的本地缓存的框架。他们都提供了缓存的过期、管理等功能。…...

Saas数据库迁移单租户数据
1、背景 租户使用Saas系统,用一段时间后要将系统、数据搬迁到自建服务器。该Saas系统没有按租户分库,且数据库数据量太大,需要将单租户的数据抽取出来。Saas系统使用Mysql5.7数据库,主要使用INFORMATION_SCHEMA.COLUMNS表进行数据…...

LeetCode100之括号生成(22)--Java
1.问题描述 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 示例1 输入:n 3 输出:["((()))","(()())","(())()","()(())","()()()&qu…...

阿里云ios镜像源
阿里云镜像源:阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 下载centos7...