SpringSecurity多表,多端账户登录

本文章对应视频SpringSecurity6多端账号登录,可无限扩展教程,记得三连哦,这对我很重要呢!
温馨提示:视频与文章相辅相成,结合学习效果更强哦!更多视频教程可移步B站【石添的编程哲学】

SpringSecurity实现多表账户登录

需求:针对公司员工,普通用户等各类型用户,将其分别存储在不同的用户表中,基于SpeingSecurity实现用户认证,也就是登录功能

流程

  • 首先做数据库设计
  • 基于SpringBoot创建一个项目
  • 项目中做相关的实现
  • 通过apifox接口测试工具进行测试
  • 分别测试不同用户的登录方法,是否调用了对应的登录逻辑【登录也称为认证】

注意:权限这块并没有涉及,仅仅是用户数据这块

数据表设计

本文先不涉及权限,表设计就是两张用户表

员工表

CREATE TABLE `ums_sys_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户账号',`nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称',`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '用户邮箱',`mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '手机号码',`sex` int DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',`avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '头像地址',`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '密码',`status` int DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',`creator` bigint DEFAULT '1' COMMENT '创建者',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`updater` bigint DEFAULT '1' COMMENT '更新者',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',`deleted` tinyint DEFAULT '0',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='后台用户表';

客户表

CREATE TABLE `ums_site_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户账号',`nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称',`openid` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '微信openid',`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '用户邮箱',`mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '手机号码',`sex` int DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',`avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '头像地址',`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '密码',`status` int DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`updater` bigint DEFAULT '1' COMMENT '更新者',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',`deleted` tinyint DEFAULT '0',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='外部用户表';

创建项目

登录功能,使用非常简单的三层架构,技术选型有:

  • SpringBoot 3.1.X
  • SpringSecurity 6.1.X
  • Mybatis Plus
  • lombok【简化实体类】,可以通过注解生成getter、setter方法,构造方法,toString方法等
  • maven

pom文件

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
</dependencies>

application.yml文件配置

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: stt123456

创建三层架构

Controller

@RestController
@RequestMapping("/auth")
public class AuthController {private final ISysUserService sysUserService;private final ISiteUserService siteUserService;public AuthController(ISysUserService sysUserService, ISiteUserService siteUserService) {this.sysUserService = sysUserService;this.siteUserService = siteUserService;}/*** 后端管理系统登录* 返回值:token*/@PostMapping("sys_login")public String sysLogin(@RequestBody LoginParam loginParam) {return "后台用户登录======》" +sysUserService.sysLogin(loginParam);}@PostMapping("site_login")public String siteLogin(@RequestBody LoginParam loginParam) {return "APP用户登录======》" + siteUserService.siteLogin(loginParam);}}

SysUserServiceImpl

