springboot整合Sa-Token实现登录认证和权限校验(万字长文)

news2024/11/15 13:45:38

目前在国内的后端开发中,常用的安全框架有spring security、shiro。现在,介绍一款由国人开发的安全框架Sa-Token。这个框架完全由国人开发,所提供的Api文档和一些设置都是比较符合国人的开发习惯的,本次就来介绍一下如何在spring boot框架中整合Sa-Token框架,以实现我们最常使用的登录认证和权限校验;

Sa-Token的官网地址如下:
Sa-Token

Sa-Token 是由国人开发的 一个轻量级 Java 权限认证框架,主要解决:登录认证权限认证单点登录OAuth2.0分布式Session会话微服务网关鉴权 等一系列权限相关问题。

版本:spring boot3.15、Sa-Token1.37.0

1、新建一个spring boot项目,并导入一些起始的依赖:
 

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.32</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.18</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.3.0</version>
        </dependency>

        <!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot3-starter</artifactId>
            <version>1.37.0</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2、新建五张数据表,来展示登录认证和权限校验:


1、用户表

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL UNIQUE,
  password VARCHAR(100) NOT NULL,
  email VARCHAR(100) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2、角色表

CREATE TABLE roles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3、权限表

CREATE TABLE permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

4、用户角色表

CREATE TABLE user_roles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  role_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (role_id) REFERENCES roles(id)
);

5、角色权限表

CREATE TABLE role_permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  role_id INT NOT NULL,
  permission_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (role_id) REFERENCES roles(id),
  FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

3、现在我们开始进行基于Sa-Token进行的登录校验和权限认证:
 

与spring security不同的是,引入了Sa-Token的依赖之后。并不会自动生效,需要我们自己编写相应的逻辑代码;

写一个TestController,用来进行测试;

@RestController
@RequestMapping("/test")
public class TestController {


    @GetMapping("/hello")
    public String hello() {
        System.out.println("说hello啊");
        return "test接口的hello";
    }
    
}

我们现在启动项目,并访问/test/hello接口,发现能够正常访问。这一点与spring security是不同的,spring security默认会拦截所有请求;

Sa-Token已经为我们编写了很多我们经常使用的功能,我们只需要在yml配置文件中进行配置即可。以下是一些常用的配置:
 

############## 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: true
    # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
    token-style: uuid
    # 是否输出操作日志 
    is-log: true

3.1、登录认证:

使用Sa-Token进行登录认证是非常简单的,只需要一句代码就可以搞定:

// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);     

只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于:

  1. 检查此账号是否之前已有登录;
  2. 为账号生成 Token 凭证与 Session 会话;
  3. 记录 Token 活跃时间;
  4. 通知全局侦听器,xx 账号登录成功;
  5. 将 Token 注入到请求上下文;
  6. 等等其它工作……

我们暂时不需要完整了解整个登录过程,只需要记住关键一点:Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端

一个登录接口:

@Autowired
    IUsersService usersService;

    @PostMapping("/login")
public SaResult login(String username, String password){
//根据用户名从数据库中查询
        Users users = usersService.getOne(new LambdaQueryWrapper<Users>().eq(Users::getUsername, username));
        if(users == null){
            return SaResult.error("用户名不存在");
        }
        if(!users.getPassword().equals(password)){
    return SaResult.error("密码错误");
    }
//根据用户id登录
        StpUtil.login(users.getId());
        return SaResult.ok("登录成功");
    }

运行检验:

发现确实能够登录成功。

此处仅仅做了会话登录,但并没有主动向前端返回 token 信息。 是因为不需要吗?严格来讲是需要的,只不过 StpUtil.login(id) 方法利用了 Cookie 自动注入的特性,省略了你手写返回 token 的代码。

如果你对 Cookie 功能还不太了解,也不用担心,我们会在之后的 [ 前后端分离 ] 章节中详细的阐述 Cookie 功能,现在你只需要了解最基本的两点:

  • Cookie 可以从后端控制往浏览器中写入 token 值。
  • Cookie 会在前端每次发起请求时自动提交 token 值。

因此,在 Cookie 功能的加持下,我们可以仅靠 StpUtil.login(id) 一句代码就完成登录认证。

除了登录方法,我们还需要:

// 当前会话注销登录
StpUtil.logout();

// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();

// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();

异常 NotLoginException 代表当前会话暂未登录,可能的原因有很多: 前端没有提交 token、前端提交的 token 是无效的、前端提交的 token 已经过期 …… 等等

