前端带你学后端系列 ⑥【安全框架Spring Security篇二】

前端带你学后端系列 ⑥【安全框架Spring Security篇二】

  • Ⅰ Spring Security实战一
    • ① Spring Security中的密码加密
    • ② Spring Security四种权限控制方式
    • ③ 关于JWT,以及Spring Security 结合JWT实现登陆验证
      • ① jwt 的组成
      • ② Spring Security 结合JWT登陆验证的流程
        • ① 提前准备,写一个Result返回结果集
        • ② 提前准备,写一个JWT工具类
        • ③ 写LoginSuccessHandler、LoginFailureHandler
        • ④ 验证码相关的配置
          • ① 验证码配置类
          • ② 验证码的controller,返回给前端验证码图片
          • ③ 验证码的filter
        • ⑤ 继承BasicAuthenticationFilter,实现用户验证
        • ⑥ 认证失败的JwtAuthenticationEntryPoint(用户未登录处理类)
        • ⑦ 暂无权限处理类(AccessDeniedHandler,状态码403)
        • ⑧ 登出处理器LogoutSuccessHandler
        • ⑨ 自定义AccountUser类实现UserDetails(拓展原有的UserDetails)
        • ⑩ 实现UserDetailsService,用于和数据库比对
        • ⑩① 密码的加密解密
        • ⑩② SecurityConfig配置类
    • ④ 回顾一下Security的登陆流程

Ⅰ Spring Security实战一

① Spring Security中的密码加密


Spring Security处理密码加密的几种方式
在这里插入图片描述

官方推荐使用BCryptPasswordEncoder

使用方法:

  1. 配置密码加密的方式
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//对跨域请求伪造进行防护---->csrf:利用用户带有登录状态的cookie进行攻击的手段.csrf().disable();}//配置采用哪种密码加密算法@Beanpublic PasswordEncoder passwordEncoder() {//不使用密码加密//return NoOpPasswordEncoder.getInstance();//使用默认的BCryptPasswordEncoder加密方案return new BCryptPasswordEncoder();//strength=10,即密钥的迭代次数(strength取值在4~31之间,默认为10)//return new BCryptPasswordEncoder(10);//利用工厂类PasswordEncoderFactories实现,工厂类内部采用的是委派密码编码方案.//return PasswordEncoderFactories.createDelegatingPasswordEncoder();}}
  1. 使用
    //对密码进行加密user.setPassword(passwordEncoder.encode(user.getPassword()));

② Spring Security四种权限控制方式

Spring Security 的认证方式有 认证+授权。我们授权的时候,不仅可以使用默认的授权,还可以自定义授权。

在这里插入图片描、述

使用说明:

  1. 利用Ant表达式(主要是在配置类中SecurityConfig中使用)
    在这里插入图片描述
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER").antMatchers("/visitor/**").permitAll().anyRequest().authenticated().and().formLogin().permitAll().and()//对跨域请求伪造进行防护---->csrf:利用用户带有登录状态的cookie进行攻击的手段.csrf().disable();
}
  1. 利用授权注解结合SpEl表达式实现权限控制
    在这里插入图片描述
@RestController
public class UserController {@Secured({"ROLE_USER"})//@PreAuthorize("principal.username.equals('user')")@GetMapping("/user/hello")public String helloUser() {return "hello, user";}@PreAuthorize("hasRole('ADMIN')")@GetMapping("/admin/hello")public String helloAdmin() {return "hello, admin";}@PreAuthorize("#age>100")@GetMapping("/age")public String getAge(@RequestParam("age") Integer age) {return String.valueOf(age);}@GetMapping("/visitor/hello")public String helloVisitor() {return "hello, visitor";}}
  1. 利用过滤器注解实现权限控制
    在这里插入图片描述
@RestController
public class FilterController {/*** 只返回结果中id为偶数的user元素。* filterObject是@PreFilter和@PostFilter中的一个内置表达式,表示集合中的当前对象。*/@PostFilter("filterObject.id%2==0")@GetMapping("/users")public List<User> getAllUser() {List<User> users = new ArrayList<>();for (int i = 0; i < 10; i++) {users.add(new User(i, "yyg-" + i));}return users;}}
  1. 利用动态权限实现权限控制

