当前位置: 首页 > news >正文

Spring Security 6 系列之二 - 基于数据库的用户认证和认证原理

之所以想写这一系列,是因为之前工作过程中使用Spring Security,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级为6.3.0,关键是其风格和内部一些关键Filter大改,导致在配置同样功能时,多费了些手脚,因此花费了些时间研究新版本的底层原理,这里将一些学习经验分享给大家。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 spring-boo-3.3.0(默认引入的Spring Security是6.3.0),JDK版本使用的是19,所有代码都在spring-security-study项目上:https://github.com/forever1986/spring-security-study.git

目录

  • 1 用户读取的基本原理
  • 2 基于内存的用户配置
  • 3 基于数据库的用户配置
    • 3.1 Spring Security默认的JdbcUserDetailsManager
    • 3.2 自定义基于数据库的用户配置
  • 4 Spring Security认证底层原理
  • 5 密码加密方式PasswordEncoder
    • 5.1 密码加密原理
    • 5.2 DelegatingPasswordEncoder原理
    • 5.3 指定PasswordEncoder

上一章中,我们讲了基本入门,spring-boot如何通过默认配置集成Spring Security,也讲了如何自定义配置用户名和密码。在上一章中留下一个问题,就是用户名密码都是配置在项目里面,但实际项目中,我们都是放在数据库中,那么Spring Security如何配置数据库以及其认证原理是如何的,我们在这一章揭晓。

1 用户读取的基本原理

我们先来了解Spring Security如何读取到我们在yml文件中的数据。

1)我们看一下UserDetailsServiceAutoConfiguration这个类,其中有一个inMemoryUserDetailsManager方法
在这里插入图片描述

从上图中,我们可以看到,方法inMemoryUserDetailsManager注入了一个Bean,是一个InMemoryUserDetailsManager,其数据来自SecurityProperties(这个类在系列一中讲过,是读取yml或生成默认用户名密码的)。也就是说Spring Security默认构建了一个基于内存的用户密码管理类。

2)我们再看看InMemoryUserDetailsManager继承哪些类或者实现哪些接口。
在这里插入图片描述

上图中,我们可以看到InMemoryUserDetailsManager类实现了UserDetailsManager接口,而UserDetailsManager接口继承了UserDetailsService接口。关键点:UserDetailsService接口只有一个loadUserByUsername方法(通过用户名获得UserDetails)。UserDetailsManager接口只是在UserDetailsService接口的基础上增加了对用户的增删改查。
那么可以理解,只需要我们自己实现一个实现UserDetailsService接口或者UserDetailsManager接口的Bean,即可替换原先的配置

3)UserDetails接口,我们看到UserDetailsService接口的loadUserByUsername方法返回一个对象是UserDetails,其也是一个接口。该接口定义了一系列获取用户信息相关的方法
在这里插入图片描述

4)而我们在UserDetailsServiceAutoConfiguration这个类中的inMemoryUserDetailsManager方法看到,使用了一个User类创建了一个用户。这个User类其实就是实现了UserDetails接口

User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()

5)总结:Spring Security是通过调用UserDetailsService接口的实现类,获得一个UserDetails,里面包括用户信息,可以用于认证的。而Spring Security默认有2个实现类InMemoryUserDetailsManager和JdbcUserDetailsManager,分别对应基于内存的用户认证和基于数据库的用户认证。另外内置一个User类,实现最简单的UserDetails信息。

2 基于内存的用户配置

通过上面对其基本原理的理解,我们知道只需要实现一个UserDetailsService接口的类,就可以替换原来的用户密码配置,下面我们就开始实现。

代码参考lesson02子模块

1)新建一个子模块lesson02,其pom文件引入

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--Spring Boot 提供的 Security 启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies>

2)我们只需要新增一个名称service的pakage,在下面新增一个类InMemoryUserDetailsServiceImpl,实现UserDetailsService 接口

@Service
public class InMemoryUserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return User.withUsername("test").password("{noop}4321").build();}
}

注意:需要将该类设置注解@Service,这样就纳入spring的Bean管理,同时也屏蔽原先默认配置的Bean
密码部分:{noop}4321,前面那个{noop}代表未加密密码,为什么要加入这个东西,后面讲解Spring Security认证流程原理在仔细讲解

