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

Spring security学习笔记

目录

  • 1. 概要
  • 2. spring security原理
    • 2.1 DelegatingFilterProxy
    • 2.2 FilterChainProxy
    • 2.3 SecurityFilterChain
    • 2.4 Spring Security 作用机制
  • 3.Spring Security快速入门
  • 4.高级自定义配置
  • 5. Spring Security 结合 JWT使用

1. 概要

Spring Security是一个用于在Java应用程序中实现 身份验证访问控制 的强大框架。它可以轻松地集成到任何基于Spring的应用程序中,提供了一套丰富的功能来保护应用程序的安全性。

2. spring security原理


对于servlet三大组件,spring security位于Filter过滤链中,SpringSecurity 本质就是一个过滤器链
三大组件

SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链

在这里插入图片描述

2.1 DelegatingFilterProxy

Spring 提供了一个 DelegatingFilterProxy 代理类,DelegatingFilterProxy 实现了Filter,因此它可以被注入到 FilterChain(过滤器链)中,同时,当请求到来时,它会把请求转发到Spring容器 中实现了Filter接口的 Bean 实体,DelegatingFilterProxy会从ApplicationContext中获取FilterBean 实体,然后将请求转发给到它,所以 DelegatingFilterProxy 桥接了 Servlet容器 和 Spring容器。

2.2 FilterChainProxy

DelegatingFilterProxy从Spring容器中获取得到的就是FilterChainProxy实体,而FilterChainProxy也是一个代理类,它最终会将请求转发到 Spring Security 提供的 SecurityFilterChain 中,FilterChainProxy就是 Spring Security 真正的入口起始点

2.3 SecurityFilterChain

SecurityFilterChain作用其实跟Servlet的FilterChain一样,同样维护了很多Filters,这些Filters 是由Spring Security提供的,每个 Security Filter 都有不同的职能,比如登录认证、CSRF防御…

2.4 Spring Security 作用机制

  1. 注册标准Filter:首先,会自动注入一个DelegatingFilterProxy到 Servlet 的FilterChain中。
  2. 请求转发到 Spring Security:当请求到来时,DelegatingFilterProxy就会自动在 Spring容器 中搜索名称为springSecurityFilterChain的Filter实体,其实际类型为FilterChainProxy。DelegatingFilterProxy最终会将请求转发给到FilterChainProxy。
  3. 找到匹配请求处理的SecurityFilterChain:FilterChainProxy内部维护了一系列SecurityFilterChains,他会依据请求内容找到对应处理该请求的SecurityFilterChain。
  4. 请求处理:找到能处理请求的第一个SecurityFilterChain后,就会遍历该SecurityFilterChain内部维护的一系列Filters,依次让这些 Security Filter 处理该请求,完成认证、授权等功能。

3.Spring Security快速入门

  • 步骤1:添加Spring Security依赖
    在你的Spring Boot项目的 pom.xml文件中,添加Spring Security的依赖:

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  • 步骤2:写一个简单地hello接口:
    hello接口

  • 步骤3:浏览器访问接口
    登录界面

    默认的用户名是 user ,默认的登录密码 在每次启动项目时随机生成

    密码

    从项目启动日志中可以看到默认的登录密码,登录成功后,就可以访问hello 接口了。

  • 步骤4:自己配置账户名或者密码
    配置账户

  • 步骤5:创建Spring Security配置类
    创建一个继承自 WebSecurityConfigurerAdapter的配置类,并重写 configure() 方法来配置Spring Security的行为。

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()  //这开始了一个授权请求的配置链。.authorizeRequests() 方法允许你指定哪些 URL 需要认证和授权。.antMatchers("/", "/home").permitAll() // 方法指定了两个 URL 模式,/ 和 /home。.permitAll() 表示这些 URL 可以被任何用户无条件访问,即使他们没有登录。.anyRequest().authenticated() //.anyRequest() 表示所有未被前面规则明确指定的请求。.authenticated() 指定这些请求需要经过身份验证才能访问。.and() //.and() 方法用于继续下一个配置步骤。在这里,它用于从授权请求配置切换到其他配置,如表单登录配置。.formLogin() //.formLogin() 配置了表单登录功能//.loginPage("/login") 指定了登录页面的 URL 注意:配置了.loginPage("/login") ,你必须写这个页面,如果不配置,就用Spring Security 内置的Form登录页面.permitAll()   // 表示这个登录页面可以被任何人访问,不需要身份验证。.and()  //同样地,.and() 用于继续到下一个配置步骤,这里是登出配置。.logout()   //.logout() 开始配置登出功能。.logoutUrl("/logout")  //设置了处理登出请求的 URL.logoutSuccessUrl("/login") //指定了登出成功后用户会被重定向到的 URL.invalidateHttpSession(true) //表示在登出时会销毁 HttpSession。.deleteCookies("JSESSIONID");   //用于删除 JSESSIONID cookie,以完全清除用户的会话}
    }
    