我们一般会使用标准的RABC进行权限控制,Spring Security中的动态权限,主要是通过重写拦截器和决策器来进行实现,一般满足不了我们的需求。

③ 关于JWT,以及Spring Security 结合JWT实现登陆验证

① jwt 的组成

在这里插入图片描述

② Spring Security 结合JWT登陆验证的流程

在这里插入图片描述

① 提前准备,写一个Result返回结果集
@Data
public class Result implements Serializable {private int code;private String msg;private Object data;public static Result succ(Object data) {return succ(200, "操作成功", data);}public static Result fail(String msg) {return fail(400, msg, null);}public static Result succ (int code, String msg, Object data) {Result result = new Result();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}public static Result fail (int code, String msg, Object data) {Result result = new Result();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}
}
② 提前准备,写一个JWT工具类

该工具类需要有3个功能:生成JWT解析JWT判断JWT是否过期

@Data
@Component
@ConfigurationProperties(prefix = "test.jwt")
public class JwtUtils {private long expire;private String secret;private String header;// 生成JWTpublic String generateToken(String username) {Date nowDate = new Date();Date expireDate = new Date(nowDate.getTime() + 1000 * expire);return Jwts.builder().setHeaderParam("type", "JWT").setSubject(username).setIssuedAt(nowDate).setExpiration(expireDate)    // 7天过期.signWith(SignatureAlgorithm.HS512, secret).compact();}// 解析JWTpublic Claims getClaimsByToken(String jwt) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();} catch (Exception e) {return null;}}// 判断JWT是否过期public boolean isTokenExpired(Claims claims) {return claims.getExpiration().before(new Date());}}

我们可以配置JWT的有效时间和加密算法所需使用的秘钥,以及返回给前端时在Http response的Header中所叫的名字。这种配置项我们需写入application.yml中,然后使用@ConfigurationProperties注解接收,这样能便于我们日后修改配置。
使用@ConfigurationProperties注解可以读取配置文件中的信息,只要在 Bean 上添加上了这个注解,指定好配置文件中的前缀,那么对应的配置文件数据就会自动填充到 Bean 的属性中
application.yml中的配置如下:

test:jwt:header: Authorizationexpire: 604800 # 7天,s为单位secret: test
③ 写LoginSuccessHandler、LoginFailureHandler

因为我们是前后端分离模式,当成功或者失败以后,需要返回JSON 所以需要写这两个handler。用于成功或者失败的返回

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {@AutowiredJwtUtils jwtUtils;@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = httpServletResponse.getOutputStream();// 生成JWT,并放置到请求头中String jwt = jwtUtils.generateToken(authentication.getName());httpServletResponse.setHeader(jwtUtils.getHeader(), jwt);Result result = Result.succ("SuccessLogin");outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = httpServletResponse.getOutputStream();String errorMessage = "用户名或密码错误";Result result;if (e instanceof CaptchaException) {errorMessage = "验证码错误";result = Result.fail(errorMessage);} else {result = Result.fail(errorMessage);}outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}
④ 验证码相关的配置
① 验证码配置类
/*配置验证码的大小,宽度等等
*/
@Configuration
public class KaptchaConfig {@BeanDefaultKaptcha producer() {Properties properties = new Properties();properties.put("kaptcha.border", "no");properties.put("kaptcha.textproducer.font.color", "black");properties.put("kaptcha.textproducer.char.space", "4");properties.put("kaptcha.image.height", "40");properties.put("kaptcha.image.width", "120");properties.put("kaptcha.textproducer.font.size", "30");Config config = new Config(properties);DefaultKaptcha defaultKaptcha = new DefaultKaptcha();defaultKaptcha.setConfig(config);return defaultKaptcha;}}
② 验证码的controller,返回给前端验证码图片
@GetMapping("/captcha")
public Result Captcha() throws IOException {String key = UUID.randomUUID().toString();String code = producer.createText();BufferedImage image = producer.createImage(code);ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ImageIO.write(image, "jpg", outputStream);BASE64Encoder encoder = new BASE64Encoder();String str = "data:image/jpeg;base64,";String base64Img = str + encoder.encode(outputStream.toByteArray());redisUtil.hset(Const.CAPTCHA_KEY, key, code, 120);return Result.succ(MapUtil.builder().put("userKey", key).put("captcherImg", base64Img).build());
}
③ 验证码的filter
  1. 过滤器将来放到验证用户名密码过滤器前端
  2. 需要先判断请求是否是登录请求,若是登录请求,则进行验证码校验。若不是,则直接跳过这个过滤器。
  3. CaptchaFilter继承了OncePerRequestFilter抽象类,该抽象类在每次请求时只执行一次过滤,即它的作用就是保证一次请求只通过一次filter,而不需要重复执行。
@Component
public class CaptchaFilter extends OncePerRequestFilter {@AutowiredRedisUtil redisUtil;@AutowiredLoginFailureHandler loginFailureHandler;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {String url = httpServletRequest.getRequestURI();if ("/login".equals(url) && httpServletRequest.getMethod().equals("POST")) {// 校验验证码try {validate(httpServletRequest);} catch (CaptchaException e) {// 交给认证失败处理器loginFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);}}filterChain.doFilter(httpServletRequest, httpServletResponse);}// 校验验证码逻辑private void validate(HttpServletRequest httpServletRequest) {   String code = httpServletRequest.getParameter("code");String key = httpServletRequest.getParameter("userKey");if (StringUtils.isBlank(code) || StringUtils.isBlank(key)) {throw new CaptchaException("验证码错误");}if (!code.equals(redisUtil.hget(Const.CAPTCHA_KEY, key))) {throw new CaptchaException("验证码错误");}// 若验证码正确,执行以下语句// 一次性使用redisUtil.hdel(Const.CAPTCHA_KEY, key);}
}
⑤ 继承BasicAuthenticationFilter,实现用户验证
  1. login-form(登录表单认证):使用基于表单的用户界面进行认证。用户在登录页面中输入用户名和密码,提交表单后,Spring Security会验证用户凭据并完成认证过程。
  2. httpBasic(基本身份验证):在HTTP请求头中发送用户名和密码进行认证。客户端会在每个请求中添加Authorization头,其中包含Basic认证信息。
  1. UsernamePasswordAuthenticationFilter过滤器用于处理基于表单方式的登录验证。
  2. BasicAuthenticationFilter用于处理基于HTTP Basic方式的登录验证

JwtAuthenticationFilter继承了BasicAuthenticationFilter,该类用于普通http请求进行身份认证,该类有一个重要属性:AuthenticationManager,表示认证管理器,它是一个接口,它的默认实现类是ProviderManager


public class JwtAuthenticationFilter extends BasicAuthenticationFilter {  @AutowiredJwtUtils jwtUtils;@AutowiredUserDetailServiceImpl userDetailService;@AutowiredSysUserService sysUserService;public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String jwt = request.getHeader(jwtUtils.getHeader());// 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的// 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口if (StrUtil.isBlankOrUndefined(jwt)) {     chain.doFilter(request, response);return;}Claims claim = jwtUtils.getClaimsByToken(jwt);if (claim == null) {throw new JwtException("token 异常");}if (jwtUtils.isTokenExpired(claim)) {throw new JwtException("token 已过期");}String username = claim.getSubject();// 获取用户的权限等信息SysUser sysUser = sysUserService.getByUsername(username);// 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getId()));SecurityContextHolder.getContext().setAuthentication(token);chain.doFilter(request, response);}
}
⑥ 认证失败的JwtAuthenticationEntryPoint(用户未登录处理类)

当BasicAuthenticationFilter认证失败的时候会进入AuthenticationEntryPoint,我们定义JWT认证失败处理器JwtAuthenticationEntryPoint,使其实现AuthenticationEntryPoint接口

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=UTF-8");httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);ServletOutputStream outputStream = httpServletResponse.getOutputStream();Result result = Result.fail("请先登录");outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}
⑦ 暂无权限处理类(AccessDeniedHandler,状态码403)
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {httpServletResponse.setContentType("application/json;charset=UTF-8");httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);ServletOutputStream outputStream = httpServletResponse.getOutputStream();Result result = Result.fail(e.getMessage());outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}
⑧ 登出处理器LogoutSuccessHandler
@Component
public class JWTLogoutSuccessHandler implements LogoutSuccessHandler {@AutowiredJwtUtils jwtUtils;@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {if (authentication != null) {new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);}httpServletResponse.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = httpServletResponse.getOutputStream();httpServletResponse.setHeader(jwtUtils.getHeader(), "");Result result = Result.succ("SuccessLogout");outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}
⑨ 自定义AccountUser类实现UserDetails(拓展原有的UserDetails)
public class AccountUser implements UserDetails {private Long userId;private static final long serialVersionUID = 540L;private static final Log logger = LogFactory.getLog(User.class);private String password;private final String username;private final Collection<? extends GrantedAuthority> authorities;private final boolean accountNonExpired;private final boolean accountNonLocked;private final boolean credentialsNonExpired;private final boolean enabled;public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {this(userId, username, password, true, true, true, true, authorities);}public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");this.userId = userId;this.username = username;this.password = password;this.enabled = enabled;this.accountNonExpired = accountNonExpired;this.credentialsNonExpired = credentialsNonExpired;this.accountNonLocked = accountNonLocked;this.authorities = authorities;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return this.accountNonExpired;}@Overridepublic boolean isAccountNonLocked() {return this.accountNonLocked;}@Overridepublic boolean isCredentialsNonExpired() {return this.credentialsNonExpired;}@Overridepublic boolean isEnabled() {return this.enabled;}
}
⑩ 实现UserDetailsService,用于和数据库比对
@Service
public class UserDetailServiceImpl implements UserDetailsService {@AutowiredSysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserService.getByUsername(username);if (sysUser == null) {throw new UsernameNotFoundException("用户名或密码错误");}return new AccountUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));}
}
⑩① 密码的加密解密
@NoArgsConstructor
public class PasswordEncoder extends BCryptPasswordEncoder {@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {// 接收到的前端的密码String pwd = rawPassword.toString();// 进行rsa解密try {pwd = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, pwd);} catch (Exception e) {throw new BadCredentialsException(e.getMessage());}if (encodedPassword != null && encodedPassword.length() != 0) {return BCrypt.checkpw(pwd, encodedPassword);} else {return false;}}
}
⑩② SecurityConfig配置类
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredLoginFailureHandler loginFailureHandler;@AutowiredLoginSuccessHandler loginSuccessHandler;@AutowiredCaptchaFilter captchaFilter;@AutowiredJwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;@AutowiredJwtAccessDeniedHandler jwtAccessDeniedHandler;@AutowiredUserDetailServiceImpl userDetailService;@AutowiredJWTLogoutSuccessHandler jwtLogoutSuccessHandler;@BeanJwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());return jwtAuthenticationFilter;}private static final String[] URL_WHITELIST = {"/login","/logout","/captcha","/favicon.ico"};@BeanPasswordEncoder PasswordEncoder() {return new PasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable()// 登录配置.httpBasic().successHandler(loginSuccessHandler).failureHandler(loginFailureHandler).and().logout().logoutSuccessHandler(jwtLogoutSuccessHandler)// 禁用session.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 配置拦截规则.and().authorizeRequests().antMatchers(URL_WHITELIST).permitAll().anyRequest().authenticated()// 异常处理器.and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).accessDeniedHandler(jwtAccessDeniedHandler)// 配置自定义的过滤器.and().addFilter(jwtAuthenticationFilter())// 验证码过滤器放在UsernamePassword过滤器之前.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailService);}
}