3)跟lesson01一样,定义一个demo的controller和启动类
4)访问:http://127.0.0.1:8080/demo 我们可以看见,是使用新的test用户名和4321的密码才能登陆成功,原先控制台或者yml配置都失效了。

3 基于数据库的用户配置

3.1 Spring Security默认的JdbcUserDetailsManager

上面分别讲述了读取用户的原理以及基于内存读取用户的方法。那么基于数据库的用户配置才是今天的主菜。我们看到中Spring Security已经有一个JdbcUserDetailsManager实现类,如果我们要使用该类,需要按照其默认的配置创建表,创建表的语句如下位置:

在这里插入图片描述

注意:如果使用默认JdbcUserDetailsManager,可以去看其源码,定义了很多SQL语句,且使用的是传统JdbcTemplate的方式。而在真正项目中,我们一般会引入如mybatis框架,以及用户表会有自己的一些格外信息,所以JdbcUserDetailsManager大部分时候都是不符合我们的要求,因此实际中,我们会自定义自身的用户表以及基于数据库的UserDetailsService

3.2 自定义基于数据库的用户配置

前提条件:基于mysql数据库创建一个数据库,名为spring_security_study,创建用户表t_user

-- spring_security_study.t_user definition
CREATE TABLE `t_user` (`id` bigint NOT NULL AUTO_INCREMENT,`username` varchar(100) NOT NULL,`password` varchar(100) NOT NULL,`email` varchar(100) DEFAULT NULL,`phone` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;INSERT INTO spring_security_study.t_user (username, password, email, phone)VALUES('test', '{noop}1234','test@test.com','13788888888');

下面开始说明基于自定义数据库的用户配置

代码参考lesson03子模块

1)新建子模块lesson03,其pom引入以下依赖:(引入mybatis-plus、mysql-connector、druid连接池、lombok)

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--Spring Boot 提供的 Security 启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency>
</dependencies>

2)在resources下面创建yaml文件,配置数据库连接和mybatis-plus相关配置

server:port: 8080
spring:# 配置数据源datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/spring_security_study?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: rootdruid:initial-size: 5min-idle: 5maxActive: 20maxWait: 3000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: select 'x'testWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: falsefilters: stat,wall,slf4jconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;socketTimeout=10000;connectTimeout=1200mybatis-plus:global-config:banner: falsemapper-locations: classpath:mappers/*.xmltype-aliases-package: com.demo.lesson03.entityconfiguration:cache-enabled: falselocal-cache-scope: statement

3)创建package:entity和mapper,分别定义TUser和TUserMapper,读取数据库用户数据。

@Data
public class TUser {@TableId(type = IdType.ASSIGN_ID)private Long id;private String username;private String password;private String email;private String phone;
}

TUserMapper定义个通过用户名获取用户的方法

@Mapper
public interface TUserMapper {// 根据用户名,查询用户信息@Select("select * from t_user where username = #{username}")TUser selectByUsername(String username);}

4)新建package:service,新建一个JdbcUserDetailsServiceImpl,通过mapper获取用户并返回

@Service
public class JdbcUserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate TUserMapper tUserMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查询自己数据库的用户信息TUser user = tUserMapper.selectByUsername(username);if(user == null){throw new UsernameNotFoundException(username);}return User.builder().username(user.getUsername()).password(user.getPassword()).roles("USER").build();}}

5)和lesson02一样定义一个demo的controller接口和启动类SecurityLesson03Application,并启动
6)访问:http://127.0.0.1:8080/demo 我们可以看见,是使用新的test用户名和1234的密码才能登陆成功,原先控制台或者yml配置都失效了。

4 Spring Security认证底层原理

上面讲解了如何通过内存或者数据库配置登录用户信息,那么Spring Security是如何做用户认证的呢?
首先我们要先了解到Spring Security是一系列的Filter过滤器组成的链路,每个过滤器处理器对应的功能,如果符合则往下走,不符合则返回。关于这部分,我们在下一章中在着重讲。
之前说过Spring Security支持的认证有多种,还可以自定义认证。我们这里以用户名和密码的认证方式为例,讲解一下其主要的原理。我们主要关注Spring Security的认证过滤器(UsernamePasswordAuthenticationFilter)。