还有一些登录过程中常用的方法:

// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();

// 类似查询API还有:
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型

// ---------- 指定未登录情形下返回的默认值 ----------

// 获取当前会话账号id, 如果未登录,则返回 null 
StpUtil.getLoginIdDefaultNull();

// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);


// 获取当前会话的 token 值
StpUtil.getTokenValue();

// 获取当前`StpLogic`的 token 名称
StpUtil.getTokenName();

// 获取指定 token 对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);

// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();

// 获取当前会话的 token 信息参数
StpUtil.getTokenInfo();

3.1.1前后端分离模式下的登录(无cookie模式)

无 Cookie 模式:特指不支持 Cookie 功能的终端,通俗来讲就是我们常说的 —— 前后端分离模式

常规 Web 端鉴权方法,一般由 Cookie模式 完成,而 Cookie 有两个特性:

  1. 可由后端控制写入。
  2. 每次请求自动提交。

这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)

而在app、小程序等前后端分离场景中,一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊?

见招拆招,其实答案很简单:

  • 不能后端控制写入了,就前端自己写入。(后端将登录成功之后生成的Token 传递到前端
  • 每次请求不能自动提交了,那就手动提交。(前端将登陆成功后获取的Token封装到请求头中每次请求时都携带后端将其读取出来,并判断用户信息

修改我们之前写的登录接口,返回token给前端;

 @PostMapping("/login")
public SaResult login(String username, String password){
//根据用户名从数据库中查询
        Users users = usersService.getOne(new LambdaQueryWrapper<Users>().eq(Users::getUsername, username));
        if(users == null){
            return SaResult.error("用户名不存在");
        }
        if(!users.getPassword().equals(password)){
    return SaResult.error("密码错误");
    }
//根据用户id登录,第1步,先登录上
        StpUtil.login(users.getId());
        // 第2步,获取 Token  相关参数 
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        // 第3步,返回给前端 
        return SaResult.data(tokenInfo);
    }

在进行一次登录:

可以看到登录成功之后,返回了很多相关的数据,而我们的token也在其中,那么前端就可以将token取出来放到一个状态管理器(pinia中),并且在每次请求时都将token放在请求头中。

如果你对 Cookie 非常了解,那你就会明白,所谓 Cookie ,本质上就是一个特殊的header参数而已, 而既然它只是一个 header 参数,我们就能手动模拟实现它,从而完成鉴权操作。

3.1.2路由拦截鉴权:

假设我们有如下需求:

项目中所有接口均需要登录认证,只有 “登录接口” 本身对外开放

我们怎么实现呢?给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。

在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式,那么在Sa-Token怎么实现路由拦截鉴权呢?

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns("/users/login");
    }
    
}

以上代码,我们注册了一个基于 StpUtil.checkLogin() 的登录校验拦截器,并且排除了/user/login接口用来开放登录(除了/user/login以外的所有接口都需要登录才能访问)这就与我们spring security有了一点点相似了,因为spring security默认的就是所有的接口都被拦截到。

我们再启动项目,并访问我们最开始写的/test/hello接口;

可以看到这个接口被拦截了。那么我们带上之前登录成功,生成的token之后再去访问呢?

可以看到我们成功访问了这个接口;

new SaInterceptor(handle -> StpUtil.checkLogin()) 是最简单的写法,代表只进行登录校验功能。实际上Sa-Token还可以实现一些更详细的校验规则,在Sa-Token的官网上可以看到。

3.2权限认证:

我们已经了解了常用的登录认证方法,接下来,我们来看一下权限认证;

1、获取当前用户的权限码集合:

因为每个项目的需求不同,其权限设计也千变万化,因此 [ 获取当前账号权限码集合 ] 这一操作不可能内置到框架中, 所以 Sa-Token 将此操作以接口的方式暴露给你,以方便你根据自己的业务逻辑进行重写。

你需要做的就是新建一个类,实现 StpInterface接口

/**
 * 自定义权限加载接口实现类
 */
@Component    // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一个账号所拥有的权限码集合 
     */

//    角色权限表
    @Autowired
    IRolePermissionsService rolePermissionsService;
//    用户角色表
    @Autowired
    IUserRolesService userRolesService;
//权限表
    @Autowired
    IPermissionsService permissionsService;
    