4.高级自定义配置

Spring Security抽象类 WebSecurityConfigAdapter—— 开发者通过继承它就能 得到Spring Security 默认的安全功能。 也可以通过覆盖它提供的方法来自定义自 的安全拦截方案。

  1. WebSecurityConfigAdapter 中默认存在的方法

    /**
    *在此方法中,可以配置认证管理器(AuthenticationManager),包括定义用户、密码、角色等。例如,可以连接到数据库或LDAP来加载用户信*息,也可以配置内存中的用户数据等等.*/
    protected void configure(AuthenticationManagerBuilder auth ); /**
    *这个方法允许配置不受Spring Security过滤器链保护的资源,比如静态资源、视图解析器资源等。
    */
    public void configure(WebSecurity web) ; /**
    *configure(HttpSecurity http): 开发者通过重写这个方法来配置HTTP安全相关的所有细节。在这里,可以指定哪些URL需要身份验证、哪些
    *可以匿名访问,还可以配置表单登录、HTTP基本认证、JWT认证等多种认证方式,以及自定义过滤器链、异常处理等
    */
    protected void configure(HttpSecurity http) throws Exception ;
    
  2. 写自己的Security配置类
    ##记得加@EnableWebSecurity注解##

    @Configuration
    @EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 从 Spring5 开始,强制要求密码要加密* BCryptPasswordEncoder是Spring Security提供的一个密码加密工具类,* 它实现了PasswordEncoder接口,用于对用户的密码进行安全哈希加密存储,增强了系统的安全性。*/@BeanPasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication()//配置了两个用户,包括用户的用户名、角色、密码,用户密码已经加密(123).withUser("zhangsan")//角色.roles("BOSS")//密码已经加密,可以通过 passwordEncoder.encode("123")获取.password("$2a$10$dQLFreAJHM0F.4XAWQMTA.kB5W3H2.hjA6xBUJFHTFT7iHRzO0flm")//连接方法.and().withUser("lisi").roles("EMPLOYEE").password("$2a$10$RDdoj3sm/RD7HzqSnU864eEE5kEZZxbyQqnYQJGrO2pgkUGCDutTC");}
    }
    

    重启应用就可以通过zhangsan和lisi登陆了,虽然实现认证功能,但是受保护的资源都是一样的,只要登陆了,所有的接口都能访问。

    但实际企业开发过程中我们要根据进行角色管理,如果要实现这些功能 就需要重写 WebSecurityConfigurerAdapter 中的 HttpSecurity 方法:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests() //开启登录配置// 使用antMatchers 匹配请求路径.antMatchers("/public/**").permitAll()  // 允许所有人访问以 "/public/" 开头的资源//访问以 "/boss/" 开头的资源需要 "boss" 角色.antMatchers("/boss/**").hasRole("ROLE_boss")//访问以 "/employee/" 开头的资源,必须具备 boos、manager 的角色.antMatchers("/employee/**").access("hasAnyRole('ROLE_boss','ROLE_manager')")// 访问以 "/api/" 开头的资源需要经过身份验证.antMatchers("/api/**").authenticated()// 任何其他请求也需要经过身份验证.anyRequest().authenticated()//开启表单登录.and().formLogin();}
    }
    
  3. 书写接口验证
    验证接口

  4. 使用注解简化
    上面介绍的认证与授权都是基于 URL 的,也可以通过注解来灵活地配置方法安全,要使用相关注解,首先要通过 @EnableGlobalMethodSecurity 注解开启基于注解的安全配置:

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {}
    

    (1) prePostEnabled=true

    开启后会解锁 @PreAuthorize@PostAuthorize 两个注解

    • @PreAuthorize
      会在执行方法前验证, 推荐使用 @PreAuthorize 因为支持Spring EL表达式
      @PreAuthorize("hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')")  //必须有全部的权限才可以访问
      @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") //至少有一个即可访问
      @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_USER')")  // 同上 
      
    • @PostAuthorize
      会在执行方法后验证

    (2) securedEnabled=true

    开启后会解锁 @Secured 注解,是基于角色的简单访问控制,Spring Security 3.x 之后不推荐。
    判断是否具有角色,注意这里匹配的字符串需要添加前缀 “ROLE_”

        @Secured({ "ROLE_admin", "ROLE_user" })@Secured({"ROLE_admin"})@GetMapping(value = "/test")public String testSecured() {return "testSecured";}
    
  5. 验证
    得要把之前的 配置移除掉

     // .antMatchers("/bossapi/**").hasRole("boss") 都移除掉
    

    验证