1)我们可以debug一下HttpSecurity下面这行代码,里面可以看到Spring Security默认配置定义了哪些Filter过滤器。

在这里插入图片描述
2)我们可以截图看到以下默认配置下有16个过滤器(不同Security版本可能不同,我这6.3.0默认什么都不配置是16),其过滤器的各自用途我们下一章讲。这一章我们主要看UsernamePasswordAuthenticationFilter

在这里插入图片描述
3)UsernamePasswordAuthenticationFilter是继承AbstractAuthenticationProcessingFilter,我们看看AbstractAuthenticationProcessingFilter的doFilter方法,里面有2个关键内容,一个是调用attemptAuthentication方法做认证(这个方法具体实现是在UsernamePasswordAuthenticationFilter),一个successfulAuthentication方法做后续认证成功处理(包括保存SecurityContext,调用securityContextRepository存储session)
在这里插入图片描述

4)我们回到认证流程,刚才说AbstractAuthenticationProcessingFilter调用attemptAuthentication,其实就是调用UsernamePasswordAuthenticationFilter里面的attemptAuthentication方法就是执行关键。看下图解释attemptAuthentication的流程
在这里插入图片描述
5)上图的最后一步验证,其实是调用AuthenticationManager的authenticate方法,AuthenticationManager是一个接口,该接口实现类有几个,Spring Security默认提供ProviderManager
6)ProviderManager的authenticate方法中,真正认证的是result = provider.authenticate(authentication);这一句,而provider是一个叫AuthenticationProvider接口
在这里插入图片描述
7)AuthenticationProvider接口,Spring Security默认提供抽象实现类AbstractUserDetailsAuthenticationProvider,我们关注其authenticate方法,方法里面有2个调用值得我们注意,一个是retrieveUser(这个类获得用户信息)和一个additionalAuthenticationChecks(做用户认证)。
在这里插入图片描述

8)我们可以看到retrieveUser和additionalAuthenticationChecks方法在AbstractUserDetailsAuthenticationProvider只是一个定义,还需要其子类实现,而Spring Security默认提供DaoAuthenticationProvider实现类
在这里插入图片描述

从上图我们就可以理解,为什么我们实现UserDetailsService接口,就能够修改用户信息的来源(比如说数据库),因为在retrieveUser方法中就是调用UserDetailsService接口去获取用户信息。
另外一个就是用户认证additionalAuthenticationChecks方法,通过一个PasswordEncoder去做用户密码匹对。

9)总结:通过UsernamePasswordAuthenticationFilter过滤器做认证,UsernamePasswordAuthenticationFilter调用AuthenticationManager管理器的authenticate方法,而AuthenticationManager是调用AuthenticationProvider的authenticate方法,AuthenticationProvider是通过其实现类DaoAuthenticationProvider提供最终的认证。如下流程图:
在这里插入图片描述

5 密码加密方式PasswordEncoder

5.1 密码加密原理

我们在前面分析认证原理中,DaoAuthenticationProvider的additionalAuthenticationChecks方法就是实现其密码匹配的。我们可以看到里面有一个PasswordEncoder。从名字来看就是密码加密
在这里插入图片描述
Spring Security提供了PasswordEncoder接口,并通过实现该接口内置很多密码加密验证方式,比如不加密、对称加密、非对称加密甚至可以自定义。这里我们着重了解3个实现类NoOpPasswordEncoder、BCryptPasswordEncoder和DelegatingPasswordEncoder
在这里插入图片描述
上图中就是3个常用的内置加密方式,他们分别代表NoOpPasswordEncoder(不加密)、BCryptPasswordEncoder(使用SHA-256 +随机盐+密钥对密码进行加密)、DelegatingPasswordEncoder(代理类,通过其前缀判断不同加密方式)。Spring Security默认情况下,采用的是DelegatingPasswordEncoder,下面我们了解一下DelegatingPasswordEncoder。

5.2 DelegatingPasswordEncoder原理

DelegatingPasswordEncoder可以通过给密码前面增加一个{前缀},同时支持多种密码加密验证,而Spring Security默认的方式就是DelegatingPasswordEncoder。下图就是不同加密方式的前缀。

