Spring Boot + Spring Security基础入门教程
Spring Security简介
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Security 致力于为 Java 应用程序提供身份验证和授权的能力。
Spring Security 两大重要核心功能:用户认证(Authentication)和用户授权(Authorization)。
用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,有的用户既能读取,又能修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
准备工作
创建Spring Boot项目
pom.xml文件(根据自己所需引入)
<dependencies><!-- security(安全认证) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mybatis-plus(数据库操作) --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><!-- redis(缓存) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- swagger(api接口文档) --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><!-- jjwt(token生成与校验) --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- fastjson2(JSON处理) --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.23</version></dependency><!-- mysql(连接驱动) --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- druid(mysql连接池) --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency></dependencies>
认证(Authentication)
登陆校验流程

原理初探
SpringSecurity 的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。

图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。
FilterSecurityInterceptor:负责权限校验的过滤器。
我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。
在控制台处点击Evaluate Expression或Alt+F8,如下图:
然后输入 run.getBean(DefaultSecurityFilterChain.class) 进行过滤,可以看到 run 容器中的 15 个过滤器:

Spring Security配置类
import com.zm.springsecurity.common.filter.CustomAuthenticationFilter;
import com.zm.springsecurity.common.security.CustomAuthenticationFailureHandler;
import com.zm.springsecurity.common.security.CustomAuthenticationSuccessHandler;
import com.zm.springsecurity.common.security.CustomLogoutSuccessHandler;
import com.zm.springsecurity.service.impl.CustomUserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {private static final String URL_WHITELIST[] ={"/v2/api-docs", "/swagger-resources/configuration/ui","/swagger-resources", "/swagger-resources/configuration/security","/swagger-ui.html", "/webjars/**", // swagger不需要授权即可访问的路径"/login","/logout","/my/login","/my/logout","/captcha","/password","/image/**","/test/**"};@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Beanprotected CustomAuthenticationFilter customAuthenticationFilter() throws Exception {CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter();authenticationFilter.setFilterProcessesUrl("/my/login");authenticationFilter.setUsernameParameter("username");authenticationFilter.setPasswordParameter("password");authenticationFilter.setAuthenticationManager(super.authenticationManager());authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());return authenticationFilter;}// @Override
// @Bean
// public AuthenticationManager authenticationManagerBean() throws Exception {
// return super.authenticationManagerBean();
// }
//
// @Override
// protected AuthenticationManager authenticationManager() throws Exception {
// return super.authenticationManager();
// }@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable() // 开启跨域请求和关闭csrf攻击.userDetailsService(new CustomUserDetailsServiceImpl())
// .formLogin().loginPage("/login_page")
// .loginProcessingUrl("/my/login")
// .usernameParameter("username").passwordParameter("password").permitAll()
// .successHandler(new CustomAuthenticationSuccessHandler()) // 认证成功处理器
// .failureHandler(new CustomAuthenticationFailureHandler()) // 认证失败处理器
// .and().logout().logoutUrl("/my/logout").logoutSuccessHandler(new CustomLogoutSuccessHandler()) // 退出登录成功处理器.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session禁用配置(无状态).and().authorizeRequests() // 验证请求拦截规则.antMatchers(URL_WHITELIST).permitAll() // 配置访问认证白名单.antMatchers("/admin/**").hasRole("admin") // 要具有某种权限.antMatchers("/user/**").hasAnyRole("admin", "user") // 要具有某种权限中的一种.anyRequest().authenticated();http.addFilterAt(this.customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}
}
使用数据库进行认证
注:本文采用 MyBatis-Plus 作为持久层框架,与 MyBatis-Plus 相关内容自行编写。
实现UserDetails接口
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;public class CustomUserDetails implements UserDetails {private User user;private List<SimpleGrantedAuthority> authorityList;public CustomUserDetails() {}public CustomUserDetails(User user, List<String> roleList) {this.user = user;this.authorityList = roleList.stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorityList;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getPassword();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return user.getStatus();}
}
自定义UsernamePasswordAuthenticationFilter过滤器
若处理请求为表单类型的数据,则此步忽略并删除 Security 配置类中 CustomAuthenticationFilter 相关的内容。UsernamePasswordAuthenticationFilter 为认证过滤器,默认只能处理表单提交的数据,如需处理 JSON 数据,则需要重写 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;/*** 登录认证过滤器,处理认证的请求体为 JSON 的数据*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {String contentType = request.getContentType();logger.info("contentType = " + contentType);if (!Objects.isNull(contentType) && (contentType.equals(MediaType.APPLICATION_JSON_VALUE) || contentType.equals(MediaType.APPLICATION_JSON_UTF8_VALUE))) {UsernamePasswordAuthenticationToken authRequest = null;try (InputStream inputStream = request.getInputStream()) {ObjectMapper mapper = new ObjectMapper(); // JSON数据映射器Map<String,String> params = mapper.readValue(inputStream, Map.class);authRequest = new UsernamePasswordAuthenticationToken(params.get("username"), params.get("password"));} catch (IOException e) {e.printStackTrace();authRequest = new UsernamePasswordAuthenticationToken("", "");} finally {setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}}else {return super.attemptAuthentication(request, response);}}
}
自定义处理器
JWT工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;import java.util.Date;public class JWTUtils {private static final String tokenSignKey = "zm_sign_key"; // 私钥(盐),太短会报异常:secret key byte array cannot be null or empty.private static final Integer tokenExpiration = 60 * 60 * 24 * 14; // 14天public static String createToken(String username){String token = Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("username", username).signWith(SignatureAlgorithm.HS512, tokenSignKey).compact();return token;}public static String createToken(Long userId, String username){String token = Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("userId", userId).claim("username", username).signWith(SignatureAlgorithm.HS512, tokenSignKey).compact();return token;}public static Long getUserId(String token) {try {if (!StringUtils.hasLength(token)) {return null;}Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return claims.get("userId", Long.class);} catch (Exception e) {e.printStackTrace();return null;}}public static String getUsername(String token) {try {if (!StringUtils.hasLength(token)) {return "";}Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return claims.get("username", String.class);} catch (Exception e) {e.printStackTrace();return null;}}
}
响应JSON数据信息
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;public class ResponseUtils {public static void response(HttpServletResponse response, String data) throws IOException {response.setContentType("text/html;charset=utf-8");PrintWriter responseWriter = response.getWriter();responseWriter.write(data);responseWriter.flush();responseWriter.close();}
}
自定义认证成功处理器
import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.JWTUtils;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String customUserDetails = authentication.getPrincipal().toString(); // 继承UserDetails的对象String token = JWTUtils.createToken(username);String jsonString = JSON.toJSONString(ResultUtils.ok("登录成功", token));ResponseUtils.response(response, jsonString);}
}
为什么 authentication.getPrincipal() 的结果是继承 UserDetails 的对象:ProviderManager 中的 this.copyDetails(authentication, result); 语句。
private void copyDetails(Authentication source, Authentication dest) {if (dest instanceof AbstractAuthenticationToken && dest.getDetails() == null) {AbstractAuthenticationToken token = (AbstractAuthenticationToken)dest;token.setDetails(source.getDetails());}}
自定义认证失败处理器
import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {String message = exception.getMessage();if(exception instanceof BadCredentialsException){message = "用户名或密码错误!";}String jsonString = JSON.toJSONString(ResultUtils.fail(message));ResponseUtils.response(response, jsonString);}
}
自定义注销成功处理器
import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String jsonString = JSON.toJSONString(ResultUtils.ok("退出登录成功!"));ResponseUtils.response(response, jsonString);}
}
授权(Authorization)
未完待续...
相关文章:
Spring Boot + Spring Security基础入门教程
Spring Security简介 Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Security 致力于为 Java 应用程序提供身份验证和授权的能力。 Spring Security 两大重要核心功能:用户认证(Authentication)和用户授权&am…...
MySQL数据库,表的增删改查详细讲解
目录 1.CRUD 2.增加数据 2.1创建数据 2.2插入数据 2.2.1单行插入 2.2.2多行插入 3.查找数据 3.1全列查询 3.2指定列查询 3.3查询字段为表达式 3.3.1表达式不包含字段 3.3.2表达式包含一个字段 3.3.3表达式包含多个字段 3.4起别名 3.5distinct(去重) 3.6order …...
SpringCloud-Gateway实现网关
网关作为流量的入口,常用的功能包括路由转发、权限校验、限流等 Spring Cloud 是Spring官方推出的第二代网关框架,由WebFluxNettyReactor实现的响应式的API网关,它不能在传统的servlet容器工作,也不能构建war包。基于Filter的方式…...
Redis 如何配置读写分离架构(主从复制)?
文章目录 Redis 如何配置读写分离架构(主从复制)?什么是 Redis 主从复制?如何配置主从复制架构?配置环境安装 Redis 步骤 通过命令行配置从节点通过配置文件配置从节点Redis 主从复制优点Redis 主从复制缺点 Redis 如何…...
代码随想录二刷day05 | 哈希表之242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和
当遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了 二刷day05 242.有效的字母异位词349. 两个数组的交集202. 快乐数1. 两数之和 242.有效的字母异位词 题目链接 解题思路: class Solution { public:bool isAnagram(string s, string…...
2023年4月广东省计算机软考中/高级备考班招生简章
软考是全国计算机技术与软件专业技术资格(水平)考试(简称软考)项目,是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试,既属于国家职业资格考试,又是职称资格考试。 系统集成…...
在Github中77k星的王炸AutoGPT,会独立思考,直接释放双手
文章目录 1 前言1.1 什么是AutoGPT1.2 为什么是AutoGPT 2 AutoGPT部分实例2.1 类似一个Workflow2.2 市场调研2.3 自己写播客2.4 接入客服 3 安装和使用AutoGPT3.1 安装3.2 基础用法3.3 配置OpenAI的API3.4 配置谷歌API3.5 配置Pinecone API 4.讨论 1 前言 迄今为止,…...
FVM链的Themis Pro,5日ido超百万美元
交易一直是 DeFi 乃至web3领域最经久不衰的话题,也因此催生了众多优秀的去中心化协议,如 Uniswap 和 Curve。这些协议逐渐成为了整个系统的基石。 在永续合约方面,DYDX 的出现将 WEB2 时代的订单簿带回了web3。其链下交易的设计,仿…...
OpenCV实战——尺度不变特征检测器
OpenCV实战——尺度不变特征检测器 0. 前言1. SURF 特征检测器2. SIFT 特征检测算法3. 完整代码相关链接0. 前言 特征检测的不变性是一个重要概念,虽然方向不变性(即使图像旋转也能检测到相同特征点)能够被简单特征点检测器(例如 FAST 特征检测器等)处理,但难以实现在图像尺…...
如何快速建立一个podman环境
本文介绍如何安装podman,并创建podman容器 环境 Centos8 安装podman Podman 是一个容器环境,首先在主机上安装 Podman。执行下面命令来安装podman: [rootlocalhost ~]# yum -y install podman然后修改一下用户命名空间的大小:…...
计算机视觉:人工智能领域当下火热的计算机视觉技术综述
计算机视觉技术发展火热,是当前人工智能技术核心领域之一,计算机视觉是人工智能领域的一颗明珠,它是目前人工智能领域最早得到应用的技术之一,拥有广大的发展空间,目前很多技术产品已经得到应用,并改变着这个世界。 当下火热的技术 1. 目标检测:通过计算机视觉技术,检…...
EMC 专用名词大全~骚扰波形
2.1 瞬态(的) transient (adjective and noun) 在两相邻稳定状态之间变化的物理量或物理现象,其变化时间小于所关注的时间尺度。 2.2 脉冲 Pulse 在短时间内突变,随后又迅速返回其初…...
14:24面试,14:32就出来了 ,问的实在是太...
从外包出来,没想到算法死在另一家厂子,自从加入这家公司,每天都在加班,钱倒是给的不少,所以也就忍了。没想到8月一纸通知,所有人不许加班,薪资直降30%,顿时有吃不起饭的赶脚。 好在有…...
高频算法题
排序 冒泡排序快速排序选择排序归并排序堆排序 912. 排序数组 - 力扣(LeetCode) 数组中重复的数字 数组 删除有序数组中的重复项 26. 删除有序数组中的重复项 - 力扣(LeetCode) 最小的K个数 最小K个数 - 最小K个数 - 力扣&a…...
AI工程师眼中的未来 | 年轻人如何求职选方向
一个人的命运不仅要看个人的奋斗 也要看历史的选择 如果能顺应未来的趋势选择对了方向 就能让财富巨增瞬间起飞 但是如果选择错了方向 随着社会的发展 有很多工作的机会会渐渐的消失 而我们自己也会更容易被社会所淘汰 所以未来的趋势是什么 我们应该如何选择不同的方向 这对现…...
能自动翻译的软件-最精准的翻译软件
批量翻译软件是一种利用自然语言处理技术和机器学习算法,可以快速翻译大量文本内容的工具。批量翻译软件可以处理多种格式的文本,包括文档、网页、邮件、PDF等等,更符合掌握多语言的计算机化需求。 147CGPT翻译软件特点: 1.批量任…...
7.1 大学排行榜分析(project)
大学排名没有绝对的公正与权威,文件(alumni.txt, soft.txt)中为按照不同评价体系给出的国内大学前100名排行,对比两个排行榜单前m的学校的上榜情况,分析不同排行榜排名的差异。 输入输出 第一行输入1,第二行输入m&…...
TensorFlow 2.0 的新增功能:第三、四部分
原文:What’s New in TensorFlow 2.0 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只关心如何实现目…...
第1章 如何听起来像数据科学家
第1章 如何听起来像数据科学家 文章目录 第1章 如何听起来像数据科学家1.1.1 基本的专业术语1.1.3 案例:西格玛公司1.2.3 为什么是Python1.4.2 案例:市场营销费用1.4.3 案例:数据科学家的岗位描述 我们拥有如此多的数据,而且正在生…...
哈希表题目:在系统中查找重复文件
文章目录 题目标题和出处难度题目描述要求示例数据范围进阶 解法思路和算法代码复杂度分析 进阶问题答案后记 题目 标题和出处 标题:在系统中查找重复文件 出处:609. 在系统中查找重复文件 难度 6 级 题目描述 要求 给定一个目录信息列表 paths…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
