1.SpringSecurity -快速入门、加密、基础授权

SpringSecurity简介

文章目录

  • SpringSecurity简介
  • 一、基本概念
    • 1.1 认证(Authentication)方式
    • 1.2 会话(Session)介绍
    • 1.3 授权(Authorization)介绍
    • 1.4 RBAC
  • 二、SpringSecurity入门
    • 2.1 快速入门
      • 2.1.1 Maven坐标
      • 2.1.2 接口
      • 2.1.3 源码
    • 2.2 配置文件配置用户名和密码
    • 2.3 基于内存的多用户管理
  • 三、加密
    • 3.1 密码加密学习
    • 3.2 BCryptPasswordEncoder
  • 四、权限信息

一、基本概念

1.1 认证(Authentication)方式

系统为什么要认证

认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源

什么是认证(登录)

用户认证就是判断一个用户的身份是否合法的过程

常见的用户身份认证方式

  • 用户名密码登录
  • 二维码登录
  • 手机短信登录
  • 指纹认证
  • 人脸识别

1.2 会话(Session)介绍

下面这个文章中有对session的理解:基于Session实现短信登录_c# 登录session怎么使用_我爱布朗熊的博客-CSDN博客

过滤器与拦截器 - 登录校验与登录认证(JWT令牌技术)_jwt过滤器_我爱布朗熊的博客-CSDN博客

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。

会话就是系统为了保持当前用户的登录状态所提供的机制

常见的有基于session方式、基于token方式

  • 基于session的认证方式

    它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

有一些用户在使用浏览器的时候会禁用Cookie,这会导致发一次请求,用户登录一次

  • 基于token的认证方式

    它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。可以使用Redis 存储用户信息(分布式中共享session)。

    基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。

1.3 授权(Authorization)介绍

为什么要授权(控制资源被访问)?

因为不同的用户可以访问的资源不一样

什么是授权(给用户颁发权限)?

授权是用户认证通过后,根据用户的权限来控制用户访问资源的过程

拥有资源的访问权限则正常访问,没有权限则拒绝访问

1.4 RBAC

RBAC(Role-Based Access Control) 基于角色的访问控制

image-20230925222010168

用户,角色,权限 本质:就是把权限打包给角色(角色拥有一组权限),又将角色分配给用户(用户拥有多个角色)。

最少包括五张表 (用户表、角色表、用户角色表、权限表、角色权限表)

二、SpringSecurity入门

声明式(注解)的安全访问控制解决方案的安全框架

提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作

2.1 快速入门

2.1.1 Maven坐标

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.4.2</version>
</dependency>

2.1.2 接口

@RestController
@RequestMapping("/hello")
public class HelloController {@GetMapping("/hello")private String hello(){return "hello";}}

访问上面的接口就会出现下面的页面(登录)

image-20231007105125017

默认的用户名是User,密码是在控制台上的UUID

image-20231007105319945

登录上后便可以访问接口。

之后不会使用这种方式

我们也可以退出,访问IP:端口号/logout

image-20231007105829342

2.1.3 源码

为什么默认的用户名是user并且密码是一串UUID

如下代码所示

image-20231007111234007

2.2 配置文件配置用户名和密码

在配置文件中配置如下信息

spring:security:user:name: adminpassword: 123456

此时我们再使用默认的用户就不行了,而且控制台也不会输出UUID形式的密码了

image-20231007110802830


Spring Security配置文件中默认配置用户是单一的用户,大部分系统都有多个用户,多个用户如何配置

可以使用基于内存的多用户管理

2.3 基于内存的多用户管理

2.2 中配置的用户名和密码只能配置一个,但是我们的系统有许多的用户名和密码。

/*** 自定义类实现用户详情服务接口**   系统中默认是有这个UserDetailsService的,也就是默认的用户名(user)和默认密码(控制台生成的)*   如果在yaml文件中配置了用户名和密码,那在系统中的就是yaml文件中的信息** 我们自定义了之后,就会把系统中的UserDetailsService覆盖掉*/
@Configuration
public class MySecurityUserConfig  {/*** 根据用户名把用户的详情从数据库中获取出来,封装成用户细节信息UserDetailsService(包括用户名、密码、用户所拥有的权限)** UserDetails存储的是用户的用户名、密码、去权限信息*/@Beanpublic UserDetailsService userDetailsService() {
//      用户细节信息,创建两个用户
//      此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 =User.builder().username("zhangjingqi1").password("123456").roles("student").build();UserDetails user2 = User.builder().username("zhangjingqi2").password("123456789").roles("teacher").build();//      InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);return  userDetailsManager;}
}   

上面的程序启动会报错,原因

SpringSecurity强制要使用密码加密,当然我们也可以不加密,但是官方要求是不管你是否加密,都必须配置一个密码编码(加密)器

/*** 自定义用户必须配置密码加密器。* NoOpPasswordEncoder.getInstance() 此实例表示不加密*/
@Bean
public PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();
}