比较好的代码推荐
https://zhuanlan.zhihu.com/p/585835490

好文推荐

④ 回顾一下Security的登陆流程


在这里插入图片描述

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

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

相关文章

社交网络分析4(下):社交网络链路预测分析、LightGBM框架、LLSLP方法(LightGBM 堆叠链路预测)、堆叠泛化 、社交网络链路预测分析的挑战

社交网络分析4 写在最前面LightGBMLightGBM简介GBDT的核心概念和应用LightGBM的特点LightGBM与GBDT的比较 LightGBM的原理与技术GBDT的传统算法LightGBM的创新算法 GOSS&#xff08;Gradient-based One-Side Sampling&#xff09;算法解析概念和工作原理算法的逻辑基础GOSS算法…

app上架-您的应用在运行时,未同步告知权限申请的使用目的,向用户索取(相机)等权限,不符合华为应用市场审核标准。

上架提示 您的应用在运行时&#xff0c;未同步告知权限申请的使用目的&#xff0c;向用户索取&#xff08;相机&#xff09;等权限&#xff0c;不符合华为应用市场审核标准。 测试步骤&#xff1a;管理-添加-点击二维码&#xff0c;申请相机权限 修改建议&#xff1a;APP在调…

PyQt6 QFileDialog文件对话框控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计49条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