5. Spring Security 结合 JWT使用

  1. 为什么要结合JWT使用?

    Spring Security与JWT的结合在分布式系统、移动端和单页应用等场景下具备灵活性、可扩展性和安全性,因此被广泛应用于现代Web应用程序的身份验证和授权中。

    JWT有不可替代的优势,但Security也有使用的便利性(已内置了很多权限验证功能),所以我们要结合一起使用,结合的时候,我们只使用Security鉴权功能,登陆功能不需要,因为我们已经自己实现了JWT相关的登陆接口,只是登陆成功后我们要把用户的角色Role写进token就可以了。

  2. 角色Role认证

    默认情况下,UsernamePasswordAuthenticationFilter 处理的是 POST 请求到 /login 路径, 我们可以模仿 UsernamePasswordAuthenticationFilter 书写自己的过滤器

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = false) // 开启注解
    public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable();http.logout().disable();http.formLogin().disable();http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);// 禁用sessionhttp.authorizeRequests().anyRequest().authenticated();// 所有请求都需要认证// 添加自定义的过滤器http.addFilterBefore(new JwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);// 配置HTTP安全模块的异常处理,指定未授权访问时的处理逻辑http.exceptionHandling().authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response,AuthenticationException authException)->{response.setContentType("application/json;charset=utf-8");response.getWriter().write("{\"code\":403,\"message\":\"未授权\"}");});}private class JwtTokenFilter extends BasicAuthenticationFilter {public JwtTokenFilter() throws Exception {super(authenticationManager());}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {// 从请求头中获取认证令牌String token = request.getHeader("token");// 初始化一个空的权限列表List<GrantedAuthority> authorityList = new ArrayList<>();// 如果令牌存在且不为空if (ObjectUtil.isNotEmpty(token)) {// 解析令牌中的载荷信息JSONObject jsonObject = JSONUtil.parseObj(JWTUtil.parseToken(token).getPayload().toString());// 获取用户角色信息JSONArray roles = jsonObject.getJSONArray("roles");// 如果角色信息存在且不为空if (ObjectUtil.isNotEmpty(roles)) {// 遍历角色信息,构建权限列表for (Object role : roles) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.toString());authorityList.add(authority);}}}// 创建认证对象,用于存放权限列表UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("唯一", null, authorityList);// 将认证对象设置到当前的安全上下文中SecurityContextHolder.getContext().setAuthentication(authenticationToken);// 继续执行过滤器链中的下一个过滤器chain.doFilter(request, response);}}
    }
    