此时用户名和密码就是user1与user2中配置的用户名和密码(此时配置文件中的用户名和密码会失效)

image-20231014192803769

image-20231014192744528



UserDetails类如下所示:

image-20231014190836999

三、加密

3.1 密码加密学习

  • 密码为什么要加密

CSDN网站六百万用户信息外泄-月光博客 (williamlong.info)

在加解密时一定要注意,我们要使用明文能加密成密文但是密文解密不成明文的加密算法

如果密文能解密,那我们加密的效果其实不算太好,黑客照样还是可以破译

image-20231014211903859

  • 加密的方式有哪些涉及到密码加密问题

    密码加密一般使用散列函数,又称散列算法,哈希函数,这些函数都是单向函数(从明文到密码,反之不行)

    常见的散列算法有MD5和SHA

    SpringSecurity提供了多种密码加密方案,基本上都实现了PasswordEncode接口官方推荐使用BCryptPasswordEncode

    image-20231014213450153

image-20231014213514723

  • NoOpPasswordEncoder类已经过期了,而且还没有加密,怎么解决

  • 以学生身份登录,发现不但可以访问学生的页面,还可以访问教师的页面和管理员的页面,如何解决

    权限问题,后面解决

  • 如果要动态的创建用户,或者修改密码等(不是把用户名和密码写死到代码中),怎么办

热疹信息存储到数据库中

3.2 BCryptPasswordEncoder

        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String encode1 = passwordEncoder.encode("123456");String encode2 = passwordEncoder.encode("123456");String encode3 = passwordEncoder.encode("123456");System.out.println(encode1);//$2a$10$hkaTFSnsEBYcZxXMrDpMQu.IiPM5ZAIQ63Vvkq01.oxxv0yVRmKlySystem.out.println(encode2);//$2a$10$nuPqwjhW0e/RZ.h3L1gZx.KanwUNQd4GEB2YoeB/LeVOhavcoBS7OSystem.out.println(encode3);//$2a$10$SPHV9tuI6JhgOwDO2hMR4eq43E5BGmYJDQJ5GltIuK7WSQqi1sHzm//      参数1:原文   参数2:密文boolean result1 = passwordEncoder.matches("123456", encode1);boolean result2 = passwordEncoder.matches("123456", encode2);System.out.println(result1);//trueSystem.out.println(result2);//true

补充断言的知识

如果result1的结果是true的话,运行时的标记就是”√“,反之则是“×”

assertTrue(result1);//期望result1为true
assertFalse(result2);//期望result2为true

以下就是错误的情况

image-20231014220215113

我们可以将PasswordEncoder放入到Bean工厂中

/*** 配置密码加密器* NoOpPasswordEncoder.getInstance() 此实例表示不加密* BCryptPasswordEncoder() 会加密*/
@Bean
public PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();
}