微店商品API:电商的实时数据利器

一、引言 随着电商行业的快速发展&#xff0c;越来越多的消费者选择通过电商平台进行购物。微店作为电商领域的一种新型模式&#xff0c;凭借其便捷性和个性化服务&#xff0c;吸引了大量用户。为了满足用户对商品信息的快速获取需求&#xff0c;微店提供了商品详情API接口。本…

Spark编程实验二:RDD编程初级实践

目录 一、目的与要求 二、实验内容 三、实验步骤 1、pyspark交互式编程 2、编写独立应用程序实现数据去重 3、编写独立应用程序实现求平均值问题 4、三个综合实例 四、结果分析与实验体会 一、目的与要求 1、熟悉Spark的RDD基本操作及键值对操作&#xff1b; 2、熟悉使…

如何记录函数递归的次数----静态局部变量的使用

记录函数递归的次数,不像是其他普通的函数,只需要一个简单的局部变量作为计数器,每次就好了,函数递归是不断地调用函数,换言之,如果你将一个局部变量定义在函数的内部,那么每次递归都会创建一个这样的变量,每次的值都会初始化,这样也就达不到记录递归次数的目的. 为了解决这个…

极狐GitLab DevSecOps 之容器镜像安全扫描

容器镜像安全 现状 最近某银行遭受供应链攻击的事件传的沸沸扬扬&#xff0c;安全又双叒叕进入了人们的视野。安全确实是一个非常重要&#xff0c;但是又最容易被忽略的话题。但是现在到了一个不得不人人重视安全&#xff0c;人人为安全负责的时代。尤其以现在非常火爆的云原…