相关文章:

Spring security学习笔记

目录 1. 概要2. spring security原理2.1 DelegatingFilterProxy2.2 FilterChainProxy2.3 SecurityFilterChain2.4 Spring Security 作用机制 3.Spring Security快速入门4.高级自定义配置5. Spring Security 结合 JWT使用 1. 概要 Spring Security是一个用于在Java应用程序中实…...

MySQL:基础增删查改

MySQL&#xff1a;基础增删查改 插入插入冲突 查询distinctwhereorder bylimit 删除deletetruncate 更新 插入 基本插入语法&#xff1a; insert [into] 表名 (列1, 列2 ...) values (值1, 值2 ...);into可以省略(列1, 列2 ...)与后面的(值1, 值2)一一对应如果插入时数据完全…...

Apache DolphinScheduler 1.3.4升级至3.1.2版本过程中的踩坑记录

因为在工作中需要推动Apache DolphinScheduler的升级&#xff0c;经过预研&#xff0c;从1.3.4到3.1.2有的体验了很大的提升&#xff0c;在性能和功能性有了很多的改善&#xff0c;推荐升级。 查看官方的升级文档&#xff0c;可知有提供升级脚本&#xff0c;如果只是跨小版本的…...

最后一块石头的重量(超级妙的背包问题)

1049. 最后一块石头的重量 II 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果…...

如何评估和提升审查者在前端代码审查中的专业技能?

评估和提升审查者在前端代码审查中的专业技能可以通过以下步骤&#xff1a; 技能评估&#xff1a; 定期进行技能评估&#xff0c;了解审查者在前端开发各方面的能力&#xff0c;包括但不限于HTML、CSS、JavaScript、框架使用、代码规范等。 代码审查实践&#xff1a; 通过实…...

C++(区别于C的)基础内容总结

参考&#xff1a; C 教程 | 菜鸟教程 (runoob.com) 简介 C 被认为是一种中级语言&#xff0c;它综合了高级语言和低级语言的特点。 C 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C 进一步扩充和完善了 C 语言&#xff0c;最初命名为带类的C&…...

实现代码灵活性:用Roslyn动态编译和执行存储在数据库中的C#代码

在许多现代应用程序中&#xff0c;动态编译和执行代码是提升灵活性和功能的一种强大技术。本文将介绍如何使用Roslyn编译器平台动态编译和执行存储在数据库中的C#代码&#xff0c;并结合实际公司案例来说明这些技术的应用场景。 1. 引言 在很多应用场景中&#xff0c;我们可能…...

探索哈希表:C++中的实现与操作详解【Map、Set、数据结构】

探索哈希表&#xff1a;C中的实现与操作详解 介绍 哈希表&#xff08;Hash Table&#xff09;是一种常见的数据结构&#xff0c;它提供了一种高效的键值对存储方式&#xff0c;能够快速进行插入、删除和查找操作。在这篇博客中&#xff0c;我们将详细介绍哈希表的概念、在C中的…...

Python酷库之旅-第三方库Pandas(062)

目录 一、用法精讲 241、pandas.Series.view方法 241-1、语法 241-2、参数 241-3、功能 241-4、返回值 241-5、说明 241-6、用法 241-6-1、数据准备 241-6-2、代码示例 241-6-3、结果输出 242、pandas.Series.compare方法 242-1、语法 242-2、参数 242-3、功能 …...

python学习之旅(基础篇看这篇足够了!!!)

目录 前言 1.输入输出 1.1 输入 1.2 输出 2. 变量与常量 2.1 变量 2.2 常量 2.3 赋值 2.4格式化输出 3. 数据类型 4. 四则运算 5.“真与假” 5.1 布尔数 5.2 比较运算和逻辑运算 5.3 布尔表达式 6.判断语句 6.1 基本的if语句 6.2 if-else语句 6.3 if-elif-el…...

Azure OpenAI Embeddings vs OpenAI Embeddings

