Spring Security实战(一)——基于内存和数据库模型的认证与授权

news2024/10/5 21:24:58

目录

简介

一、初识Spring Security(入门案例)

(1)新建project

(2)选择依赖

(3)编写一个 HelloController

(4)启动项目,访问localhost:8080

(5)自定义用户名和密码

 二、表单认证

1. 自定义表单登录页

2. 配置spring security

3. 重启项目

​编辑

三、认证与授权

1. 资源准备

(1)新建两个controller

(2)资源授权的配置

(3)重启服务访问

 2. 基于内存的多用户支持

(1)在内存中配置用户

 (2)访问测试

  3. 基于默认数据库模型的认证与授权

(1)创建数据库表

(2)引入jdbc依赖,配置数据库

(3)配置spring securiy授权

 4. 基于自定义数据库模型的认证与授权

(1)数据库准备

 (2)编写实体类User

(3)编写自定义的UserDetailsServiceImpl

(4)编写Mapper

(5)spring security 配置

(6)测试


简介

​ 一般Web应用的需要进行认证授权

​ 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

​ 授权:经过认证后判断当前用户是否有权限进行某个操作

​ 而认证和授权也是SpringSecurity作为安全框架的核心功能。

(1)前后端分离项目登录校验流程:

 (2)Spring Security 完整流程

Spring Security 的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器,如:

一、初识Spring Security(入门案例)

(1)新建project

(2)选择依赖

(3)编写一个 HelloController

@RestController
@RequestMapping("/")
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello spring security!";
    }
}

(4)启动项目,访问localhost:8080

即使没有任何配置,在引入Spring Security后,要访问对应的 URL 资源需要经过HTTP基本认证

启动后,控制台会打印一个初始密码,如图:

 访问localhost:8080,填写登录信息(这就是HTTP基本认证),填写用户名 user

(5)自定义用户名和密码

在配置文件 application.yml 中配置用户名和密码,重启项目之后使用该用户名和密码即可登录。

spring:
  security:
    user:
      name: zy
      password: abc

入门案例总结:

 

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

步骤:

(1)提交用户名和密码之后,会将用户名和密码传给UsernamePasswordAuthenticationFilter

        UsernamePasswordAuthenticationFilter是Spring Security中一个非常重要的过滤器,它负责处理基于表单的身份验证,即当用户提交包含用户名和密码的表单时,该过滤器会从该请求中提取用户名和密码并进行身份验证。 

(2)UsernamePasswordAuthenticationFilter调用authenticate() 方法进行认证

        执行该方法时,它会获取请求中的用户名和密码参数,然后调用 AuthenticationManager 对象的 authenticate() 方法来进行身份验证。如果身份验证成功,则创建一个 Authentication 对象并将其传递给 SecurityContextHolder 中。如果身份验证失败,则会抛出 AuthenticationException。

 (3)AuthenticationManager 会继续调用DaoAuthenticationProvider的authenticate() 进行验证

        DaoAuthenticationProvider是一个AuthenticationProvider的实现类,用于处理用户名和密码验证的过程。当AuthenticationManager调用authenticate()方法时,实际上是委托给了DaoAuthenticationProvider来处理认证请求。

(4)DaoAuthenticationProvider也会调用 loadUserByUsername() 方法查询用户

        在DaoAuthenticationProvider的authenticate()方法中,会先调用UserDetailsService的loadUserByUsername()方法获取用户信息,(这里根据实际情况可能去内存中查找,也可以去数据库查找)然后再将获取到的用户信息与用户输入的密码进行比较,最终确定用户是否通过认证。因此,在认证流程中,loadUserByUsername()方法是一个非常重要的环节。

(5)返回 UserDetail对象

        loadUserByUsername() 方法主要是根据给定的用户名查询用户信息,并返回一个 UserDetails 对象。该方法的具体实现可能会涉及到访问数据库或其他存储设备,以获取用户的详细信息。在 Spring Security 中,这个方法通常由 UserDetailsService 的实现类来完成。在实现类中,通常会根据用户名查询用户信息,并将其封装为一个 UserDetails 对象,以便后续的身份验证过程中使用。UserDetails 对象包含用户的身份信息、授权信息和其他详细信息,如密码和是否启用等。

(6)通过PasswordEncoder对象对比UserDetail的密码和提交的密码是否一致

        通过PasswordEncoder对象将用户输入的密码加密,然后与UserDetails中存储的加密后的密码进行比较,来验证用户的身份。如果两者一致,则认为用户身份验证通过。PasswordEncoder主要用于将密码进行加密,以提高安全性。

