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

小菜家教平台(三):基于SpringBoot+Vue打造一站式学习管理系统

目录

前言

今日进度

详细过程

相关知识点

前言

昨天重构了数据库并实现了登录功能,今天继续进行开发,创作不易,请多多支持~

今日进度

添加过滤器、实现登出功能、实现用户授权功能校验

详细过程

一、添加过滤器

自定义过滤器作用:自定义的 JWT 认证过滤器,用于解析请求头中的 token,验证用户身份,并将用户信息存入 SecurityContextHolder,从而支持 Spring Security 的认证和授权功能。

package com.example.familyeducation.utils.filter;import com.example.familyeducation.entity.LoginUser;
import com.example.familyeducation.utils.JwtUtil;
import com.example.familyeducation.utils.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;import static com.example.familyeducation.utils.constants.RedisConstants.LOGIN_USER_KEY;/*** @ClassDescription:自定义过滤器,获取请求头中的token并解析* @Author:小菜* @Create:2024/11/6 10:34**/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Resourceprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//1.获取请求头中的tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//2.token为空,直接放行filterChain.doFilter(request, response);return;}//3.token不为空//3.1解析token中的idString id;try {Claims claims = JwtUtil.parseJWT(token);id = claims.getSubject();} catch (Exception e) {//TODO 使用统一异常类封装throw new RuntimeException("token非法");}//4.根据id从redis中获取用户信息String key = LOGIN_USER_KEY + id;LoginUser loginUser = redisCache.getCacheObject(key);if(Objects.isNull(loginUser)){throw new RuntimeException("redis中数据为空,用户未登录");}//5.将用户信息存入SecurityContextHolder//TODO获取当前用户权限信息封装到Authentication 直接从LoginUser中获取即可//5.1封装用户信息到AuthenticationUsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//5.2将信息存入SecurityContextHolderSecurityContextHolder.getContext().setAuthentication(authenticationToken);//6.放行filterChain.doFilter(request,response);}
}

添加完过滤器后记得去将过滤器添加上

//添加自定义过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

这样访问其他接口时,就会自动解析token并进行对应操作

二、登出功能

在登出功能中我们将数据从Redis中进行删除,那样其他接口就无法访问到这些数据,在过滤器中就会显示用户为登录,实现登出功能

@Overridepublic ResponseResult logout() {//1.从SecurityContextHolder中查找到AuthenticationAuthentication authentication = SecurityContextHolder.getContext().getAuthentication();//2.从Authentication中获取LoginUserLoginUser loginUser  = (LoginUser) authentication.getPrincipal();//3.获取id并从Redis中删除,那样下次再进行过滤时就查询不到Redis中的数据,显示用户未登录Integer userId = loginUser.getUser().getId();redisCache.deleteObject(LOGIN_USER_KEY+userId);return new ResponseResult(200,"成功退出登录");}

三、权限校验

权限校验今天踩了很多坑,因为之前没有接触过这个

 在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

首先,权限校验的思路如下:

  1. 请求先到过滤器,此时的请求中携带token
  2. 在过滤器中解析token得到id并从Redis中取得数据(用户数据和权限信息)
  3. 过滤器将数据存到SecurityContextHolder,这样请求进行过程中就能得到数据,可以来判断是否有权限,若无权限则返回403

首先我们先去配置中配置一下

我们要开启一下配置并指定路径与权限的关系,指定哪些路径需要哪些权限才能访问

package com.example.familyeducation.config;import com.example.familyeducation.utils.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
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
//@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()// 对于登录接口 允许匿名访问.antMatchers("/user/login").anonymous().antMatchers("/hello/**").hasRole("ADMIN")//对于/hello的路径,只有ADMIN权限的用户才能访问.antMatchers("/ok/**").hasAnyRole("ADMIN","USER")//对于/ok的路径,ADMIN和USER权限的用户都可以访问// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//添加自定义过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}}

配置完后,因为登录成功后才能得到用户数据和权限,所以我们要去UserDetailsServiceImpl中完成之前的TODO将用户权限信息添加到Login中

package com.example.familyeducation.service.impl;import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.familyeducation.entity.LoginUser;
import com.example.familyeducation.entity.User;
import com.example.familyeducation.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** @ClassDescription:* @Author:小菜* @Create:2024/11/4 19:06**///这里继承的是security中的一个默认接口,重写其中的查询用户方法
@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询用户信息LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUsername,username);User user = userMapper.selectOne(wrapper);//如果查询不到数据就通过抛出异常来给出提示if(Objects.isNull(user)){throw new RuntimeException("用户名或密码错误");}//TODO根据用户查询权限信息 添加到LoginUser中//上面的user信息中已经包含了权限信息,但是我们还是要单独把权限提出来List<String> list = new ArrayList<>();//这里list就是LoginUser中的redisAuthoritiesString role = user.getRole();if(role.equals("admin")){list.add("ROLE_ADMIN");//注意这里添加自定义权限时要加前缀ROLE_,SpringSecurity会默认根据ROLE_去查找权限} else if (role.equals("teacher")) {list.add("ROLE_TEACHER");}//封装成UserDetails对象返回return new LoginUser(user,list);}
}