题意&#xff1a;Azure OpenAI 嵌入与 OpenAI 嵌入的比较 问题背景&#xff1a; Is anyone getting different results from Azure OpenAI embeddings deployment using text-embedding-ada-002 than the ones from OpenAI? Same text, same model, and the results are cons…...

重生奇迹MU职业成长三步走

在重生奇迹MU游戏中&#xff0c;转职是最重要的玩法之一。每个职业在转职后都会发生巨大的变化&#xff0c;经过三次转职后&#xff0c;你才有资格成为该游戏中最强大的冒险者。 一转&#xff0c;一切才刚刚开始 玩家完成第一次转职任务后&#xff0c;标志着我们成功度过了游…...

2024年中国数据中台行业研究报告

数据中台丨研究报告 核心摘要&#xff1a; 数据中台是企业数字化建设的重要构成&#xff0c;其通过整合企业基础设施和数据能力&#xff0c;实现数据资产化和服务复用&#xff0c;降低运营成本&#xff0c;支撑业务创新。受宏观经济影响&#xff0c;部分企业减少了对数据中台等…...

MySQL——数据表的基本操作(一)创建数据表

数据库创建成功后,就需要创建数据表。所谓创建数据表指的是在已存在的数据库中建立新表。需要注意的是&#xff0c;在操作数据表之前&#xff0c;应该使用 “ USE 数据库名 ” 指定操作是在哪个数据库中进行&#xff0c;否则会抛出 “ No database selected ” 错误。创建数据表…...

EPLAN EDZ 文件太大导入很慢如何解决?

目前各个品牌都在提供 EPLAN EDZ部件库文件,但是一般都是一个总的EDZ文件,导入过程中,因为电脑配置和其他问题,导致导入过程中EPLAN会崩溃或者长时间不动。 我们分析下EDZ文件的构成,这是个压缩文件,换了个壳而已。用压缩软件把edz打开,这里不是解压,直接右键,用解压…...

刷题——缺失的第一个正整数

缺失的第一个正整数_牛客题霸_牛客网 我选择了一个我比较能看懂的&#xff0c; int minNumberDisappeared(vector<int>& nums) {// write code heremap<int, int>hash;int n nums.size();//哈希表记录数组中出现的每个数字for(int i 0; i < n; i)hash[n…...

代理设置--一些库的代理设置

首先最好能获取一个免费代理&#xff0c;来继续下面的阅读和实验 也可以在本机设置代理&#xff0c;具体流程由于比较敏感&#xff0c;请自行搜索 代理设置成功后的测试网站是 http://www.httpbin.org/get , 访问该链接可以得到请求相关的信息&#xff0c;返回结果中的 ori…...

Debezium系列之:PostgreSQL数据库赋予账号数据采集权限的详细步骤

Debezium系列之:PostgreSQL数据库赋予账号数据采集权限的详细步骤 一、账号需要的权限二、创建账号,赋予登陆、复制权限三、赋予账号数据库权限四、赋予账号对表的权限五、创建PostgreSQL数据库复制组六、账号权限授予完整案例七、扩展——分区表设置八、扩展-撤销账号的权限…...

javascript:判断输入值是数字还是字母

1 代码示例 要判断输入值是数字还是字母&#xff0c;我们可以通过JavaScript获取输入框的值&#xff0c;然后使用isNaN函数来检查输入值是否为数字。 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title><s…...

Java-排序算法-复盘知识点

刷了24道简单排序题&#xff0c;18道中等排序题之后&#xff0c;给排序算法来个简单的复盘&#xff08;从明天开始刷动态规划咯&#xff09; 1.对于找多数元素&#xff08;出现次数超过一半的元素&#xff09;可以使用摩尔投票法。 2.HashSet的add方法非常实用&#xff1a;如…...

DMG2IMG终极指南:3分钟掌握苹果DMG文件跨平台转换技巧