注意:前面我们在密码之前增加{noop},其实就是采用不加密方式,当然这是非常不安全的,只有在演示中使用

在这里插入图片描述

5.3 指定PasswordEncoder

你可以指定自己的密码加密方式,只需要继承PasswordEncoder接口,并实现其方法即可。比如指定BCryptPasswordEncoder

@Bean
public BCryptPasswordEncoder createPasswordEncoder(){return new BCryptPasswordEncoder();
}

在实际项目中,密码一定是要加密,且最好不可逆和难以破解的方式,比如SHA-256。

结语:至此,我们终于将Spring Security的认证底层原理讲了一遍。如果不了解的朋友,可以多debug几次就能够明白其中原理。到目前为止,我们只是配置了用户读取方式,其它的Spring Security配置都还是默认的,我们也可以看到默认情况下,Spring Security就加载了16个过滤器,说明有很多功能还没有讲,那么下一章,就是了解Spring Security底层原理以及常见Filter过滤的作用。

相关文章:

Spring Security 6 系列之二 - 基于数据库的用户认证和认证原理

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级为6.3.0&#xff0c;关键是其风…...

mfc140.dll是什么东西?mfc140.dll缺失的几种具体解决方法

mfc140.dll是Microsoft Foundation Classes&#xff08;MFC&#xff09;库中的一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它是微软基础类库的一部分&#xff0c;为Windows应用程序的开发提供了丰富的类库和接口。MFC库旨在简化Windows应用程序的开发过程&…...

【STM32 Modbus编程】-作为主设备写入多个线圈和寄存器

作为主设备写入多个线圈和寄存器 文章目录 作为主设备写入多个线圈和寄存器1、硬件准备与连接1.1 RS485模块介绍1.2 硬件配置与接线1.3 软件准备2、写入多个线圈2.1 数据格式2.2 发送数据2.3 结果3、写入多个寄存器3.1 数据格式3.2 发送数据3.3 结果本文将实现STM32作为ModBus主…...

Windows安全中心(病毒和威胁防护)的注册

文章目录 Windows安全中心&#xff08;病毒和威胁防护&#xff09;的注册1. 简介2. WSC注册初探3. WSC注册原理分析4. 关于AMPPL5. 参考 Windows安全中心&#xff08;病毒和威胁防护&#xff09;的注册 本文我们来分析一下Windows安全中心&#xff08;Windows Security Center…...

微积分复习笔记 Calculus Volume 2 - 4.2 Direction Fields and Numerical Methods

4.2 Direction Fields and Numerical Methods - Calculus Volume 2 | OpenStax...

深入理解旋转位置编码(RoPE)及其在大型语言模型中的应用

文章目录 前言一、 旋转位置编码原理1、RoPE概述2、 复数域内的旋转1、位置编码生成2、 应用位置编码二、RoPE的实现细节1、RotaryEmbedding类设计2、apply_rotary_pos_emb函数3、demo_apply_rotary_pos_emb函数三、完整RoPE代码Demo前言 随着自然语言处理(NLP)领域的快速发…...

内网穿透的应用-在OpenWrt上轻松搭建SFTP服务,安全传输文件不再难!

文章目录 前言1. 安装openssh-sftp-server2. 安装cpolar工具3.配置SFTP远程访问4.固定远程连接地址 前言 本次教程我们将在OpenWRT系统上安装SFTP服务&#xff0c;并结合cpolar内网穿透&#xff0c;创建安全隧道映射22端口&#xff0c;实现在公网环境下远程OpenWRT SFTP&#…...

【图像处理lec3、4】空间域的图像增强

目录 1. 空间域图像增强的背景与目标 2. 空间域处理的数学描述 3. 灰度级变换 4. 幂律变换&#xff08;Power-Law Transformation&#xff09; 5、 分段线性变换 Case 1: 对比度拉伸 Case 2: 灰度切片 Case 3: 按位切片 6、对数变换&#xff08;Logarithmic Transform…...

【算法day13】二叉树:递归与回溯

题目引用 找树左下角的值路径总和从中序与后序遍历构造二叉树 今天就简简单单三道题吧~ 1. 找到树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 我们…...