同时在LoginUser中我们要进行authorities的管理并编写getAuthorities()方法保证过滤器能获取到用户权限

package com.example.familyeducation.entity;import com.alibaba.fastjson.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
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;@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {private User user;private List<String> redisAuthorities;public LoginUser(User user, List<String> redisAuthorities) {this.user=user;this.redisAuthorities=redisAuthorities;}@JSONField(serialize = false)//保证该集合不被序列化private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!=null){return authorities;}else{authorities = redisAuthorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;}}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

最后我们使用Apifox进行测试

先登录一下,成功返回了token,同时Redis中保存了用户信息和权限

好,接下来对权限进行测试,hello路径是只有ADMIN能进行访问,而我们当前用户刚好是管理员,所以得到了返回信息,测试成功!

相关知识点

登录功能思路:

  1. 请求先到过滤器,过滤器检查到token为空,直接放行
  2. 放行后请求到controller层,并调用service层的实现类LoginServiceImpl
  3. Service实现类LoginServiceImpl中会对认证信息进行验证,于是调用SpringSecurity中的authenticationManager.authenticate()方法进行检验
  4. 这个方法会自动调用UserDetailsService中的loadUserByUsername进行用户信息验证
  5. 在UserDetailsService中进行用户数据查询,同时将用户信息中的权限进行封装
  6. 最后将用户信息和权限信息封装成LoginUser进行返回到登录实现类LoginServiceImpl中
  7. 实现类LoginServiceImpl根据返回的信息生成token返回前端,并将loginUser存到Redis

实现授权功能思路:

  1. 请求先到过滤器,此时的请求中携带token
  2. 在过滤器中解析token得到id并从Redis中取得数据(用户数据和权限信息)
  3. 过滤器将数据存到SecurityContextHolder,这样请求进行过程中就能得到数据,可以来判断是否有权限,若无权限则返回403

这里有两个坑,一个是权限能获取到,但是保存到Redis中一直是null,另一个是成功保存了权限,但是测试时一直显示403。

我们先解决第一个问题,在封装信息时我们会调用LoginUser中的一个获取权限的方法,这里一开始是定义了一个authorities并直接返回,但是就会有一个问题,当一个新方法调用时,authorities会被重新刷新为null,就导致权限信息一直是null,解决方法是定义一个新的List,并将其保存到Redis中,那样调用新方法,我们在过滤器中获取Redis中的权限,就不会是null了。

private User user;private List<String> redisAuthorities;public LoginUser(User user, List<String> redisAuthorities) {this.user=user;this.redisAuthorities=redisAuthorities;}@JSONField(serialize = false)//保证该集合不被序列化private List<GrantedAuthority> authorities;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(authorities!=null){return authorities;}else{authorities = redisAuthorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return authorities;}}

第二个问题,这个真的给我整无语了,Spring Security 会通过 GrantedAuthority 来验证用户是否拥有特定的权限。例子中,用户访问 /ok/** 时,Spring Security 会检查 SecurityContextHolder.getContext().getAuthentication().getAuthorities() 中是否包含 ROLE_ADMINROLE_USER

  • Spring Security 的 hasRole() 方法默认会自动在角色前加上 ROLE_ 前缀,所以当你配置 .hasRole("ADMIN") 时,实际上是在检查用户是否有 ROLE_ADMIN 权限。
  • 如果用户的权限中有 ROLE_ADMINROLE_USER,则通过访问检查,允许访问这个路径,否则拒绝访问。

所以就是在手动封装权限的时候没有加上前缀导致权限信息一直对不上,所以就显示403了。。。

 List<String> list = new ArrayList<>();//这里list就是LoginUser中的redisAuthoritiesString role = user.getRole();if(role.equals("admin")){list.add("ROLE_ADMIN");//注意这里添加自定义权限时要加前缀ROLE_,SpringSecurity会默认根据ROLE_去查找权限} else if (role.equals("teacher")) {list.add("ROLE_TEACHER");}

ok,大概就是这样,如果有帮到你的话,请多多支持哦!你的鼓励就是我最大的动力,我们下篇再见~

相关文章:

小菜家教平台(三):基于SpringBoot+Vue打造一站式学习管理系统

目录 前言 今日进度 详细过程 相关知识点 前言 昨天重构了数据库并实现了登录功能&#xff0c;今天继续进行开发&#xff0c;创作不易&#xff0c;请多多支持~ 今日进度 添加过滤器、实现登出功能、实现用户授权功能校验 详细过程 一、添加过滤器 自定义过滤器作用&…...

ArcGIS/QGIS按掩膜提取或栅格裁剪后栅格数据的值为什么变了?

问题描述&#xff1a; 现有一栅格数据&#xff0c;使用ArcGIS或者QGIS按照矢量边界进行按掩膜提取或者栅格裁剪以后&#xff0c;其值的范围发生了变化&#xff0c;如下&#xff1a; 可以看到&#xff0c;不论是按掩膜提取还是进行栅格裁剪后&#xff0c;其值的范围均与原来栅…...

Linux的基本指令(一)

1.ls指令 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及信息。 常用选项&#xff1a; -a列出目录下的所有文件&#xff0c;包括以 . 开头的隐含文件。 -l列出文件的详细信息 举例&#xff1a; rooti…...

python导入包失败 in <module> import pandas as pd

如果安装不成功就更新一下pip python.exe -m pip install --upgrade pip 再删掉原来的pandas pip uninstall pandas 再安装一次 pip install pandas...

不惧风雨,硬核防护!雷孜LaCie小金刚三防移动硬盘颠覆认知

不惧风雨&#xff0c;硬核防护&#xff01;雷孜LaCie小金刚三防移动硬盘颠覆认知 哈喽小伙伴们好&#xff0c;我是Stark-C~ 说到移动硬盘大家潜意识的认为是一件很娇贵的数码产品&#xff0c;很怕湿&#xff0c;摔不得。所以我们在使用传统移动硬盘的时候不能摔&#xff0c;远…...

Yocto 项目下通过网络更新内核、设备树及模块

Yocto 项目下通过网络更新内核、设备树及模块 前言 在 Yocto 项目的开发过程中&#xff0c;特别是在进行 BSP&#xff08;Board Support Package&#xff09;开发时&#xff0c;经常需要调整特定软件包的版本&#xff0c;修改内核、设备树以及内核模块。然而&#xff0c;每次…...

Scheduled Sampling工作原理【小白记笔记】

Scheduled Sampling&#xff08;计划采样&#xff09;是一种在序列生成任务中用于逐步引导模型的训练策略。该方法最早由 Bengio 等人在 2015 年提出&#xff0c;主要用于解决序列到序列&#xff08;sequence-to-sequence&#xff09;模型中的曝光偏差&#xff08;exposure bia…...

C++:C++的IO流

目录 一.C标准IO流 1.operator bool 二.C文件IO流 1.文件读取 ifstream &#xff08;1&#xff09;ifstream继承istream &#xff08;2&#xff09;ifstream 构造函数 &#xff08;3&#xff09;ifstream&#xff0c;get读取整个文件 &#xff08;4&#xff09;>&g…...

「QT」几何数据类 之 QLine 整型直线类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…...

day58 图论章节刷题Part09(dijkstra(堆优化版)、Bellman_ford 算法)

dijkstra(堆优化版) 朴素版的dijkstra解法的时间复杂度为 O(n^2)&#xff0c;时间复杂度只和 n&#xff08;节点数量&#xff09;有关系。如果n很大的话&#xff0c;可以从边的角度来考虑。因为是稀疏图&#xff0c;从边的角度考虑的话&#xff0c;我们在堆优化算法中最好使用…...

【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】试卷(1)

前言 大家好吖&#xff0c;欢迎来到 YY 滴计算机网络 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 本博客主要内容&#xff0c;收纳了一部门基本的计算机网络题目&#xff0c;供yy应对期中考试复习。大家可以参考 本章是去答案版本。带答案的版本在下…...

智能出行助手:SpringBoot共享汽车管理平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理共享汽车管理系统的相关信息成为必然。开发…...

【月之暗面kimi-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …...

Flink实现实时数据处理

代码如下&#xff1a; #!/usr/bin/python # -*- coding: UTF-8 -*-from pyflink.datastream import StreamExecutionEnvironment from pyflink.table import StreamTableEnvironment, EnvironmentSettings, DataTypes# 初始化执行环境 s_env StreamExecutionEnvironment.get_…...

11.9.2024刷华为

文章目录 HJ31 单词倒排HJ32 密码提取语法知识记录 傻逼OD题目又不全又要收费&#xff0c;看毛线&#xff0c;莫名奇妙 HW这叼机构别搁这儿害人得不得&#xff1f; 我觉得我刷完原来的题目 过一遍华为机考的ED卷出处&#xff0c;就行了 HJ31 单词倒排 游戏本做过了好像 HJ3…...

Chromium 中chrome.system.storage扩展接口定义c++

一、chrome.system.storage 您可以使用 chrome.system.storage API 查询存储设备信息&#xff0c;并在连接和分离可移动存储设备时收到通知。 权限 system.storage 类型 EjectDeviceResultCode 枚举 "success" 移除命令成功执行 - 应用可以提示用户移除设备。…...

【Qt聊天室客户端】登录窗口

1. 验证码 具体实现 登录界面中创建验证码图片空间&#xff0c;并添加到布局管理器中 主要功能概述&#xff08;创建一个verifycodewidget类专门实现验证码操作&#xff09; 详细代码 // 头文件#ifndef VERIFYCODEWIDGET_H #define VERIFYCODEWIDGET_H#include <QWidget>…...

如何显示模型特征权重占比图【数据分析】

可视化模型的特征权重 1、流程 1、导入库: numpy:用于处理数组和矩阵。 matplotlib.pyplot:用于绘图。 sklearn.datasets:用于加载数据集。 sklearn.ensemble.RandomForestClassifier:用于训练随机森林模型。2、加载数据集: 使用load_iris函数加载Iris数据集。3、训练模…...

Ubuntu24安装MySQL

下载deb包&#xff1a; 先更新系统包&#xff1a; sudo apt update sudo apt update -y下载mysql: wget https://dev.mysql.com/get/mysql-apt-config_0.8.17-1_all.deb 安装deb包&#xff1a; sudo dpkg -i mysql-apt-config_0.8.17-1_all.deb目前mysql还没有正式支持Ubun…...

微服务架构面试内容整理-Eureka

Spring Cloud Netflix 是一个为构建基于 Spring Cloud 的微服务应用提供的解决方案,利用 Netflix 的开源组件来实现常见的分布式系统功能。以下是 Spring Cloud Netflix 的一些主要组件和特点: 服务注册与发现:Eureka 是一个 RESTful 服务,用于注册和发现微服务。服务实例在…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...