【LeetCode: 2276. 统计区间中的整数数目 | 线段树】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

龙芯loongarch64安装numpy报错“No module named ‘numpy.core._multiarray_umath‘”

前言 在之前编译安装Python3.8的文章中说明了,龙芯仓库的很多包都有问题,安装之后很多无法使用,比如安装numpy后,就会出现“No module named numpy.core._multiarray_umath” 问题复现 配置pip源 vim /etc/pip.conf 复制下面的内容并保存 [global] timeout = 60 index-url…

抖店怎么快速起店?不掺杂汤汤水水,全是干货!

我是电商珠珠 我做抖店也已经有三年的时间了&#xff0c;团队也从原来的几人扩大到了70。对于抖店的玩法已经完全摸透熟通&#xff0c;在做店的同时也会带着学生一起做店&#xff0c;他们经常问的问题就是抖店怎么快速起店。 今天&#xff0c;我就来给大家做个分享。根据我的…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Text文本组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之文本组件 一、操作环境 操作系统: Windows 10 专业版 IDE:DevEco Studio 3.1 SDK:HarmonyOS 3.1 二、文本组件 Text 是显示文本的基础组件之一&#xff0c;它可以包含子组件 Span &…

Python 自动化之收发邮件(一)

imapclient / smtplib 收发邮件 文章目录 imapclient / smtplib 收发邮件前言一、基本内容二、发送邮件1.整体代码 三、获取邮件1.整体代码 总结 前言 简单给大家写个如何用Python进行发邮件和查看邮件教程&#xff0c;希望对各位有所帮助。 一、基本内容 本文主要分为两部分…