修改我们之前的程序

    @Beanpublic UserDetailsService userDetailsService() {
//      用户细节信息,创建两个用户
//      此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456")).roles("student").build();UserDetails user2 = User.builder().username("zhangjingqi-2").password(passwordEncoder().encode("123456")).roles("teacher").build();//      InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);return  userDetailsManager;}

四、权限信息

4.1 获取登录用户信息

关于已经验证实体的详情被存储到安全性上下文中(SpringContext)

image-20231014222351220

如果用户名和密码正确,说明认证成功

此时会把认证信息放到Authentication中,再把Authentication放到SecurityContext中

然后可以通过安全上下文持有器SecurityContextHolder获取安全上下文

/*** 获取用户登录信息的方式*/
@RestController
@Slf4j
public class CurrentLoginUserController {/*** import org.springframework.security.core.Authentication;* 一旦登录成功,访问下面的请求就可以得到authentication** Authentication 继承 Principal**/@GetMapping("/getLoginUser1")public Authentication getLoginUser1(Authentication authentication) {return authentication;}/***import java.security.Principal;* 一旦登录成功,访问下面的请求就可以得到principal*/@GetMapping("/getLoginUser2")public Principal getLoginUser2(Principal principal) {return principal;}/*** 一旦我们登陆成功,框架就会把我们的信息放到安全性上文中SpringContext* 所以我们可以通过安全性上文SpringContext获取用户信息*/@GetMapping("/getLoginUser3")public Principal getLoginUser3() {
//      通过安全上下文持有器获取安全上下文return SecurityContextHolder.getContext().getAuthentication();}}

访问:localhost:8080/getLoginUser1

“name”用户名

“credentials”凭据,其实就是密码,这里是null的原因就是直接给展示密码太不安全了,所以显示为null

“authorities”权限信息,授权,权限

“principal”中的内容就是代码UserDetails中定义的内容

image-20231014224745740

"accountNonExpired"账户是否未过期,true表示未过期

"accountNonLocked"账户是否未锁定,true表示未锁定

"credentialsNonExpired"凭证是否未过期,true表示未过期,这里的凭证一般是指密码
**“enabled”**账户是否可用,true表示可用

只有当"accountNonExpired"、“accountNonLocked”、“credentialsNonExpired”、"enabled"都为true时,账户才能使用

image-20231014223719223

4.2 配置用户权限

如下图所示,便是给用户配置权限

image-20231015151149649

其中roles可以配置多个角色,其有一个可变数组,这样来说一个用户可以配置多个角色

UserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456")).roles("student","manager").build();

image-20231015151234272

下面来配置一下用户的权限

    @Beanpublic UserDetailsService userDetailsService() {
//      用户细节信息,创建两个用户
//      此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456"))
//               配置用户角色.roles("student", "manager") //角色到系统中会变成权限的,比如这里会变成ROLE_student,ROLE_manager
//               配置用户权限. student:delete权限处理学生的删除,student:add权限处理学生的添加.authorities("student:delete","student:add").build();UserDetails user2 = User.builder().username("zhangjingqi-2").password(passwordEncoder().encode("123456"))
//              配置权限.authorities("teacher:delete", "teacher:add")
//              配置角色.roles("teacher").build();//      InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);return userDetailsManager;}

登录zhangjingqi-1账号,访问localhost:8080/getLoginUser3

我们发现,JSON串中“authorities”的内容是中配置的”authorities“,而没有将“roles”变成权限

image-20231015152408310

之后再登录zhangjingqi-2账号访问localhost:8080/getLoginUser3,我们发现下面的JSON串中”authorities“是在代码中配置的“roles”角色信息转换成的权限

image-20231015152855592

所以配置用户权限我们得出一个结论:

  • 在代码中配置“authorities“与“roles”时,谁再后面谁执行,后者会覆盖前者;

  • 配置的”roles“角色信息,在前面添加一个“ROLE_”前缀就会变成权限信息

  • 系统不会给配置的”authorities“权限信息添加任何前缀

4.3 针对URL授权

此控制级别只在Controller层

虽然我们实现了认证功能,但是受保护的资源是默认的,默认所有认证(登录)用户均可以访问所有资源,不能根据实际情况进行角色管理

实现授权功能,需重新WebSecurityConfigurerAdapter中的一个configure方法

虽然之前我们配置了用户角色和用户权限,但是用户登录上之后依然可以随便访问,比如说“student”角色可以访问“teacher”角色对应的功能。

4.3.1 拒绝任何请求

如下代码所示:表示拒绝任何http请求

虽然可以使用zhangjingqi-1或zhangjingqi-2认证成功登录,但是Controller不让访问,并且包括springSecurity框架中的login也会被屏蔽掉

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest()//任何请求.denyAll();//拒绝}

访问http://localhost:8080/login如下所示

image-20231015155702659

4.3.2 允许登录表单

为了可以访问http://localhost:8080/login,我们可以使用下面的一个配置

    /*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest()//任何请求.denyAll();//拒绝http.formLogin().permitAll();//允许表单登录}

此时就可以看到下面的登录界面了,但是如果访问其他请求,就会被拒绝

image-20231015160916199

4.3.3 允许任何请求

    /*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest()//任何请求.permitAll();//允许任何请求http.formLogin().permitAll();//允许表单登录}

4.3.4 匹配请求

zhangjingqi-1对应的权限:student:delete、student:add

zhangjingqi-2对应的权限:ROLE_teacher

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.mvcMatchers("/student/**")// 匹配/student开头的请求.hasAnyAuthority("student:add")// 拥有student:add权限的用户可以访问上面的url.mvcMatchers("/teacher/**") // 匹配/teacher/**开头的请求.hasAnyAuthority("ROLE_teacher")//拥有ROLE_teacher权限的用户可以访问/teacher/**开头的url.anyRequest()// 任何请求.authenticated()//需要验证。注意:没有配置的url,只要登录成功就可以访问;http.formLogin().permitAll();//允许表单登录}
}

使用zhangjingqi-1访问一下localhost:8080/student/hello

image-20231015162519777

使用zhangjingqi-1访问一下localhost:8080/hello/hello

image-20231015162537580

使用zhangjingqi-1访问一下localhost:8080/teacher/hello

image-20231015162553881

使用zhangjingqi-2访问一下http://localhost:8080/student/hello

image-20231015162750496

使用zhangjingqi-2访问一下localhost:8080/hello/hello

image-20231015162537580

使用zhangjingqi-2访问一下localhost:8080/teacher/hello

image-20231015162818021

4.3.5 总结

  • 匹配请求的三种方式
.mvcMatchers("/student/**")
.regexMatchers("/student/**")
.anyMatchers("/student/**")
  • 判断权限的五种方式

推荐第一种

.hasAuthority("ROLE_teacher") //是否有单个权限,参数只能写一个权限
.hasAnyAuthority("ROLE_teacher","student:add")//是否有其中的任意一个权限
.access( "hasAuthority('student:query') or  hasAuthority('student:add')")
.hasRole("student") // 是否有单个角色
.hasAnyRole("student") //是否有其中的某个角色
  • 先写匹配路径,再写对应的权限

4.4 针对方法进行授权

下面 通过更灵活的配置方法安全,我们先通过@EnableGlobalMethodSecurity开启基于注解的安全配置

4.4.1 抽象类

public interface TeacherService {String add();String update();String delete();String query();
}

4.4.2 实现类

@Slf4j
@Service
public class TeacherServiceImpl implements TeacherService {//预授权注解,此处采用表达式的形式。//如果有teacher:add权限,才会执行下面这个方法;反之不会访问@PreAuthorize("hasAuthority('teacher:add')")@Overridepublic String add() {log.info("添加教师成功");return "添加教师成功";}//只要有teacher:update或teacher:add权限其中之一,便可以执行下面的方法@PreAuthorize("hasAnyAuthority('teacher:update','teacher:add')")@Overridepublic String update() {log.info("修改教师成功");return "修改教师成功";}@PreAuthorize("hasAuthority('teacher:delete')")@Overridepublic String delete() {log.info("删除教师成功");return "删除教师成功";}@PreAuthorize("hasAuthority('teacher:query')")@Overridepublic String query() {log.info("查询教师成功");return "查询教师成功";}
}

4.4.3 teacher的Controller

@RestController
@RequestMapping("/teacher")
public class TeacherController {@Resourceprivate TeacherService teacherService;@GetMapping("/query")public String queryInfo() {return teacherService.query();}@GetMapping("/add")public String addInfo() {return teacherService.add();}@GetMapping("/update")public String updateInfo() {return teacherService.update();}@GetMapping("/delete")public String deleteInfo() {return teacherService.delete();}
}

4.4.4 用户详情服务接口配置类

@Configuration
public class MySecurityUserConfig {/*** 根据用户名把用户的详情从数据库中获取出来,封装成用户细节信息UserDetails(包括用户名、密码、用户所拥有的权限)* <p>* UserDetails存储的是用户的用户名、密码、去权限信息*/@Beanpublic UserDetailsService userDetailsService() {
//      用户细节信息,创建两个用户
//      此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456"))
//               配置用户角色.roles("student") //角色到系统中会变成权限的,比如这里会变成ROLE_student,ROLE_manager.build();UserDetails user2 = User.builder().username("zhangjingqi-2").password(passwordEncoder().encode("123456"))
//              配置权限.authorities("teacher:query").build();UserDetails user3 = User.builder().username("admin").password(passwordEncoder().encode("123456"))
//              配置权限.authorities("teacher:query","teacher:add","teacher:update","teacher:delete").build();//      InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);userDetailsManager.createUser(user3);return userDetailsManager;}/*** 配置密码加密器* NoOpPasswordEncoder.getInstance() 此实例表示不加密* BCryptPasswordEncoder() 会加密*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}

4.4.5 安全配置

WebSecurityConfig这个地方其实是URL的权限配置

预授权:在访问方法之前判断有无权限(使用量最大)

后授权:访问完方法后再判断有无权限

//@Configuration
//开启全局方法安全。 prePostEnabled = true 表示预授权和后授权开启
//此注解中包含@Configuration注解,所以不用重复标识
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest() //任何请求.authenticated();//都需要认证http.formLogin().permitAll();//允许表单登录//      或者下面这种形式
//        http.authorizeRequests()//授权http请求
//                .anyRequest() //任何请求
//                .authenticated()//都需要认证
//                .and()
//                .formLogin().permitAll();}
}

4.4.6 测试

用户admin,访问localhost:8080/teacher/query

image-20231016213447361

用户admin,访问localhost:8080/teacher/add

image-20231016213506529

用户zhangjingqi-2,访问localhost:8080/teacher/query

image-20231016213447361

用户zhangjingqi-2,访问localhost:8080/teacher/add

image-20231016213634402

用户zhangjingqi-1,访问localhost:8080/teacher/query

image-20231016213716614

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Bugku sql注入 基于布尔的SQL盲注 经典题where information过滤

目录 绕过空格 /**/绕过 ()绕过 回车绕过 &#xff08;键按钮&#xff09;绕过 等号绕过 绕过&#xff0c;&#xff08;逗号&#xff09;使用substr 下面存在基本绕过方式 注释符绕过 /**/绕过 #绕过 /*注释内容*/绕过 //注释绕过 大小写绕过 绕过information过…

14-k8s-基本存储之EmptyDir、HostPath、NFS

文章目录 一、相关概念二、EmptyDir存储三、HostPath存储四、NFS存储 一、相关概念 概述 Volumn定义在Pod上&#xff0c;然后被该Pod里面的多个容器挂载到具体的文件目录下。实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命周期不和Pod中的单个容器的…

MySQL逻辑架构

文章目录 逻辑架构剖析1. 连接层2. 服务层3. 引擎层4. 存储层 SQL执行流程1. MySQL中的 SQL执行流程&#xff08;理论&#xff09;2. MySQL8中的 SQL 执行流程&#xff08;实践&#xff09;确认profiling 是否开启多次执行相同SQL查询查看profiles查看profile 3. SQL语法顺序 数…

MFC-对话框

目录 1、模态和非模态对话框&#xff1a; &#xff08;1&#xff09;、对话框的创建 &#xff08;2&#xff09;、更改默认的对话框名称 &#xff08;3&#xff09;、创建模态对话框 1&#xff09;、创建按钮跳转的界面 2&#xff09;、在跳转的窗口添加类 3&#xff0…

Ceph 中的写入放大

新钛云服已累计为您分享769篇技术干货 介绍 Ceph 是一个开源的分布式存储系统&#xff0c;设计初衷是提供较好的性能、可靠性和可扩展性。 Ceph 独一无二地在一个统一的系统中同时提供了对象、块、和文件存储功能。 Ceph 消除了对系统单一中心节点的依赖&#xff0c;实现了无中…

Android自定义AppGlideModule,DataFetcher ,ModelLoaderFactory,ModelLoader,Kotlin(1)

Android自定义AppGlideModule,DataFetcher ,ModelLoaderFactory,ModelLoader,Kotlin(1) 假设实现一个简单的功能&#xff0c;对传入要加载的path路径增加一定的筛选、容错或“重定向”&#xff0c;需要自定义一个模型&#xff0c;基于这个模型&#xff0c;让Glide自动匹配模型…

C++前缀和算法:构造乘积矩阵

基础知识点 C算法&#xff1a;前缀和基础 题目 给你一个下标从 0 开始、大小为 n * m 的二维整数矩阵 grid &#xff0c;定义一个下标从 0 开始、大小为 n * m 的的二维矩阵 p。如果满足以下条件&#xff0c;则称 p 为 grid 的 乘积矩阵 &#xff1a; 对于每个元素 p[i][j] …

Redis HyperLogLog的使用

Redis HyperLogLog知识总结 一、简介二、使用 一、简介 Redis HyperLogLog是一种数据结构&#xff0c;用于高效地计算基数&#xff08;集合中唯一元素的数量&#xff09;。它的主要作用是用于在内存中高效地存储和计算大量数据的基数&#xff0c;而无需完全存储所有的数据。Hy…

CICD:Circle CI 实现CICD

持续集成解决什么问题 提高软件质量效率迭代便捷部署快速交付、便于管理 持续集成&#xff08;CI&#xff09; 集成&#xff0c;就是一些孤立的事物或元素通过某种方式集中在一起&#xff0c;产生联系&#xff0c;从而构建一个有机整体的过程。 持续&#xff0c;就是指长期…

BIM轻量化技术简介

BIM轻量化技术是指在工程建筑的BIM模型建立之后&#xff08;利用专业的BIM建模软件&#xff0c;比如Autodesk Revit, Bentley MicroStation, DS Catia等&#xff09;&#xff0c;通过对BIM模型的压缩处理等技术手段&#xff0c;让BIM可以在各类WEB浏览器、移动App上被使用的技术…

[架构之路-237]:目标系统 - 纵向分层 - 网络通信 - DNS的递归查询和迭代查询

目录 一、DNS协议与DNS系统架构 1.1 什么是DNS协议 1.2 为什么需要DNS协议 1.3 DNS系统架构 二、DNS系统的查询方式 2.1 递归与迭代的比较 2.2 DNS递归查询 2.3 DNS迭代查询 一、DNS协议与DNS系统架构 1.1 什么是DNS协议 DNS&#xff08;Domain Name System&#xff…

2.3 初探Hadoop世界

文章目录 零、学习目标一、导入新课二、新课讲解&#xff08;一&#xff09;Hadoop的前世今生1、Google处理大数据三大技术2、Hadoop如何诞生3、Hadoop主要发展历程 &#xff08;二&#xff09;Hadoop的优势1、扩容能力强2、成本低3、高效率4、可靠性5、高容错性 &#xff08;三…

数据分析案例-基于snownlp模型的MatePad11产品用户评论情感分析(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Android 14 正式发布,已经在 AOSP 中上线

本心、输入输出、结果 文章目录 Android 14 正式发布,已经在 AOSP 中上线前言总结主要更新内容机型支持优化性能的数据体现字体放大、多媒体支持加强Android 14 增加了对 10 位高动态范围 (HDR) 图像的支持提供了新的图形和尺寸管理用户体验 与隐私安全弘扬爱国精神Android 14…

nvm、node、npm解决问题过程记录

在Windows10如何降级Node.js版本&#xff1a;可以尝试将Node.js版本降级到一个较旧的版本&#xff0c;以查看问题是否得以解决。可以使用Node Version Manager (nvm) 来轻松切换Node.js版本&#xff0c;具体完整步骤&#xff1a; 首先&#xff0c;需要安装Node Version Manager…

leetcode-62.不同路径

1. 题目 2. 解答 dp[i][j]表示机器人位于第i&#xff0c;j位置的时候&#xff0c;有多少路径 如果i 0&#xff0c;dp[i][j] 1;如果j 0&#xff0c;dp[i][j] 1;其他情况dp[i][j] dp[i-1][j] dp[i][j - 1] #include <stdio.h>int solve(int m, int n) {int dp[m][…

【python海洋专题二十】subplots_adjust布局调整

上期读取soda&#xff0c;并subplot 但是存在一些不完美&#xff0c;本期修饰 本期内容 subplots_adjust布局调整 1&#xff1a;未调整布局的 2&#xff1a;调整布局 往期推荐 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深…

微信小程序框架---视图层逻辑层API事件

目录 前言 一、小程序框架介绍 1.响应的数据绑定 2.页面管理 3.基础组件 4.丰富的 API 二、视图层 View 1.WXML 数据绑定 列表渲染 条件渲染 模板 2.WXSS 尺寸单位 样式导入 内联样式 选择器 全局样式与局部样式 3.WXS 示例 注意事项 页面渲染 数据处理 …

水滴怕片效果实现

效果展示 CSS 知识点 border-radius 属性运用 FANCY-BORDER-RADIUS 工具 此工具主要是实现不规则的图形。 FANCY-BORDER-RADIUS 工具地址 页面整体布局实现 <div class"container"><div class"drop" style"--clr: #ff0f5b">&l…

Python学习基础笔记六十六——对象的方法

我们已经学习到的对象类型&#xff1a; 整数类型的对象 字符串类型的对象 列表类型的对象 元组类型的对象 对象通常都有属于自己的方法&#xff08;method&#xff09; 调用对象的方法和调用函数差不多&#xff0c;只要在前面加上所属对象的一个点。 var1 [1, 2, 3,4, 5,…