@Service
@Slf4j
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {@Autowired@Qualifier("sysUserAuthenticationManager")private AuthenticationManager authenticationManager;/*** 登录是SpringSecurity实现的,我们就是去告诉SpringSecurity现在要登录* SpringSecirity登录是通过 AuthticationManager 实现的* 将AuthticationManager引入到service中,调用他的认证方法就可以了* @param loginParam* @return*/@Overridepublic String sysLogin(LoginParam loginParam) {// 通过authenticationManager 的认证方法实现登录,该方法需要传入 Authentication 对象 就是一个认证对象// Authenticationl里边存储的就是用户的认证信息,权限,用户名,密码的等信息,其实就是loadUserByUsername方法返回的UserDetailsUsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginParam.getUsername(), loginParam.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);// 获取用户信息SysUser sysUser = (SysUser) authenticate.getPrincipal();log.info("sysUser==========》{}",sysUser);// 返回的是tokenreturn sysUser.getUsername();}
}

SiteUserServiceImpl

@Service
@Slf4j
public class SiteUserServiceImpl extends ServiceImpl<SiteUserMapper, SiteUser> implements ISiteUserService {/*** 将AuthenticationManager注入*/@Autowired@Qualifier("siteUserAuthenticationManager")private AuthenticationManager authenticationManager;@Overridepublic String siteLogin(LoginParam loginParam) {UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginParam.getMobile(), loginParam.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);// 强转为用户类型SiteUser siteUser = (SiteUser) authenticate.getPrincipal();log.info("siteUser===========>{}",siteUser);return siteUser.getUsername();}
}

实现login功能

项目中引入SpringSecurity,SpringSecurity在实现用户登录【认证】时需要使用到两个接口

  • UserDetailsService:是一个接口 ,提供了一个方法loadUserByUsername();
  • UserDetails:是一个接口,用来存储用户权限,状态【是否禁用,超时等】

通过UserDetailsService查询用户,将用户信息放到UserDetails中,剩下的就交给SpringSecurity的AuthenticationManager做判断,判断用户是否允许登录

分两步走

创建UserDetailsService接口实现类

查询用户,分别为客户和用后台系统用户创建对应的查询用户的实现类

// 系统用户的DetailsService
@Service
public class SysUserDetailsService implements UserDetailsService {private final SysUserMapper sysUserMapper;public SysUserDetailsService(SysUserMapper sysUserMapper) {this.sysUserMapper = sysUserMapper;}/*** 此方法从数据库中查询用户* 返回一个 UserDetails*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {log.info("后台系统用户登录=============》");// 根据用户名查询用户SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));// 有权限的话,需要查询该用户对应的权限if(sysUser == null) {throw new UsernameNotFoundException("用户或密码不正确");}return sysUser;}
}
// APP用户的DetailsService
@Slf4j
public class SiteUserDetailsService implements UserDetailsService {private final SiteUserMapper siteUserMapper;public SiteUserDetailsService(SiteUserMapper siteUserMapper) {this.siteUserMapper = siteUserMapper;}@Overridepublic UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {log.info("APP用户登录===================》");SiteUser siteUser = siteUserMapper.selectOne(new LambdaQueryWrapper<SiteUser>().eq(SiteUser::getMobile, mobile));if(siteUser == null) {throw new UsernameNotFoundException("用户名或密码错误!");}return siteUser;}
}

创建UserDetails接口实现类

存储用户信息,同样的创建两个实现类,存储不同的用户信息,再实体类上直接修改

// 后台管理系统用户类
@TableName("ums_sys_user")
@Data
public class SysUser implements Serializable, UserDetails {private Long id;private String username;private String nickname;private String email;private String mobile;private Integer sex;private String avatar;@JsonIgnoreprivate String password;private Integer status;private Long creator;private Long updater;private String remark;@TableLogicprivate Integer deleted;private LocalDateTime createTime;private LocalDateTime updateTime;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
// APP用户实体类
@Data
@TableName("ums_site_user")
public class SiteUser implements Serializable, UserDetails {private Long id;private String username;private String nickname;private String openid;private String email;private String mobile;private Integer sex;private String avatar;@JsonIgnoreprivate String password;private Integer status;private Long updater;private String remark;@TableLogicprivate Integer deleted;private LocalDateTime createTime;private LocalDateTime updateTime;/*** 权限。现在并没有查询权限* @return*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

关联

将SpringSecurity的AuthenticationManager 【认证管理器,管登录的组件】,与我们写的登录逻辑关联起来【loadUserByUsername方法】,实现方式就是在SpringSecurity的配置类中实现

/*** 现在使用的是SpringSecurity 6.1.5版本,开启SpringSecurity的自定义配置,* 需要使用 @EnableWebSecurity注解,而不再是继承Adpater*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate SysUserDetailsService sysUserDetailsService;@Autowiredprivate SiteUserDetailsService siteUserDetailsService;// 配置SpringSecurity的过滤器链@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {// 设置登录接口放行http.authorizeHttpRequests(auth -> auth.requestMatchers("/auth/sys_login","/auth/site_login").permitAll().anyRequest().authenticated());// 关闭csrfhttp.csrf(csrf -> csrf.disable());return http.build();}// 配置AuthenticationManager,配置两个。一个管理后台用户@Primary@Bean("sysUserAuthenticationManager")public AuthenticationManager sysUserAuthenticationManager(PasswordEncoder passwordEncoder) {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();// 关联UserDetailsServiceauthenticationProvider.setUserDetailsService(sysUserDetailsService);// 关联密码管理器authenticationProvider.setPasswordEncoder(passwordEncoder);return new ProviderManager(authenticationProvider);}// 配置AuthenticationManager,管理APP用户@Bean("siteUserAuthenticationManager")public AuthenticationManager siteUserAuthenticationManager(PasswordEncoder passwordEncoder) {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();// 关联UserDetailsServiceauthenticationProvider.setUserDetailsService(siteUserDetailsService);// 关联密码管理器authenticationProvider.setPasswordEncoder(passwordEncoder);return new ProviderManager(authenticationProvider);}/*** 密码管理器,会将明文密码转换成密文,加密,而且不能解码* @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

密码

需要对数据库中存储的密码进行编码,因为SpringSecurity进行密码匹配时,会对用户输入的密码先编码,再验证,先通过PasswordEncoder生成加密后的密码

@SpringBootTest
public class MyTestApplication {@Autowiredprivate PasswordEncoder passwordEncoder;@Testpublic void test() {// 密码加密String encode = passwordEncoder.encode("123456");System.out.println(encode);}
}

SpringSecurity配置

创建两个AuthenticationManager,一定要设置一个主AuthenticationManager否则将会报错,即在任意一个Bean上添加@Primary注解标记

@Primary
@Bean("sysAuthenticationManager")
public AuthenticationManager sysAuthenticationManager(PasswordEncoder passwordEncoder) {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();authenticationProvider.setUserDetailsService(sysUserDetailsService);authenticationProvider.setPasswordEncoder(passwordEncoder);ProviderManager providerManager = new ProviderManager(authenticationProvider);providerManager.setEraseCredentialsAfterAuthentication(false);return providerManager;
}/*** 外部用户验证管理器* @param passwordEncoder* @return*/
@Bean("siteAuthenticationManager")
public AuthenticationManager siteAuthenticationManager(PasswordEncoder passwordEncoder) {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();authenticationProvider.setUserDetailsService(siteUserDetailsService);authenticationProvider.setPasswordEncoder(passwordEncoder);ProviderManager providerManager = new ProviderManager(authenticationProvider);providerManager.setEraseCredentialsAfterAuthentication(false);return providerManager;
}

数据库配置

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springsecurity?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: stt123456

AuthenticationManager

AuthenticationManager用于定义SpringSecurity如何进行身份认证,之后将认证信息封装在Authentication对象上,设置到SecurityContextHolder上,AuthenticationManager常用的实现是ProviderManager,你也可以对其做自定义实现。

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

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

相关文章

快解析Tplink端口映射如何设置

Tplink作为国内知名路由器品牌&#xff0c;有着广泛的用户群体。使用快解析端口映射是实现内网服务器被外网访问必须要做的设置&#xff0c;很多对网络不懂得小白不知道该到哪里去做&#xff0c;下面我就讲解一下tplink路由器如何做端口映射。 1&#xff1a;访问路由器 &#…

uboot 顶层 Makefile 逐行分析

文章目录 0001-00080009-00180019-00510052-00920093-01070108-01230124-01770178-21350178-01810182-01860187-02020203-02450246-02620263-02720273-03370338-03830384-03870388-04250426-04490450-04740475-04860487-04980499-05340535-05500551-05650566-221822192220-2332…

想半天憋不出几个字?试试AI扩写

大家在写文章时是否也经常这样&#xff1f;想了半天&#xff0c;结果只能写出几个字&#xff0c;但是要求往往又是几百多个字&#xff0c;那么有没有啥工具可以帮我们在原文的基础上扩写一下文章字数&#xff0c;让我们达到字数要求呢&#xff1f; 下面给大家介绍一下如何扩写文…

Django开发实战之定制管理后台界面及知识梳理(下)

接上一篇&#xff1a;Django开发实战之定制管理后台界面及知识梳理&#xff08;中&#xff09; 1、前台设置 1、隐藏路由 当你输入一个错误地址时&#xff0c;可以看到这样的报错&#xff1a; 从这样的报错中&#xff0c;我们可以看到&#xff0c;这个报错页面暴漏了路由&a…

江西省生态环境工程技术人员职称申报条件

江西省生态环境工程技术人员职称申报条件评审文件链接江西省生态环境厅 其它通知、公示信息 关于印发《江西省生态环境工程技术人员职称申报条件&#xff08;试行&#xff09;》的通知评审工作的通知江西省生态环境厅 其它通知、公示信息 江西省生态环境厅关于做好2024年工程系…

到底什么是离岸账户?

离岸账户&#xff0c;顾名思义就是银行为非居民开设的账户&#xff0c;举个例子&#xff0c;英国公司在香港汇丰开设账户或者香港公司在大陆的平安银行开设账户&#xff0c;就属于离岸账户&#xff0c;如果是中国居民想要开设离岸账户&#xff0c;只能先注册一个其属地的离岸公…

FullCalendar日历组件集成实战(1)

背景 有一些应用系统或应用功能&#xff0c;如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件&#xff0c;但功能比较简单&#xff0c;用来做数据展现勉强可用。但如果需要进行复杂的数据展示&#xff0c;以及互动操作如通过点击添加事件&#xff0…

python数据可视化:从n个点中挑选m组3个点绘制m个三角形matplotlib.pyplot.triplot()

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 python数据可视化&#xff1a; 从n个点中挑选m组3个点 绘制m个三角形 matplotlib.pyplot.triplot() [太阳]选择题 以下关于matplotlib.pyplot.triplot()函数说法正确的是&#xff1f; impor…

Linux---windows 机器和远端的 Linux 机器如何通过 XShell 传输文件

一、关于rzsz 这个工具用于 windows 机器和远端的 Linux 机器通过 Xshell 传输文件. 二、下载rzsz软件 用root输入命令&#xff1a; sudo yum install -y lrzsz下载完成&#xff1a; 三、如何传输 有图形化界面 1、从Windows机器传输给远端Linux机器 ① 直接拖拽 直接将…

微软如何打造数字零售力航母系列科普10 - 什么是Azure Databricks?

什么是Azure Databricks&#xff1f; 目录 一、数据智能平台是如何工作的&#xff1f; 二、Azure Databricks的用途是什么&#xff1f; 三、与开源的托管集成 四、工具和程序访问 五、Azure Databricks如何与Azure协同工作&#xff1f; 六、Azure Databricks的常见用例是…

JavaSE——集合框架一(2/7)-Collection集合的遍历方式-迭代器、增强for循环、Lambda、案例

目录 Collection的遍历方式 迭代器 增强for循环&#xff08;foreach&#xff09; Lambda表达式遍历集合 案例 需求与分析 代码部分 运行结果 Collection的遍历方式 迭代器 选代器是用来遍历集合的专用方式&#xff08;数组没有选代器&#xff09;&#xff0c;在Java中…

【Spring Boot】 深入理解Spring Boot拦截器:自定义设计与实现全攻略

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring Boot】 深入理解Spring Boot拦截器&#xff1a;自定义设计与实现全攻略 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 SpringBoot统⼀功能处理一…

第十五节:贪心算法(下)

一 、 贪心算法的解题套路实战一&#xff08;最多的会议宣讲场次&#xff09; 1.1 描述 一些项目要占用一个会议室宣讲&#xff0c;会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间 你来安排宣讲的日程&#xff0c;要求会议室进行的宣讲的场次最多。…

Flutter 中的 FloatingActionButton 小部件:全面指南

Flutter 中的 FloatingActionButton 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;FloatingActionButton 是一个圆形的浮动操作按钮&#xff0c;通常用于在移动应用中提供一个快速的、突出的操作入口。它不仅外观醒目&#xff0c;而且可以提升用户体验&#xff0c;因…

HTML静态网页成品作业(HTML+CSS)——动漫哆啦A梦网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有3个页面。 二、作品演示 三、代…

打印kafka最近的消息

使用 kafka-run-class 指令&#xff0c;获取topic的最小offset和最大offset #查看各个分区的最小offset(这个意思就是&#xff0c;这个offset之前的消息已经被清除了&#xff0c;现在consumer是从这个offset之后开始消费): ./kafka-run-class.sh kafka.tools.GetOffsetShell …

Mongo关联查询两张表中分别满足某些条件的记录

如果是在mysql里面&#xff0c;这个查起来就很方便&#xff0c;但是&#xff0c;在mongo里面的话&#xff0c;查询起来就没这么方便了。 如果使用付费版的Studio 3T工具的话&#xff0c;也可以像使用mysql一样查询mongo数据&#xff0c;但是免费版不支持sql的用法&#xff0c;只…

rust类型和变量(二)

基础知识 Rust中的变量基础知识 1.在Rust中&#xff0c;使用Iet关键字来声明变量 2.Rust支持类型推导&#xff0c;但你也可以显式指定变量的类型&#xff1a; Ietx:i325;/显式指定x的类型为i32 3.变量名蛇形命名法(Snake Case),i 而枚举和结构体命名使用帕斯卡命名法(Pasca|Ca…

【联邦学习——手动搭建简易联邦学习】

1. 目的 用于记录自己在手写联邦学习相关实验时碰到的一些问题&#xff0c;方便自己进行回顾。 2. 代码 2.1 本地模型计算梯度更新 # 比较训练前后的参数变化 def compare_weights(new_model, old_model):weight_updates {}for layer_name, params in new_model.state_dic…

Python---Pandas万字总结(1)

Pandas基础-1 Pandas 是 一个强大的分析结构化数据的工具集。Pandas 以 NumPy 为基础&#xff08;实现数据存储和运算&#xff09;&#xff0c;提供了专门用于数据分析的类型、方法和函数&#xff0c;对数据分析和数据挖掘提供了很好的支持&#xff1b;同时 pandas 还可以跟数…