上海亚商投顾:创业板指缩量下跌 多只高位股午后跌停

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 市场全天震荡调整&#xff0c;创业板指领跌&#xff0c;高位股开始出现退潮&#xff0c;建设工业、星光股份、…...

单步调试Android Framework——App冷启动

纸上得来终觉浅&#xff0c;绝知此事要躬行。 —— [宋]陆游 基于aosp_cf_x86_64_phone-trunk_staging-eng &#xff0c; 下面是具体断点位置。 第一部分&#xff0c;桌面launcher进程 com.android.launcher3.touch.ItemClickHandler onClickonClickAppShortcutstartAppShor…...

统计一个目录下的文件及目录数量-linux010

要统计一个目录下的文件数量&#xff08;包括子目录中的文件&#xff09;&#xff0c;可以使用以下命令&#xff1a; 1. 统计所有文件数量&#xff08;包括子目录&#xff09; 在终端中运行以下命令&#xff1a; find /path/to/directory -type f | wc -l 解释&#xff1a;…...

spring RestTemplate使用说明

rest-template是spring对httpclient的逻辑封装&#xff0c;它底层还是基于httpclient&#xff0c;所以一些配置其实跟httpclient是强相关的。 基本配置 rest-template可以不带参数&#xff0c;使用默认配置&#xff0c;也可以指定ClientHttpRequestFactory参数&#xff0c;Cl…...

thinkphp:try-catch捕获异常

使用简单的例子&#xff0c;实现了一个简单的try-catch捕获异常的实例 //开始事务Db::startTrans(); try{ //有异常抛出异常 if(存在错误){ throw new \Exception("异常信息"); } // 提交事务 Db::commit(); // 返回成功信息 ... } catch (\…...

shardingsphere分库分表跨库访问 添加分片规则

shardingsphere分库分表跨库访问 添加分片规则 建立 JDBC 环境 创建表 t_order&#xff1a; CREATE TABLE t_order (tid bigint(20) NOT NULL,tname varchar(255) DEFAULT NULL,goods_id bigint(20) DEFAULT NULL,tstatus varchar(255) DEFAULT NULL,PRIMARY KEY (tid) ) E…...

c++:std::map下标运算符的不合理使用

这是我分析之前遗留代码时发现的一个隐藏点&#xff1b;不过我并不认为这样使用std::map是合理的。 看看简化后的代码&#xff0c;v1、v2的值应该是多少呢&#xff1f; #include <map>std::map<int, int> cm[2];int get_cm_value(int device, int ctrl) { auto …...

KeyFormer:使用注意力分数压缩KV缓存

Keyformer: KV Cache Reduction through Key Tokens Selection for Efficient Generative Inference 202403&#xff0c;发表在Mlsys Introduction 优化KV cache的策略&#xff0c;主要是集中在系统级别的优化上&#xff0c;比如FlashAttention、PagedAttention&#xff0c;它…...

MetaGPT源码 (ContextMixin 类)

目录 理解 ContextMixin什么是 ContextMixin&#xff1f;主要组件实现细节 测试 ContextMixin示例&#xff1a;ModelX1. 配置优先级2. 多继承3. 多继承重写4. 配置优先级 在本文中&#xff0c;我们将探索 ContextMixin 类&#xff0c;它在多重继承场景中的集成及其在 Python 配…...

MATLAB生成.exe独立程序过程(常见问题解决方法)(2024.12.14)

本文只记录我执行过程中遇到的关键问题、以及解决方法&#xff0c;不讲诉整个流程。 电脑环境 win11系统 matlab 2024b 版本 整体流程 1.下载matlab运行时库,简写为MCR 2.配置MCR环境 3.打包程序 4.目标机器安装程序 一、下载MCR 下载这个折腾了大半天&#xff0c;大概问题就是…...

PHP排序算法:数组内有A~E,A移到C或者C移到B后排序,还按原顺序排序,循环

效果 PHP代码 public function demo($params){function moveNext($arr){$length count($arr);$lastElement $arr[$length - 1];for ($i $length - 1; $i > 0; $i--) {$arr[$i] $arr[$i - 1];}$arr[0] $lastElement;return $arr;}function moveAndReplace($array, $from…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...