DMG2IMG终极指南&#xff1a;3分钟掌握苹果DMG文件跨平台转换技巧 【免费下载链接】dmg2img DMG2IMG allows you to convert a (compressed) Apple Disk Images (imported from http://vu1tur.eu.org/dmg2img). Note: the master branch contains imported code, but lacks bug…...

BilibiliDown:突破B站视频下载限制的革新性工具

BilibiliDown&#xff1a;突破B站视频下载限制的革新性工具 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader &#x1f633; 项目地址: https://gitcode.com/gh_mirrors/bi/Bili…...

Audacity音频编辑终极指南:从零开始掌握免费专业工具

Audacity音频编辑终极指南&#xff1a;从零开始掌握免费专业工具 【免费下载链接】audacity Audio Editor 项目地址: https://gitcode.com/GitHub_Trending/au/audacity Audacity是一款功能强大的开源音频编辑软件&#xff0c;支持多轨录音、音频剪辑和效果处理&#x…...

用 AI 生成视频?试试 Hailuo 视频生成 API!

在现代数字时代&#xff0c;视频内容的需求不断增长&#xff0c;而制作高质量视频的门槛也随之降低。今天&#xff0c;我想和大家分享一个强大的工具——Ace Data Cloud Hailuo 视频生成 API。这款 API 不仅支持文本转语音、多个声音切换和情感调整&#xff0c;还能为你提供清晰…...

AudioLDM-S移动开发:Android音频API集成指南

AudioLDM-S移动开发&#xff1a;Android音频API集成指南 1. 引言 想在Android应用中实现"一句话生成专属音效"的酷炫功能吗&#xff1f;AudioLDM-S让这变得可能。这个强大的AI模型可以将文本描述直接转换为高质量的音效&#xff0c;从雨滴声到科幻音效都能轻松生成…...

ChatTTS 入门指南:从零开始构建你的第一个语音对话应用

最近在做一个需要语音交互的小项目&#xff0c;选型时发现了 ChatTTS 这个工具&#xff0c;感觉挺有意思的。它不像一些大厂的 TTS 服务那么“重”&#xff0c;更像是一个专为对话场景优化的语音合成工具。如果你是第一次接触&#xff0c;可能会觉得有点无从下手&#xff0c;比…...

告别特征点!FAST-LIVO2的‘直接法’融合:如何用原始点云和图像块实现更快的SLAM?

FAST-LIVO2&#xff1a;直接法SLAM的革命性突破与工程实践指南 1. 直接法SLAM的技术演进与核心价值 当波士顿动力的Atlas机器人完成后空翻动作时&#xff0c;其核心定位系统正面临着与人类体操运动员相似的挑战——如何在高速运动中维持对环境的精确感知。这正是FAST-LIVO2这类…...

YOLOv5 vs YOLOv8:2024年工业部署选型指南(附实测对比)

YOLOv5 vs YOLOv8&#xff1a;2024年工业部署选型指南&#xff08;附实测对比&#xff09; 在工业视觉检测领域&#xff0c;目标检测模型的选型直接关系到产线良率、运维成本和系统响应速度。作为YOLO系列当前最成熟的工业级解决方案&#xff0c;YOLOv5和YOLOv8的抉择让不少工程…...

FPGA音频播放器避坑指南:WM8731 I2C配置与左对齐时序的那些坑

FPGA音频播放器避坑指南&#xff1a;WM8731 I2C配置与左对齐时序的那些坑 第一次听到自己设计的FPGA音频播放器发出刺耳的噪音时&#xff0c;我盯着示波器上扭曲的波形陷入了沉思。作为嵌入式开发者&#xff0c;我们总在数字与模拟的交界处行走&#xff0c;而WM8731这颗看似简单…...

会Python可以找什么工作?

Python凭借简洁易用、功能强大的特点&#xff0c;成为当下就业面极广的编程语言。不少人学会后却不清楚可以找什么工作&#xff0c;其实从开发、数据分析到自动化运维都有大量机会&#xff0c;接下来为大家详细讲解一下。会Python后&#xff0c;可以找的工作有很多&#xff0c;…...