(7)如果正确就把UserDetails中的权限信息设置到Authentication对象中

(8)返回Authentication对象

(9)将认证成功的 Authentication 对象存储在 SecurityContextHolder

        SecurityContextHolder 会使用 ThreadLocal 来存储认证对象,以确保每个线程都有自己的 SecurityContext 实例。在接下来的请求中,其他过滤器可以使用 SecurityContextHolder.getContext().getAuthentication() 方法获取该认证对象。


 二、表单认证

1. 自定义表单登录页

login.html,放在static下

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>
    <style>
        /* 样式可以自行修改 */
        body {
            background-color: cadetblue;
        }

        .login-form {
            width: 350px;
            margin: 150px auto;
            background-color: #fff;
            padding: 20px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }

        h1 {
            font-size: 24px;
            text-align: center;
            margin-bottom: 30px;
        }

        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 2px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        button {
            background-color: darksalmon;
            color: white;
            padding: 12px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
        }

        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
<div class="login-form">
    <h1>Login Page</h1>
    <form th:action="@{/login}" method="post">
        <label for="username">Username</label>
        <input type="text" id="username" name="username" placeholder="Enter username">

        <label for="password">Password</label>
        <input type="password" id="password" name="password" placeholder="Enter password">

        <button type="submit">Login</button>
    </form>
</div>
</body>
</html>

2. 配置spring security

重写 configure方法,接收一个 HttpSecurity 对象,HttpSecurity提供了而很多配置相关的方法:

(1)authorizeRequests() 是 Spring Security 中的一个配置方法,用于定义哪些请求需要被授权才能被访问。该方法返回一个 ExpressionInterceptUrlRegistry 对象,用于配置针对 URL 的访问授权。

通过这个方法,我们可以使用各种方法来进行 URL 的授权配置,例如:

  • antMatchers() 方法用于匹配 URL,并设置需要的访问权限。
  • hasRole()hasAuthority() 方法用于指定需要的角色或权限。
  • permitAll() 方法用于指定不需要任何访问权限即可访问。

(2)formLogin()

formLogin() 是 Spring Security 中的一个配置方法,用于指定使用表单登录进行身份验证。在默认情况下,如果没有进行任何身份验证,Spring Security 会自动重定向到默认的登录页面。

通过这个方法,我们可以进行如下配置:

  • loginPage() 方法用于指定登录页面的 URL。
  • loginProcessingUrl() 方法用于指定处理登录请求的 URL。
  • usernameParameter()passwordParameter() 方法用于指定表单中用户名和密码的参数名称。
  • successHandler()failureHandler() 方法用于指定登录成功和失败后的处理逻辑。
  • permitAll() 方法用于指定登录页面的访问权限。

(3)csrf() 是 Spring Security 中的一个配置方法,用于配置跨站请求伪造(Cross-Site Request Forgery,CSRF)防护功能。CSRF 攻击是一种恶意攻击方式,攻击者通过某些方式获取到用户的授权信息,然后利用这些信息发送恶意请求,从而实现攻击目的。

在 Spring Security 中,CSRF 防护功能默认是开启的,如果想要关闭它,可以使用 csrf().disable() 方法进行禁用。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

3. 重启项目

访问 localhost:8080/hello,会跳转到:http://localhost:8080/login.html

三、认证与授权

1. 资源准备

(1)新建两个controller

一个是只有管理员才能访问的,一个是普通用户访问的。

@RestController
@RequestMapping("/admin/api")
public class AdminController {

    @GetMapping("/hello")
    public String helloAdmin() {
        return "hello Admin!";
    }
}
@RestController
@RequestMapping("/user/api")
public class UserController {

    @GetMapping("/hello")
    public String helloAdmin() {
        return "hello User!";
    }
}

(2)资源授权的配置