//    角色表
    @Autowired
    IRolesService rolesService;
    
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
//        根据用户id从用户角色表中获取角色id
        List<UserRoles> roleIds = userRolesService.list(new LambdaQueryWrapper<UserRoles>()
                .eq(UserRoles::getUserId,Integer.parseInt(loginId.toString())));
        List<Integer> rolesList = roleIds.stream().map(UserRoles::getRoleId).toList();
if (!(rolesList.size() >0)){
//    没有任何权限
    return null;
}
        List<String> list = new ArrayList<>();
           
        rolesList.forEach(roleId ->{
            // 根据角色id从角色权限表中获取权限id   
            List<RolePermissions> rolePermissions = rolePermissionsService.list(new LambdaQueryWrapper<RolePermissions>().
                    eq(RolePermissions::getRoleId, roleId));
            // 根据权限id从权限表中获取权限名称
            rolePermissions.forEach(permissionsId->{
                Permissions permissions = permissionsService.getById(permissionsId.getPermissionId());
                list.add(permissions.getName());
            });
        });

        // 返回用户所有权限,
        return list;
    }

    /**
     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询角色
        //        根据用户id从用户角色表中获取角色id
        List<UserRoles> roleIds = userRolesService.list(new LambdaQueryWrapper<UserRoles>()
                .eq(UserRoles::getUserId,Integer.parseInt(loginId.toString())));
        List<String> list = new ArrayList<>();
        
        if (!(roleIds.size() >0)){
//            用户没有分配角色
            return null;
        }
        roleIds.forEach(roleId ->{
            Roles byId = rolesService.getById(roleId.getRoleId());
            list.add(byId.getName());
        });
//        返回用户所有角色标识集合
        return list;
    }

}

我所实现的是标准的RBAC(基于用户、角色、权限的访问控制模型)。所以,在得到用户id的情况下、先根据用户角色表查出角色id、在根据角色权限表查询权限id,在根据权限表查出具体权限名称。

上面使用了Mybatis-plus的条件构造器和stream流的形式进行查询。

参数解释:

  • loginId:账号id,即你在调用 StpUtil.login(id) 时写入的标识值。
  • loginType:账号体系标识,此处可以暂时忽略,在 [ 多账户认证 ] 章节下会对这个概念做详细的解释。

有同学会产生疑问:我实现了此接口,但是程序启动时好像并没有执行,是不是我写错了? 答:不执行是正常现象,程序启动时不会执行这个接口的方法,在每次调用鉴权代码时,才会执行到此。

权限校验:
然后就可以用以下 api 来鉴权了:

// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();

// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");        

// 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
StpUtil.checkPermission("user.add");        

// 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");        

// 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");    

扩展:NotPermissionException 对象可通过 getLoginType() 方法获取具体是哪个 StpLogic 抛出的异常

角色校验:
在 Sa-Token 中,角色和权限可以分开独立验证

// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();

// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");        

// 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("super-admin");        

// 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");        

// 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] 
StpUtil.checkRoleOr("super-admin", "shop-admin");        

扩展:NotRoleException 对象可通过 getLoginType() 方法获取具体是哪个 StpLogic 抛出的异常

权限通配符:
Sa-Token允许你根据通配符指定泛权限,例如当一个账号拥有art.*的权限时,art.addart.deleteart.update都将匹配通过

// 当拥有 art.* 权限时
StpUtil.hasPermission("art.add");        // true
StpUtil.hasPermission("art.update");     // true
StpUtil.hasPermission("goods.add");      // false

// 当拥有 *.delete 权限时
StpUtil.hasPermission("art.delete");      // true
StpUtil.hasPermission("user.delete");     // true
StpUtil.hasPermission("user.update");     // false

// 当拥有 *.js 权限时
StpUtil.hasPermission("index.js");        // true
StpUtil.hasPermission("index.css");       // false
StpUtil.hasPermission("index.html");      // false

上帝权限:当一个账号拥有 "*" 权限时,他可以验证通过任何权限码 (角色认证同理)

在我们的项目的TestController中进行测试:
 

  @GetMapping("/hello")
    public String hello() {

        System.out.println("验证权限,是否具有‘所有权限’===============>"+StpUtil.hasPermission("所有权限"));

        System.out.println("验证角色,是否具有‘管理员’角色===============>"+StpUtil.hasRole("管理员"));  
        System.out.println("说hello啊");
        return "test接口的hello";
    }

我们测试的最终结果:
 

可以看到确实输出了两个true。并且,只有在每次调用鉴权代码时,才会执行到我们自定义的权限方法中。

总结:

我本来是想写一篇介绍spring boot项目中整合Sa-Token来实现最常用的登录校验和权限认证的,但是写着写着就变成官网的复制机了。我在本篇文章中大量复制了官网上的内容,原本只是想复制一些官方介绍就行了。但是这也从侧面说明了Sa-Token官网制作的确实是比较好的,基本上不需要额外的学习,只要你有做过登录和权限方面的项目经验,再看一遍官网的介绍就能直接上手了。

我之前写过一个B2C模式的购物商台,分为用户端和管理端。管理端的登录和权限校验是用spring security写的。现在又了解了Sa-Token之后,真的还是认为Sa-Token的使用是比较简单的。(也可能是我的项目还是比较简单的,并没有多少权限要实现)并且这也是我们国人写的开源框架,在一些方法的封装、Api的设计上还是能直接理解的。在这里也算是为Sa-Token这个框架打个广告吧,希望大家还是能够多多支持国产,国内的开源之路也是充满了坎坷与艰辛。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1430071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

海康威视有插件、无插件播放;webrtc直播;西瓜视频播放器;mpegts.js直播;flvjs直播

Notes 视频播放的几种方式 一、Video mp4链接直接播放 二、海康威视3.3插件版直播、云台控制&#xff0c;资源下载地址 index.html引入hk文件中的js文件双击HCWebSDKPlugin.exe安装插件前端参照文件夹hkCamera中的示例代码 三、海康威视3.2无插件版直播&#xff0c;资源下…

论文阅读-在分布式数据库环境中对哈希算法进行负载均衡基准测试

论文名称&#xff1a;Benchmarking Hashing Algorithms for Load Balancing in a Distributed Database Environment 摘要 现代高负载应用使用多个数据库实例存储数据。这样的架构需要数据一致性&#xff0c;并且确保数据在节点之间均匀分布很重要。负载均衡被用来实现这些目…

【Vue3】源码探索之旅:compiler-core之parseChildren函数(二)

简言 parseChildren函数是在baseParse函数中作为createRoot函数的子节点参数传入的&#xff0c;今天来探索下parseChildren函数。 parseChildren在 compiler-core/src/parse.ts文件内。 parseChildren 这个函数就是用来解析模板字符串内容的 &#xff0c;里面有个while循环…

Jmeter学习系列之四:测试计划元素介绍

测试计划元素 JMeter包含各种相互关联但为不同目的而设计的元素。在开始使用JMeter之前&#xff0c;最好先了解一下JMeter的一些主要元素。 注意:测试计划包含至少一个线程组。 以下是JMeter的一些主要组件: 测试计划&#xff08;Plan&#xff09;线程组(Thread Group)控制器…

调试以及发布npm组件

开发原因&#xff1a; 由于公司自己的封装到npm的组件有点问题&#xff0c;负责人由在忙其他&#xff0c;就由我去负责改改&#xff0c;中途出了不少问题&#xff0c;记录一下。 一、下载源码 第一步肯定是去git上把组件的源码下载下来&#xff0c;这一步没什么好说&#xf…

19.HarmonyOS App(JAVA)依赖布局DependentLayout使用方法

layout/ability_main.xml 显示位置不对&#xff1a;检查布局文件ohos:lef_of "id:tuzi",比如显示在兔子的左侧&#xff0c;这里就会显示不对。 需要id前没有$符号。改为&#xff1a; ohos:lef_of "$id:tuzi" <?xml version"1.0" encodi…

方法引用练习:

技巧&#xff1a; 1.现在有没有一个方法符合我当前的需求 2.如果有这样的方法&#xff0c;这个方法是否满足引用的规则 练习一&#xff1a; 思路&#xff1a;//集合中的数据还是String类型&#xff0c;要放入Student类型数组中还需要先转型成Student类型 public class MyTest…

每日一题Leetcode 1686石子游戏Ⅵ

1686. 石子游戏 VI 题目描述&#xff1a; Alice 和 Bob 轮流玩一个游戏&#xff0c;Alice 先手。 一堆石子里总共有 n 个石子&#xff0c;轮到某个玩家时&#xff0c;他可以 移出 一个石子并得到这个石子的价值。Alice 和 Bob 对石子价值有 不一样的的评判标准 。双方都知道…

linux安装二进制openjdk1.8版本【配置永久加载环境变量】

二进制jdk下载地址&#xff1a;https://www.openlogic.com/openjdk-downloads 选择64位 jdk的 java8下载,然后解压 $ cd /home/ec2-user/config/ $ tar xf openlogic-openjdk-8u382-b05-linux-x64.tar.gz $ mv openlogic-openjdk-8u382-b05-linux-x64 openjdk-8编辑环境变…

机器学习 | 如何利用集成学习提高机器学习的性能?

目录 初识集成学习 Bagging与随机森林 Otto Group Product(实操) Boosting集成原理 初识集成学习 集成学习&#xff08;Ensemble Learning&#xff09;是一种通过组合多个基本模型来提高预测准确性和泛化能力的机器学习方法。它通过将多个模型的预测结果进行整合或投票来做…

vue3-逻辑复用

什么是组合式函数 _无状态逻辑的函数_&#xff1a;它在接收一些输入后立刻返回所期望的输出。 比如 时间格式化的函数。 有状态逻辑的函数: 有状态逻辑负责管理会随时间而变化的状态。 比如 跟踪当前鼠标在页面中的位置。 在 Vue 应用的概念中&#xff0c;“组合式函数”(…

智能汽车竞赛摄像头处理(3)——动态阈值二值化(大津法)

前言 &#xff08;1&#xff09;在上一节中&#xff0c;我们学习了对图像的固定二值化处理&#xff0c;可以将原始图像处理成二值化的黑白图像&#xff0c;这里面的本质就是将原来的二维数组进行了处理&#xff0c;处理后的二维数组里的元素都是0和255两个值。 &#xff08;2…

LeetCode 热题 100 | 链表(中上)

目录 1 141. 环形链表 1.1 哈希表 1.2 快慢指针 2 142. 环形链表 II 2.1 哈希表 2.2 快慢指针 3 21. 合并两个有序链表 4 2. 两数相加 菜鸟做题第三周&#xff0c;语言是 C 1 141. 环形链表 1.1 哈希表 解题思路&#xff1a;遍历链表&#xff0c;在哈希表中…

ROS方向第二次汇报(5)

文章目录 1.本方向内学习内容&#xff1a;1.1.自定义msg&#xff1a;1.1.1.定义msg文件&#xff1a;1.1.2.编辑配置文件&#xff1a; 1.2.自定义srv&#xff1a;1.2.1.定义srv文件&#xff1a;1.2.2.编辑配置文件&#xff1a; 1.3.服务通信案例实现&#xff1a;1.3.1.服务端实现…

HTML+CSS:导航栏组件

效果演示 实现了一个导航栏的动画效果&#xff0c;当用户点击导航栏中的某个选项时&#xff0c;对应的选项卡会向左平移&#xff0c;同时一个小圆圈会出现在选项卡的中心&#xff0c;表示当前选项卡的位置。这个效果可以让用户更加清晰地了解当前页面的位置和内容。 Code <…

正点原子--STM32定时器学习笔记(2)

书接上文&#xff0c;本篇是对基本定时器实验部分进行的总结~ 实验目标&#xff1a;通过TIM6基本定时器定时500ms&#xff0c;让LED0每隔500ms闪烁。 解决思路&#xff1a;使用定时器6&#xff0c;实现500ms产生一次定时器更新中断&#xff0c;在中断里执行“翻转LED0”。 定时…

【leetcode题解C++】98.验证二叉搜索树 and 701.二叉搜索树中的插入操作

98. 验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例…

kubernetes基本概念和操作

基本概念和操作 1.Namespace1.1概述1.2应用示例 2.Pod2.1概述2.2语法及应用示例 3.Label3.1概述3.2语法及应用示例 4.Deployment4.1概述4.2语法及应用示例 5.Service5.1概述5.2语法及应用示例5.2.1创建集群内部可访问的Service5.2.2创建集群外部可访问的Service5.2.3删除服务5.…

Zoho Mail企业邮箱商业扩展系列第1部分:入门指南与基础设置

今天让我们来认识一下王雪琳&#xff0c;她是一位独立经营的营销咨询机构的个体企业家。在开始自己的事业之前&#xff0c;她进行了广泛的市场调研&#xff0c;明确了自己的业务定位&#xff0c;并全力以赴地投入到了自己的企业中。 一、创业背景 王雪琳的营销业务主要集中在…

日志报错 git -c dif.mnemonicprefix=false -c core.guotepath=false 解决方法

前言: 在进行下面操作前,必须确保,你是否安装了Git。 查看Git 在命令行窗口中输入`git --version`: 如果这个命令成功显示了Git的版本信息,这表明Git已经被安装。 1. 使用Sourcetree SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端…