用户登录认证和权限授权(SpringSecurity、JWT、session)
文章目录
- 前言
- 一、登录认证
- 1. 问题引入
- 2. Session
- 2.1 实现原理
- 2.2 过滤器Filter
- 2.3 上下文对象
- 3. JWT
- 3.2 实现步骤
- 3.3 拦截器 HandlerInterceptorAdapter
- 3.4 上下文对象
- 4. Session VS JWT
- 二、权限授权
- 1. 权限类型
- 1.1 页面权限(菜单项权限)
- 1.2 ACL模型 (Access Control List访问控制列表)不推荐
- 1.3 RBAC模型(Role-Based Access Controller基于角色访问控制模型)
- 1.2 接口权限(操作权限或按钮权限)
- 1.3 数据权限
- 三、SpringSecurity
- 1. 概述
- 2. 登录认证
- 2.1 了解各个组件
- 2.2 JWT+SpringSecurity
- 3. 权限授权
- 总结
前言
登录认证和权限授权是所有项目中必不可少的功能,本篇文章首先将通过最简单的方式(Session和JWT)实现登录认证和权限授权,然后再整合Springsecurity框架实现。
一、登录认证
登录认证简言之就是对用户的身份进行确认,判断用户的账号和密码是否正确。
1. 问题引入
HTTP请求是一个无状态的协议,每一次发送的请求都是独立的,需要一种机制来记住用户登录过? 需要凭证
怎么保存凭证?
Session/JWT ,原理都是token机制
- 前端发起登录认证请求
- 后端登录验证通过,返回给前端一个凭证
- 前端发起新的请求时携带凭证
2. Session
有状态的管理机制
如果用户第一次访问某个服务器时,服务器响应数据时会在响应头的Set-Cookie标识里将Session Id返回给浏览器,浏览器就将标识中的数据存在Cookie中,浏览器后续访问服务器就会携带Cookie。
2.1 实现原理
2.2 过滤器Filter
问题引入:除了登录接口外,我们其他接口都要在Controller层里做登录判断,这太麻烦了。
解决:使用过滤器拦截,判断有没有登录,登录就放行,没登录结束请求
2.3 上下文对象
问题引入:要在service层获取操作用户对象,需要从Controller层传参过来太麻烦了。
解决:通过SpringMVC提供的RequestContextHolder对象在程序任何地方获取到当前请求对象,从而获取我们保存在HttpSession中的用户对象,写一个上下文对象来实现这一功能。
然后在Service层直接调用我们写的方法就可以获取到用户对象
3. JWT
Json web token。无状态管理机制
- 可以将一段数据加密成一段字符串,也可以从这字符串解密回数据
- 可以对这个字符串进行校验,比如有没有过期,有没有被篡改
3.2 实现步骤
1、写一个JWT的工具类,工具类就提供两个方法一个生成一个解析
2、工具类做好之后我们可以开始写登录接口,登录的时候生成token
3、后续会话中,用户访问其他接口时就可以校验token来判断其是否已经登录。
一般来说,前端将token一般会放在请求头的Authorization项传递过来,其格式一般为类型 + token
调用其他接口的时候,从请求头中获取token,然后再通过JWT工具类解析token判断。
3.3 拦截器 HandlerInterceptorAdapter
如果每个接口都要手动判断一下用户有没有登录太麻烦了,所以我们做一个统一处理。
拦截器类写好之后,别忘了要使其生效,这里我们直接让SpringBoot启动类实现WebMvcConfigurer接
口,重写addInterceptors方法
3.4 上下文对象
在一个线程中横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context)
JWT不像Session把用户信息直接存储起来,所以JWT的上下文对象要靠我们自己来实现。
这个类专门存储JWT解析出来的用户信息。我们要用到ThreadLocal存储用户信息,以防止线程冲突
通过上下文对象类(如UserContext),可以在程序的其他地方直接获取到数据。上下文对象.方法()
public final class UserContext {private static final ThreadLocal<String> user = new ThreadLocal<String>();public static void add(String userName) {user.set(userName);}public static void remove() {user.remove();}/*** @return 当前登录用户的用户名*/public static String getCurrentUserName() {return user.get();}
}
4. Session VS JWT
- Session是有状态的,JWT是无状态的
- Session在服务端保存了用户信息,而JWT在服务端没有保存任何信息。
- 当前端携带Session Id到服务端时,服务端要检查其对应的HttpSession中有没有保存用户信息,保存了就代表登录了
- 当使用JWT时,服务端只需要对这个字符串进行校验,校验通过就代表登录了
二、权限授权
对用户能否问某个资源进行确认。认证成功之后,再确认能访问什么。
权限系统的设计,第一步就是考虑要保护什么资源,再接着思考如何保护这个资源。
1. 权限类型
1.1 页面权限(菜单项权限)
有权限的用户就会显示所有菜单,无权限的用户就只会显示部分菜单
一个页面(菜单)对应一个URI地址,当用户登录的时候判断这个用户拥有哪些页面权限,自然
而然就知道要渲染出什么导航菜单。
1.2 ACL模型 (Access Control List访问控制列表)不推荐
数据库设计三张表格,一张表用来存储用户信息,一张表存储资源路径,中间表用来映射用户和资源表的关系。
具体实现
前端给后端发请求后,后端回返回数据给前端,这些数据中就包含了用户能够访问的资源信息,前端本地也存有一个映射字典,字典里有资源的信息,比如id对应哪个路径、名称等等,前端拿到了用户的id后根据字典进行判断就可以做到相应的功能。
1.3 RBAC模型(Role-Based Access Controller基于角色访问控制模型)
很多用户的权限都是相同的,通过封装一层角色信息,实现角色和权限绑定,用户和角色绑定。
1.2 接口权限(操作权限或按钮权限)
前后端分离的模式下,后端在登录的时候将权限数据甩给前端后就再也不管了,如果此时用户的权限发生变化是无法通知前端的,并且数据存储在前端也容易被用户直接篡改,所以很不安全。
例如:没有这个删除权限的人就不会显示该按钮,或者该按钮被禁用.
页面渲染不走后端,但接口可必须得走后端,只需要对每个接口进行一个权限判断。
实现方式
- 接口扫描
RequestMappingInfoHandlerMapping,这个类可以拿到所有你声明的web接口信息
通过代码将接口信息批量添加到数据库,标记一下哪些接口是否需要被权限管理(注解形式) - 接口扫描具体实现
使用SpringBoot提供的ApplicationRunner接口来进行处理,重写该接口的方法会在程序启动时被
执行。 - 拦截器
每一个权限安全判断都是写在方法内,且这个逻辑判断代码都是一样的,会有重复代码
获取请求的最佳匹配路径,例如:/API/user/test/{id}路径参数
拦截器中获取权限数据现在是直接查的数据库,实际开发中一定要将权限数据存在缓存里(如Redis)
1.3 数据权限
不同用户所能访问到的数据不同。
1、硬编码,通过编写sql语句(不推荐)
2、Mybatis拦截插件
提供了一个Interceptor接口,实现该接口定义一个拦截器对sql语句进行拦截达到数据过滤效果
三、SpringSecurity
1. 概述
Web系统中登录认证(Authentication)的核心就是凭证机制,无论是Session还是JWT,都是在用户成功登录时返回给用户一个凭证,后续用户访问接口需携带凭证来标明自己的身份。权限授权(Authorization)是对用户能否访问某个资源进行确认,这种通用逻辑都是放在过滤器里进行的统一操作。
Spring Security对Web系统的支持就是基于这一个个过滤器组成的过滤器链
Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件就掌握了Spring Security。
使用Spring Security肯定是要先引入依赖包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 登录认证
2.1 了解各个组件
了解各个组件实现简单的登入认证,基于Session机制。
确认当前是哪个用户正在使用我们系统就是登录认证的最终目的
这一概念在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。
通过 SecurityContext(上下文对象) 来获取Authentication
上下文对象则是交由 **SecurityContextHolder **进行管理,用来在程序任何地方获取SecurityContext
Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
认证信息
Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象
Credentials:用户凭证,一般是密码
Authorities:用户权限
认证流程
登录接口代码
@RestController
@RequestMapping("/API")
public class LoginController {@Autowiredprivate AuthenticationManager authenticationManager;@PostMapping("/login")public String login(@RequestBody LoginParam param) {// 生成一个包含账号密码的认证信息Authentication token = new UsernamePasswordAuthenticationToken(param.getUsername(), param.getPassword());// AuthenticationManager校验这个认证信息,返回一个已认证的AuthenticationAuthentication authentication = authenticationManager.authenticate(token);// 将返回的Authentication存到上下文中SecurityContextHolder.getContext().setAuthentication(authentication);return "登录成功";}
}
AuthenticationManager认证方式
Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证
默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中调用这个组件,该过滤器
负责认证逻辑。
加密器PasswordEncoder(重写)
实现此接口定义自己的加密规则和校验规则,或者采用Spring Security提供的加密器实现
public interface PasswordEncoder {//加密String encode(CharSequence rawPassword);//将未加密的字符串(前端传递过来的密码)和已加密的字符串(数据库中存储的密码)进行校验boolean matches(CharSequence rawPassword, String encodedPassword);
}
配置类里配置
@Bean
public PasswordEncoder passwordEncoder() {// bcrypt加密算法,安全性比较高return new BCryptPasswordEncoder();
}
往数据库中添加用户数据时就要将密码进行加密,否则后续进行密码校验时从数据库拿出来的还是明文密码。
实现用户注册接口
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/register")
public String register(@RequestBody UserParam param) {UserEntity user = new UserEntity();// 调用加密器将前端传递过来的密码进行加密user.setUsername(param.getUsername()).setPassword(passwordEncoder.encode(param.getPassword()));// 将用户实体对象添加到数据库userService.save(user);return "注册成功";
}
用户对象UserDetails (重写)
提供了用户的一些通用属性
public interface UserDetails extends Serializable {// 用户权限集合Collection<? extends GrantedAuthority> getAuthorities();// 用户密码String getPassword();// 用户名String getUsername();// 用户没过期返回true,反之则falseboolean isAccountNonExpired();//......
}
以上默认属性满足不了实际开发需求,可以继承Spring Security提供org.springframework.security.core.userdetails.User类,该类实现了
UserDetails接口帮我们省去了重写方法的工作
public class UserDetail extends User {//我们自己的用户实体对象,要调取用户信息时直接获取这个实体对象。private UserEntity userEntity;public UserDetail(UserEntity userEntity, Collection<? extends GrantedAuthority> authorities) {// 必须调用父类的构造方法,以初始化用户名、密码、权限super(userEntity.getUsername(), userEntity.getPassword(), authorities);this.userEntity = userEntity;}
}
业务对象UserDetailsService(重写)
public interface UserDetailsService {//根据用户名获取用户对象(获取不到直接抛异常)UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
可以自己定义逻辑
@Service
public class UserServiceImpl implements UserService, UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) {// 从数据库中查询出用户实体对象UserEntity user = userMapper.selectByUsername(username);// 若没查询到一定要抛出该异常,这样才能被Spring Security的错误处理器处理if (user == null) {throw new UsernameNotFoundException("没有找到该用户");}// 走到这代表查询到了实体对象,那就返回我们自定义的UserDetail对象(这里权限暂时放个空集合,后面我会讲解)return new UserDetail(user, Collections.emptyList());}
}
认证异常处理器AuthenticationEntryPoint接口
当我们查询用户失败时或者校验密码失败时都会抛出Spring Security的自定义异常。这些异常不可能放任不管,Spring Security对于这些异常都是在ExceptionTranslationFilter过滤器中进行处理,AuthenticationEntryPoint则专门处理认证异常。
也可以自定义一个类实现AuthenticationEntryPoint接口从而实现我们自己的错误处理逻辑
public class MyEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {response.setContentType("application/json;charset=utf-8");PrintWriter out = response.getWriter();// 直接提示前端认证错误out.write("认证错误");out.flush();out.close();}
}
配置
Spring Security对哪些接口进行保护、什么组件生效、某些功能是否启用等等都需要在配置类中进行配置
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {....@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}
** 实现流程**
- 用户进行登录操作,传递账号密码过来 登录接口调用AuthenticationManager
- 根据用户名查询出用户数据 UserDetailService查询出UserDetails
- 将传递过来的密码和数据库中的密码进行对比校验 PasswordEncoder
- 校验通过则将认证信息存入到上下文中 将UserDetails存入到Authentication,将Authentication存入到SecurityContext
- 如果认证失败则抛出异常 由AuthenticationEntryPoint处理
基于session认证:认证后Spring Security会将Authentication存入到session中(不推荐)
2.2 JWT+SpringSecurity
1、当用户登录成功的同时,我们需要生成token并返回给前端,这样前端才能访问其他接口时携带token。
@Autowired
private UserService userService;
@PostMapping("/login")
public UserVO login(@RequestBody @Validated LoginParam user) {// 调用业务层执行登录操作return userService.login(user);
}
service层实现login方法
public UserVO login(LoginParam param) {// 根据用户名查询出用户实体对象UserEntity user = userMapper.selectByUsername(param.getUsername());// 若没有查到用户 或者 密码校验失败则抛出自定义异常if (user == null || !passwordEncoder.matches(param.getPassword(), user.getPassword())) {throw new ApiException("账号密码错误");}// 需要返回给前端的VO对象UserVO userVO = new UserVO();userVO.setId(user.getId()).setUsername(user.getUsername())// 生成JWT,将用户名数据存入其中.setToken(jwtManager.generate(user.getUsername()));return userVO;
}
2、登录成功返回token,后续我们再访问其它接口时需要将token放到请求头中。这里我们需要自定义一个认证过滤器,来对token进行校验。每当一个请求来时我们都会校验JWT进行认证,上下文对象中有了Authentication后续过滤器就会知道该请求已经认证过了。
@Component
public class LoginFilter extends OncePerRequestFilter {@Autowiredprivate JwtManager jwtManager;@Autowiredprivate UserServiceImpl userService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {// 从请求头中获取token字符串并解析Claims claims = jwtManager.parse(request.getHeader("Authorization"));if (claims != null) {// 从`JWT`中提取出之前存储好的用户名String username = claims.getSubject();// 查询出用户对象UserDetails user = userService.loadUserByUsername(username);// 手动组装一个认证对象Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());// 将认证对象放到上下文中SecurityContextHolder.getContext().setAuthentication(authentication);}chain.doFilter(request, response);}
}
3、自定义的认证过滤器需要插入到默认的认证过滤器之前,这样我们的过滤器才能生效。
http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);
3. 权限授权
接口权限授权流程
- 当一个请求过来,我们先得知道这个请求的规则,即需要怎样的权限才能访问
- 然后获取当前登录用户所拥有的权限
- 再校验当前用户是否拥有该请求的权限
- 用户拥有这个权限则正常返回数据,没有权限则拒绝请求
Spring Security的授权发生在FilterSecurityInterceptor过滤器中,步骤如下:
- 首先调用的是 SecurityMetadataSource,来获取当前请求的鉴权规则
- 然后通过Authentication获取当前登录用户所有权限数据: GrantedAuthority认证对象里存放这权限数据
- 再调用 AccessDecisionManager 来校验当前用户是否拥有该权限
- 如果有就放行接口,没有则抛出异常,该异常会被 授权错误处理器AccessDeniedHandler 处理
- 定义好以上组件,就是写一个过滤器,进行属性配置,让组件生效
- 回到Spring Security配置类让这个过滤器替换掉原有的过滤器
总结
文章中内容均来源于https://zhuanlan.zhihu.com/p/342755411,原文作者讲解得十分透彻,不仅让我理解了技术框架的使用方法,还对框架中的源码,为什么要这样去做有了更深入的认识,这篇文章是对原文作者写的三篇文章的整合,简化了很多,方便本人之后复习巩固。
相关文章:

用户登录认证和权限授权(SpringSecurity、JWT、session)
文章目录 前言一、登录认证1. 问题引入2. Session2.1 实现原理2.2 过滤器Filter2.3 上下文对象 3. JWT3.2 实现步骤3.3 拦截器 HandlerInterceptorAdapter3.4 上下文对象 4. Session VS JWT 二、权限授权1. 权限类型1.1 页面权限(菜单项权限)1.2 ACL模型…...

第十二届蓝桥杯省赛真题 Java A 组【原卷】
文章目录 发现宝藏【考生须知】试题 A: 相乘试题 B: 直线试题 C : \mathrm{C}: C: 货物摆放试题 D: 路径试题 E: 回路计数试题 F : \mathrm{F}: F: 最少砝码试题 G: 左孩子右兄弟试题 H : \mathrm{H}: H: 异或数列试题 I \mathbf{I} I 双向排序试题 J : \mathrm{J}: J: 分…...
工作随机:linux 挂载LVM管理模式的磁盘
文章目录 前言一、创建一个分区二、创建PV三、创建VG四、创建LV五、格式化并挂载目录 前言 在数据库管理中,常有比较头疼的问题,就是一段时间发展后我的磁盘空间不够了,想要扩容原有的目录很是头疼,那么LVM管理的优势就体现出来了…...
打印kafka最近的消息
使用 kafka-run-class 指令,获取topic的最小offset和最大offset #查看各个分区的最小offset(这个意思就是,这个offset之前的消息已经被清除了,现在consumer是从这个offset之后开始消费): ./kafka-run-class.sh kafka.tools.GetOffsetShell …...

e行64位V11.17.4 安卓全局虚拟定位APP
e行最新版11.17.4 支持全局虚拟位置 小米手机 百度地图 高德地图 实测成功 其他app自测 不一定支持所有app 下载:https://www.123pan.com/s/HAf9-tsyCh.html...
vue项目通过点击文字上传html文件,查看html文件
上传html文件 解决思路:新建一个上传组件,将它挪到页面之外。当点击文字时,手动触发上传组件,打开上传文件框。 <template><BasicTable register"registerTable"><template #bodyCell"{ column, …...

【WEEK12】 【DAY1】整合JDBC【中文版】
2024.5.13 Monday 目录 11.整合JDBC11.1.SpringData简介11.2.新建springboot-04-data项目11.3.新建application.yaml11.4.连接数据库11.5.修改Springboot04DataApplicationTests.java11.5.1.查看DataSourceProperties.java和DataSourceAutoConfiguration.java 11.6.JDBCTempla…...
23种设计模式(软考中级 软件设计师)
设计模式 23个设计模式,23个意图 1. 设计模式概要 设计模式的核心在于提供了相关问题的解决方案,使得人们可以更加简单方便的复用成功的设计和体系结构 设计模式的类别 创建型结构型行为型类工厂方法模式适配器模式(类)解释器模…...

记录一下 log4j的漏洞
目录 背景 bug的产生 bug复现 JNDI 网络安全学习路线 (2024最新整理) 学习资料的推荐 1.视频教程 2.SRC技术文档&PDF书籍 3.大厂面试题 特别声明: 背景 log4j这次的bug,我相信大家都已经知道了,仅以…...
Springboot-配置文件中敏感信息的加密:三种加密保护方法比较
一. 背景 当我们将项目部署到服务器上时,一般会在jar包的同级目录下加上application.yml配置文件,这样可以在不重新换包的情况下修改配置。 一般会将数据库连接、Redis连接等放到配置文件中。 例如配置数据库连接: spring:servlet:multip…...

linux 性能监控命令之dstat
1. dstat 系统默认为安装,直接安装阿里源后,yum install -y dstat安装即可,该命令整合了 vmstat , iostat 和 ifstat,我们先看下效果: 我们先看看具体参数: [rootk8s-master ~]# dstat --help …...

花趣短视频源码淘宝客系统全开源版带直播带货带自营商城流量主小游戏功能介绍
1、首页仿抖音短视频 ,关注 ,我的 本地 直播 可发布短视频 可录制上传 2、商城页面 广告位、淘口令识别、微信登录、淘宝登录、淘宝返佣、拼多多返佣、京东返佣、唯品会返佣、热销榜、聚划算、天猫超市、9.9包邮、品牌特卖、新人攻略 、小米有品、优惠加…...
大模型管理工具:Ollama
目录 一、Ollama 介绍 二、Linux 安装 Ollama 2.1 一键安装 2.2 手动安装 三、使用Ollama 3.1 配置模型下载路径 3.2 运行模型 3.3 常用命令 四、模型管理 4.1 官方模型库 4.2 导入自定义模型 五、REST API 六、Web UI 一、Ollama 介绍 Ollama 是一个基于 Go 语言…...
recycleView的item,TV正确方式获取焦点,以及刷新界面
如果你正需要recycleView的焦点 GlobalScope.launch (Dispatchers.Main){ // layout.isVisible truesettingBinding.rootLayout.isVisible truesettingBinding.rvSettingTab.layoutManager?.scrollToPosition(itemPositionSelect)if(!GlobalVariable.isT…...
QT内存管理机制
1.父子关系管理:Qt 对象之间可以建立父子关系。当一个对象是另一个对象的子对象时,父对象负责管理子对象的生命周期。当父对象被销毁时,它所拥有的子对象也会被销毁,从而释放相关的内存。这种机制简化了内存管理,确保在…...

亚马逊卖家,如何打造爆款,如何提高产品权重、曝光、流量?
新老卖家们要知道,亚马逊A9算法影响产品排名的关键因素:产品相关性、销售排名、产品价格、点击率、转化率、产品图片、买家评论、买家满意度、QA的答复情况、搜索结果页详细信息级别。亚马逊A9算法,是根据卖家提供的listing文案信息进行收录、…...
处理HTTP请求的服务器
处理HTTP请求的服务器,通常被称为HTTP服务器或Web服务器。其主要功能包括接收、解析、处理和响应HTTP请求。 HTTP服务器处理HTTP请求的基本流程: 接收请求:服务器监听特定的网络端口,等待客户端(如Web浏览器…...

打造本地GPT专业领域知识库AnythingLLM+Ollama
如果你觉得openai的gpt没有隐私,或者需要离线使用gpt,还是打造专业领域知识,可以借用AnythingLLMOllama轻松实现本地GPT. AnythingLLMOllama 实现本地GPT步聚: 1 下载 AnythingLLM软件 AnythingLLM官网地址: Anythi…...

数据可视化训练第6天(美国人口调查获得关于收入与教育背景的数据,并且可视化)
数据来源 https://archive.ics.uci.edu/dataset/2/adult 过程 首先;关于教育背景的部分翻译有问题。 本次使用字典嵌套记录数据,并且通过lambda在sorted内部进行对某个字典的排序,最后用plotly进行绘图 本次提取数据的时候,用到…...

如何更换远程服务器的Python版本
目录 前言 正文 尾声 🔭 Hi,I’m Pleasure1234🌱 I’m currently learning Vue.js,SpringBoot,Computer Security and so on.👯 I’m studying in University of Nottingham Ningbo China📫 You can reach me by url below:My Blo…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
深入浅出JavaScript中的ArrayBuffer:二进制数据的“瑞士军刀”
深入浅出JavaScript中的ArrayBuffer:二进制数据的“瑞士军刀” 在JavaScript中,我们经常需要处理文本、数组、对象等数据类型。但当我们需要处理文件上传、图像处理、网络通信等场景时,单纯依赖字符串或数组就显得力不从心了。这时ÿ…...
Shell 解释器 bash 和 dash 区别
bash 和 dash 都是 Unix/Linux 系统中的 Shell 解释器,但它们在功能、语法和性能上有显著区别。以下是它们的详细对比: 1. 基本区别 特性bash (Bourne-Again SHell)dash (Debian Almquist SHell)来源G…...

轻量安全的密码管理工具Vaultwarden
一、Vaultwarden概述 Vaultwarden主要作用是提供一个自托管的密码管理器服务。它是Bitwarden密码管理器的第三方轻量版,由国外开发者在Bitwarden的基础上,采用Rust语言重写而成。 (一)Vaultwarden镜像的作用及特点 轻量级与高性…...

使用homeassistant 插件将tasmota 接入到米家
我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能,利用了巴法接入小爱的功能,将本地mqtt转发给巴法以实现小爱控制的功能,前提条件。1需要tasmota 设备, 2.在本地搭建了mqtt服务可, 3.搭建了ha 4.在h…...