请求 /admin/api/** 下的资源,需要检查是否有 ADMIN 角色

请求 /user/api/** 下的资源,需要检查是否有 USER角色

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

(3)重启服务访问

可以看到,访问 http://localhost:8080/hello 可以成功,访问http://localhost:8080/admin/api/hello会被拒绝访问(403错误码)

 2. 基于内存的多用户支持

(1)在内存中配置用户

        下面配置中的两个 configure(HttpSecurity http) configure(AuthenticationManagerBuilder auth)方法是 WebSecurityConfigurerAdapter 中的两个关键方法,它们用于配置Spring Security 的身份验证和授权。

  configure(HttpSecurity http)方法用于配置请求的授权规则,即哪些请求需要什么权限才能访问。

  configure(AuthenticationManagerBuilder auth)方法用于配置身份验证的方式,即如何验证用户身份。在这个示例中,我们使用了内存身份验证,通过调用.inMemoryAuthentication()方法,然后使用.withUser()方法来指定用户名和密码,使用{noop}前缀来表示密码不进行加密,最后使用.roles()方法来指定用户的角色。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("{noop}abcd").roles("ADMIN")
                .and()
                .withUser("zy").password("{noop}abc").roles("USER");
    }
}

 (2)访问测试

使用用户名 admin 密码 abcd ,来登录,可以显示下面内容:

  3. 基于默认数据库模型的认证与授权

(1)创建数据库表

在数据库创建了两个用户,分别是:

user 对应的角色是 ROLE_USER;

admin 对应的角色是ROLE_ADMIN。

CREATE TABLE users (
   username VARCHAR(50) NOT NULL PRIMARY KEY,
   password VARCHAR(100) NOT NULL,
   enabled BOOLEAN NOT NULL
);

CREATE TABLE authorities (
   username VARCHAR(50) NOT NULL,
   authority VARCHAR(50) NOT NULL,
   FOREIGN KEY (username) REFERENCES users(username)
);


INSERT INTO users (username, password, enabled) VALUES ('user', '12345', true);
INSERT INTO users (username, password, enabled) VALUES ('admin', '12345', true);

INSERT INTO authorities (username, authority) VALUES ('user', 'ROLE_USER');
INSERT INTO authorities (username, authority) VALUES ('admin', 'ROLE_ADMIN');

(2)引入jdbc依赖,配置数据库

引入jdbc和MySQL依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

 配置数据库连接:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security-db?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

(3)配置spring securiy授权

        这里使用了 jdbcAuthentication() 方法来启用基于 JDBC 的用户存储,并通过 dataSource() 方法设置数据源,即连接到数据库的 DataSource。

        接着,使用了 usersByUsernameQuery() 方法设置查询用户名、密码和启用状态的 SQL 语句,该语句会在用户登录时被执行,根据输入的用户名查询数据库中的用户信息,并将查询到的密码和启用状态用于认证。authoritiesByUsernameQuery() 方法用于设置查询用户角色的 SQL 语句。 最后,使用了 passwordEncoder() 方法设置密码加密方式,这里使用了 NoOpPasswordEncoder.getInstance() 方法来禁用密码加密,即直接将从数据库中查询到的密码作为明文进行比对。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username = ?")
                .authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username = ?")
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }
}

 4. 基于自定义数据库模型的认证与授权

上面我们使用了InMemoryUserDetailsManager和JdbcUserDetailsManager两个UserDetailsService实现类。下面我们使用自定义的数据库模型,并且使用自定义的UserDetails实现类。

(1)数据库准备

CREATE TABLE my_users (
  id INT(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(100) NOT NULL,
  enabled TINYINT(1) NOT NULL DEFAULT '1',
  roles VARCHAR(200) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY username_UNIQUE (username)
);

插入两条数据:

 (2)编写实体类User

        这个实体类 User 与之前的表 my_users 是一一对应的,每个属性都对应着表中的一个字段:

  • id 对应着表中的 id 字段,用于唯一标识每个用户
  • username 对应着表中的 username 字段,表示用户的登录名
  • password 对应着表中的 password 字段,表示用户的密码
  • enabled 对应着表中的 enabled 字段,表示用户是否启用
  • roles 对应着表中的 roles 字段,表示用户所拥有的角色

        需要注意的是,这个 User 实体类中还有一个 authorities 属性,这个属性是用于保存用户的权限信息的,它不对应着表中的任何一个字段。这个属性在 UserDetailsServiceImpl 类的 loadUserByUsername 方法中会被设置为用户的权限信息,用于进行认证和授权。为了实现这个功能,User 类中还定义了一个 getAuthorities 方法,用于将 roles 字段解析成一个 List<GrantedAuthority> 类型的集合,并且在需要的时候进行懒加载。

@Data
public class User implements UserDetails {

    private Long id;
    private String username;
    private String password;
    private boolean enabled;
    private String roles;
    private List<GrantedAuthority> authorities;


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //用于将 roles 字段解析成 List<GrantedAuthority> 类型的集合
    public List<GrantedAuthority> getAuthorities() {
        if (authorities == null) {
            authorities = new ArrayList<>();
            for (String role : roles.split(",")) {
                authorities.add(new SimpleGrantedAuthority(role.trim()));
            }
        }
        return authorities;
    }
}

(3)编写自定义的UserDetailsServiceImpl

        这个 UserDetailsServiceImpl 类实现了 UserDetailsService 接口,是用于加载用户信息的服务类。在 loadUserByUsername 方法中,通过 UserMapper 从数据库中查询到对应的 User 对象,然后构建出该用户对应的 GrantedAuthority 列表,最终将该 User 对象返回。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中查询用户信息
        User user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 构建用户权限信息
        List<GrantedAuthority> authorities = user.getAuthorities();
        user.setAuthorities(authorities);
        return user;
    }
}

(4)编写Mapper

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM my_users WHERE username = #{username}")
    User findByUsername(@Param("username") String username);
}

(5)spring security 配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
//使用自定义的数据库模型进行认证和授权
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

}

(6)测试

打断点调试,可以看到在使用admin用户登录的时候可以获取到两个角色。

 

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

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

相关文章

手动清除gitlab中prometheus的数据

背景&#xff1a; gitlab服务器上&#xff0c; 磁盘经常爆满。后来通过 du -sh ./* 查出prometheus下的data占了绝大多数磁盘空间。 因此&#xff0c;准备删除prometheus的数据。 思路 由于prometheus的数据占用的空间较大&#xff0c;因此在实际使用时&#xff0c;可以关闭…

【 Spring MVC 核心功能(一) - 使用注解实现 URL 路由映射】

文章目录引言一、RequestMapping 注解介绍1.1 RequestMapping 是 post 还是 get 请求&#xff1f;1.2 RequestMapping 指定一种请求方式二、GetMapping三、PostMapping四、总结引言 前面我们讲到&#xff0c;学习 Spring MVC 需要掌握三个核心功能即连接&#xff0c;获取参数&…

avi文件怎么转成mp4视频,3种方法快速学

avi文件怎么转成mp4视频&#xff1f;前面小编给大家讲了mov格式和webm格式&#xff0c;以及如何将mov和webm格式转换成mp4格式。今天小编给大家说一说另外一种视频格式&#xff0c;就是avi&#xff0c;AVI英文全称为Audio Video Interleaved&#xff0c;即音频视频交错格式&…

SpringBoot中对Bean的处理方法以及第三方Bean处理的方式

我们可以通过Spring当中提供的注解Component以及它的三个衍生注解&#xff08;Controller、Service、Repository&#xff09;来声明IOC容器中的bean对象&#xff0c;为应用程序注入运行时所需要依赖的bean对象&#xff0c;也就是依赖注入DI。 关于IOC容器中Bean的其他使用细节…

矿泉水为什么会溴酸盐超标

矿泉水为什么会溴酸盐超标&#xff1f; 水生产企业多使用臭氧消毒&#xff0c;不过&#xff0c;水生产企业不存在水运输路途遥远的问题&#xff0c;因此可以使用臭氧消毒。同时&#xff0c;也是因为臭氧在消毒后会直接变成氧气&#xff0c;所以不会有使用氯消毒后的那种味道&a…

give_you_flag(GIF原理、二维码定位符)

下载附件是一个GIF 看到最后你会发现它突然漏了一个二维码出来 我们将这个GIF分解成图片&#xff08;动图就是由多张静态图片组合而成&#xff0c;按照一定的顺序和时间播放的&#xff09; https://tu.sioe.cn/gj/fenjie/ 提取出里面包含二维码的那张 但是我们发现这里有一个…

word2vec原理

1.背景 2013年&#xff0c;Google开源了一款用于词向量计算的工具—word2vec&#xff0c;引起了工业界和学术界的关注。首先&#xff0c;word2vec可以在百万数量级的词典和上亿的数据集上进行高效地训练&#xff1b;其次&#xff0c;该工具得到的训练结果——词向量(word embed…

GDPU C语言 天码行空7

1. 数组最大值交换 ⭐ 1e9&#xff1a;10的9次方 #include<stdio.h>int main() {int n, i,idx 0;//idx 记录第一个出现的最大值下标scanf("%d", &n);double a[30];double max -1e9;// 1e9 表示10的9次方&#xff0c;十亿&#xff0c;int 的最大范围级别…

使用RT-Thread Studio搭配STM32CubeMX新建RT-Thread项目

STM32CubeMX下载 RT-Thread Studio下载 安装好RT-Thread Studio后&#xff0c;先打开RT-Thread SDK管理器确认有没有自己MCU需要的SDK包&#xff0c;直接安装好之后里面是有STM32F1系列的SDK包&#xff0c;其他的需要自己安装。 之后点击文件→新建→RT-Thread项目&#xff0…

C++ 实现 matlab 的 zp2tf 函数

文章目录1. matlab 的 zp2tf 函数的作用2. matlab 的 zp2tf 函数的使用方法3. C实现3.1 complex.h 文件3.2 zp2tf.h 文件4. 测试结果4.1 测试文件4.2 测试结果1. matlab 的 zp2tf 函数的作用 作用是将极点形式的 H(s) 函数的分母展开 2. matlab 的 zp2tf 函数的使用方法 [z,…

ICASSP2023 | 基于多任务学习的保留背景音的语音转换

在影视、有声书内容中&#xff0c;背景音是一种表现丰富的艺术形式。语音转换&#xff08;Voice Conversion&#xff09;如能将源说话人语音转换成目标说话人语音的同时&#xff0c;保留源语音中的背景音&#xff0c;将会提供更沉浸的语音转换体验。之前的语音转换研究主要关注…

低代码平台很赞,用2个小时,搭出1套应用

最近低代码很火&#xff0c;到处都是低代码的尝试贴&#xff0c;笔者今天也决定深入体验一下&#xff0c;感受它的便捷程度。 在案例开始之前&#xff0c;我们先来聊聊概念。 一、低代码 低代码实质上并不是一个新颖的话题&#xff0c;也不是最近才有的技术突破和创新&#xf…

Python--异常处理机制

Python异常处理机制 1、什么是异常处理&#xff1a; 程序运行时常会碰到一些错误&#xff0c;例如除数为 0、年龄为负数、数组下标越界等&#xff0c;这些错误如果不能发现并加以处理&#xff0c;很可能会导致程序崩溃。 可以简单的理解异常处理机制&#xff0c;就是在程序运…

Nginx生产环境配置、elasticsearch生产环境配置、rocketmq生产环境配置 (史上最全)

Nginx实现10万并发 在优化内核时&#xff0c;可以做的事情很多&#xff0c;不过&#xff0c;我们通常会根据业务特点来进行调整&#xff0c;当Nginx作为静态web内容服务器、反向代理或者提供压缩服务器的服务器时&#xff0c;期内核参数的调整都是不同的&#xff0c; 概述&am…

计算机组成原理——第五章中央处理器

半生风雨半生伤&#xff0c;半醉半醒半心凉 文章目录前言5.1 CPU的功能和基本结构5.2 指令周期的数据流5.3.1 单总线结构5.3.2 专用通路结构前言 之前我们就说过CPU主要包括两个部分&#xff0c;运算器和控制器&#xff0c;运算器主要是实现算数运算.逻辑运算&#xff0c; 运算…

React 搜索时遇到的坑

一、业务场景&#xff1a; 最近在优化React的天枢项目里面&#xff0c;搜索时遇到了一些问题。为了大家后面遇到和我一样的问题&#xff0c;给大家分享一下 二、问题描述&#xff1a; 1.点击搜索按钮&#xff0c;报以下错误。 TypeError: Converting circular structure to J…

全景丨0基础学习VR全景制作,平台篇:如何从素材库发布VR漫游

大家好欢迎观看蛙色平台使用教程 大家可以将创建作品理解成搭建房子&#xff0c;建房子需要基础的砖块、木头、钉子等原材料&#xff0c;房子成品是多种原材料的有机组合&#xff0c;而蛙色VR平台在创建作品前&#xff0c;也需要先准备对应的基础原材料。当前蛙色VR素材上传类型…

C++实现前缀树

文章目录1. 什么是前缀树2. 前缀树的实现2.1 前缀树的基本结构2.2 插入2.3 word出现了几次2.3 word作为前缀出现几次2.4 删除1. 什么是前缀树 假设这里有一个字符串数组&#xff0c;和一个树的根结点&#xff1a; 这个结点的pass意思是&#xff1a;有几个字符通过了这个结点。…

Golang中是否可以无限开辟协程以及如何控制协程的数量?

文章目录1. Golang中是否可以无限开辟协程&#xff1f;2. 不控制goroutine数量引发的问题3. 如何控制goroutine的数量&#xff1f;⭐️3.1 只用有buffer的channel3.2 channel与sync同步组合方式3.3 利用无缓冲channel与任务发送/执行分离方式1. Golang中是否可以无限开辟协程&a…

腾讯轻联中多维表记录id是什么?如何获取记录id?

在腾讯文档智能表、金山轻维表、维格表需要去【更新表格数据】的时候&#xff0c;经常会需要输入记录id&#xff08;英文record id&#xff09;&#xff0c;很多用户也会有疑问&#xff0c;什么是记录id&#xff0c;如何获取记录id等。 金山、维格表、腾讯文档的记录ID是什么&a…