3.SpringSecurity基于数据库的认证与授权
文章目录
- SpringSecurity基于数据库的认证与授权
- 一、自定义用户信息UserDetails
- 1.1 新建用户信息类UserDetails
- 1.2 UserDetailsService
- 二、基于数据库的认证
- 2.1 连接数据库
- 2.2 获取用户信息
- 2.2.1 获取用户实体类
- 2.2.2 Mapper
- 2.2.3 Service
- 2.3 认证
- 2.3.1 实现UserDetails接口
- 2.3.2 实现UserDetailsService接口
- 2.3.3 安全配置类
- 2.3.4 测试程序
- 2.3.4.1 控制器
- 3.3.4.1 访问
- 三、基于数据库的授权
- 3.1 数据库表
- 3.2 获取权限
- 3.2.1 权限类 SysMenu
- 3.2.2 SysMenuDao
- 3.2.3 SysMenuServiceImpl
- 3.2.4 修改 UserDetailsService
- 3.2.5 修改 UserDetails
- 3.3 测试
- 四、总结
SpringSecurity基于数据库的认证与授权
承接:2.SpringSecurity - 处理器简单说明-CSDN博客
我们之前学习的用户的信息都是配置在代码中,如下段代码所示
/*** 定义一个Bean,用户详情服务接口* <p>* 系统中默认是有这个UserDetailsService的,也就是默认的用户名(user)和默认密码(控制台生成的)* 如果在yaml文件中配置了用户名和密码,那在系统中的就是yaml文件中的信息* <p>* 我们自定义了之后,就会把系统中的UserDetailsService覆盖掉*/
@Configuration
public class MySecurityUserConfig {/*** 根据用户名把用户的详情从数据库中获取出来,封装成用户细节信息UserDetails(包括用户名、密码、用户所拥有的权限)* <p>* UserDetails存储的是用户的用户名、密码、去权限信息*/@Beanpublic UserDetailsService userDetailsService() {
// 用户细节信息,创建两个用户
// 此User是SpringSecurity框架中的public class User implements UserDetails, CredentialsContainerUserDetails user1 = User.builder().username("zhangjingqi-1").password(passwordEncoder().encode("123456"))
// 配置用户角色.roles("student") //角色到系统中会变成权限的,比如这里会变成ROLE_student,ROLE_manager.build();UserDetails user2 = User.builder().username("zhangjingqi-2").password(passwordEncoder().encode("123456"))
// 配置权限.authorities("teacher:query").build();UserDetails user3 = User.builder().username("admin").password(passwordEncoder().encode("123456"))
// 配置权限.authorities("teacher:query","teacher:add","teacher:update","teacher:delete").build();// InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService,其中UserDetailsManager继承UserDetailsServiceInMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();userDetailsManager.createUser(user1);userDetailsManager.createUser(user2);userDetailsManager.createUser(user3);return userDetailsManager;}/*** 配置密码加密器* NoOpPasswordEncoder.getInstance() 此实例表示不加密* BCryptPasswordEncoder() 会加密*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}
但是我们并不希望上面这样写,我们希望把用户的信息存入到数据库
一、自定义用户信息UserDetails
用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图

1.1 新建用户信息类UserDetails
//只有当"accountNonExpired"、“accountNonLocked”、“credentialsNonExpired”、"enabled"都为true时,账户才能使用
//之前我们创建的时候,直接User.builder()创建,之后InMemoryUserDetailsManager对象createUser
public class SecurityUser implements UserDetails {/*** @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {//明文为123456return "$2a$10$KyXAnVcsrLaHMWpd3e2xhe6JmzBi.3AgMhteFq8t8kjxmwL8olEDq";}/*** @return 用户名*/@Overridepublic String getUsername() {return "thomas";}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return true;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return true;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return true;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return true;}
}
1.2 UserDetailsService
/*** 当我们定义了此类后,系统默认的UserDetailsService不会起作用,下面UserServiceImpl会起作用*/
@Service
public class UserServiceImpl implements UserDetailsService {/*** 根据用户名获取用户详情UserDetails*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SecurityUser securityUser= new SecurityUser();//我们自己定义的if(username==null || !username.equals(securityUser.getUsername())){
// SpringSecurity框架中自带的异常throw new UsernameNotFoundException("该用户不存在或用户名不正确");}
// 执行到这里,说明username是没有问题的
// 用户密码对不对,框架会帮我们进行判断return securityUser;}}
二、基于数据库的认证
我们观察到在1.1UserDetails中,我们把用户名和密码是写死的,但是这种情况下是不合理的,包括权限在内,我们都需要从数据库中取出来
将信息从数据库取出来后,可以将信息封装成一个UserDetails类

2.1 连接数据库
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.15</version>
</dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version>
</dependency>
spring:#数据源datasource:#德鲁伊连接池druid:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springsecurity?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: rootmybatis:#SQL映射文件的位置mapper-locations: classpath:mapper/**/*.xml# 指定实体类起别名,(实体类所在的包的包路径,那么包中的所有实体类别名就默认是类名首字母小写)type-aliases-package: com.zhangjingqi.entityconfiguration:#开启驼峰命名法map-underscore-to-camel-case: true#日志功能log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.2 获取用户信息
判断用户是否正确,我们可以从sys_user中取出用户相关信息,将用户的相关信息取出来后封装成一个UserDetails类


2.2.1 获取用户实体类
获取用户信息的实体类,这里不建议SysUser实体类实现UserDetails接口,因为会显得很乱
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SysUser implements Serializable {private static final long serialVersionUID = -5352627792860514242L;private Integer userId;private String username;private String password;private String sex;private String address;private Integer enabled;private Integer accountNoExpired;private Integer credentialsNoExpired;private Integer accountNoLocked;}
2.2.2 Mapper
封装dao层,也就是Mapper层,从数据库中获取用户的信息
@Mapper
public interface SysUserDao {/*** 根据用户名访问用户信息* @param userName 用户名* @return 用户信息*/SysUser getByUserName(@Param("userName") String userName);}
<mapper namespace="com.zhangjingqi.dto.SysUserDao"><select id="getByUserName" resultType="com.zhangjingqi.entity.SysUser">select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_lockedfrom sys_user where username = #{userName};</select></mapper>
2.2.3 Service
@Slf4j
@Service
public class SysUserServiceImpl implements SysUserService {@Autowiredprivate SysUserDao sysUserDao;@Overridepublic SysUser getByUserName(String userName) {return sysUserDao.getByUserName(userName);}
}
2.3 认证
2.3.1 实现UserDetails接口
我们之前是写死的用户信息,但是现在是从数据库中进行获取的
@Data
public class SecurityUser implements UserDetails {private static final long serialVersionUID = -1314948905954698478L;private final SysUser sysUser ;public SecurityUser(SysUser sysUser) {this.sysUser = sysUser;}/*** @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {return sysUser.getPassword();}/*** @return 用户名*/@Overridepublic String getUsername() {return sysUser.getUsername();}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired() != 0;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked() !=0;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired() !=0 ;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return sysUser.getEnabled() !=0 ;}
}
这个地方我们之前是这么写的
2.3.2 实现UserDetailsService接口
@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.从数据库获取用户的详情信息SysUser sysUser = sysUserService.getByUserName(username);if (null == sysUser){
// 这个异常信息是SpringSecurity中封装的throw new UsernameNotFoundException("用户没有找到");}// 2.封装成UserDetails类,SecurityUser类实现了UserDetails接口SecurityUser securityUser = new SecurityUser(sysUser);return securityUser;}
}
我们之前是这么写的
这篇文章会有介绍:1.SpringSecurity -快速入门、加密、基础授权-CSDN博客

2.3.3 安全配置类
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 重写 configure(HttpSecurity http)方法*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//授权http请求.anyRequest()//任何请求.authenticated();//需要验证http.formLogin().permitAll(); //SpringSecurity的表单认证}/*** @return 密码加密器*/@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}}
2.3.4 测试程序
2.3.4.1 控制器
@RestController
@RequestMapping("/student")
public class StudentController {@GetMapping("/query")private String query(){return "query student";}@GetMapping("/add")private String add(){return "add student";}@GetMapping("/delete")private String delete(){return "delete student";}@GetMapping("/update")private String update(){return "export student";}
}
@RestController
@RequestMapping("/teacher")
public class TeacherController {@GetMapping("/query")@PreAuthorize("hasAuthority('teacher:query')")//预授权public String queryInfo() {return "teacher query";}}
3.3.4.1 访问
用下面这个人的信息进行登录

访问一下localhost:8080/student/query,发现可以访问

再访问一下localhost:8080/teacher/query,发现是不能访问的,原因是thomas用户没有/teacher/query路径的权限

三、基于数据库的授权
3.1 数据库表
首先我们很明确的就是用户和角色的关系是多对多的
用户表


角色表


用户与角色关联表

权限表


权限与角色关联表


3.2 获取权限
3.2.1 权限类 SysMenu
@Data
public class SysMenu implements Serializable {private static final long serialVersionUID = 597868207552115176L;private Integer id;private Integer pid;private Integer type;private String name;private String code;
}
3.2.2 SysMenuDao
@Mapper
public interface SysMenuDao {List<String> queryPermissionByUserId(@Param("userId") Integer userId);
}
这个地方涉及到三张表,角色用户关联表sys_role_user、角色权限关联表sys_role_menu、权限表sys_menu
我们要实现通过用户获取对应的权限
<mapper namespace="com.zhangjingqi.dto.SysMenuDao"><select id="queryPermissionByUserId" resultType="java.lang.String">SELECT distinct sm.codeFROM sys_role_user sruinner join sys_role_menu srmon sru.rid = srm.ridinner join sys_menu sm on srm.mid = sm.idwhere sru.uid = #{userId}and sm.delete_flag = 0</select>
</mapper>
3.2.3 SysMenuServiceImpl
@Service
public class SysMenuServiceImpl implements SysMenuService {@Autowiredprivate SysMenuDao sysMenuDao;@Overridepublic List<String> queryPermissionByUserId(Integer userId) {return sysMenuDao.queryPermissionByUserId(userId);}
}
3.2.4 修改 UserDetailsService
@Slf4j
@Service
public class SecurityUserDetailsService implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysMenuService sysMenuService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.从数据库获取用户的详情信息SysUser sysUser = sysUserService.getByUserName(username);if (null == sysUser){
// 这个异常信息是SpringSecurity中封装的throw new UsernameNotFoundException("用户没有找到");}// 2.获取该用户的权限List<String> permissionList = sysMenuService.queryPermissionByUserId(sysUser.getUserId());// List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(permission -> new SimpleGrantedAuthority(permission) ).collect(Collectors.toList());
// 将集合的泛型转换成SimpleGrantedAuthority (GrantedAuthority类的子类即可)List<SimpleGrantedAuthority> simpleGrantedAuthorities = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());// 2.封装成UserDetails类,SecurityUser类实现了UserDetails接口SecurityUser securityUser = new SecurityUser(sysUser);securityUser.setSimpleGrantedAuthorities(simpleGrantedAuthorities);return securityUser;}
}
3.2.5 修改 UserDetails
@Data
public class SecurityUser implements UserDetails {private static final long serialVersionUID = -1314948905954698478L;private final SysUser sysUser ;// 用户权限private List<SimpleGrantedAuthority> simpleGrantedAuthorities;public SecurityUser(SysUser sysUser) {this.sysUser = sysUser;}/*** 这个集合中对象的类型必须是GrantedAuthority类或其子类* @return 权限信息*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return simpleGrantedAuthorities;}/*** @return 用户密码,一定是加密后的密码*/@Overridepublic String getPassword() {return sysUser.getPassword();}/*** @return 用户名*/@Overridepublic String getUsername() {return sysUser.getUsername();}/*** @return 账户是否过期*/@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired() != 0;}/*** @return 账户是否被锁住*/@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked() !=0;}/*** @return 凭据是否过期*/@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired() !=0 ;}/*** @return 账户是否可用*/@Overridepublic boolean isEnabled() {return sysUser.getEnabled() !=0 ;}
}
3.3 测试
使用Obama登录

查看obama权限

四、总结
认证与授权,需要我们实现UserDetails用户详情类和UserDetailsService类
相关文章:
3.SpringSecurity基于数据库的认证与授权
文章目录 SpringSecurity基于数据库的认证与授权一、自定义用户信息UserDetails1.1 新建用户信息类UserDetails1.2 UserDetailsService 二、基于数据库的认证2.1 连接数据库2.2 获取用户信息2.2.1 获取用户实体类2.2.2 Mapper2.2.3 Service 2.3 认证2.3.1 实现UserDetails接口2…...
【软件测试】自动化测试selenium
目录 一、什么是自动化测试 二、Selenium介绍 1、Selenium是什么 2、Selenium的原理 三、了解Selenium的常用API 1、webDriver API 1.1、元素定位 1.1.1、CSS选择器 1.1.2、Xpath元素定位 1.1.3、面试题 1.2、操作测试对象 1.3、添加等待 1.4、打印信息 1.5、浏…...
如何解决Google play开发者新注册账号,身份验证的地址证明问题?
我们知道,Google Play应用市场的发展速度惊人,但这两年,为了防止恶意软件的传播,谷歌要求开发者账号需要进行身份验证才能发布应用。 而今年越来越严格,不仅在提审时需要进行电话验证(链接)&am…...
Gin vs Beego: Golang的Web框架之争
前言 Golang作为一门高效且简洁的语言,已经在Web开发领域得到了广泛的应用。Gin和Beego是Golang中两个著名的Web框架,它们都提供了一系列强大的功能,帮助开发者构建高性能的Web应用。本文将对Gin和Beego进行全面的对比,帮助开发者…...
javascript IP地址正则表达式
/^(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2})\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)\.(1[0-9]{2}|2[0-4][0-9]|25[0-5]|(\d){1,2}|0)$/g.test(10.2.35.8) 注: 一定不要把表达式赋值给变量,直接…...
【Bash】记录一个长命令换行的BUG
假设现在我要在terminal执行如下命令跑模型: CUDA_VISIBLE_DEVICES6 python finetune.py -c configs/quantized/resnet32_cifar100_finetune.yml --model resnet32 --data-dir ~/datasets --apex-amp --initial-checkpoint /home/zwx/projects/hawq/resnet32.pth.t…...
【.net core】yisha框架imageupload组件多图上传修改
框架\wwwroot\lib\imageupload\1.0\js路径下imgup.js文件,参照旧版本代码和修改代码修改 (function ($) {"use strict";var deleteParent;var deleteDisplay none;var defaults {fileType: ["jpg", "png", "bmp", "…...
vscode markdown 使用技巧 -- 如何快速打出一个Tab 或多个空格
背景描述: 我在使用VSCode,这玩意很好用,但是,有一个缺点是,我想使用Tab来做一些对齐,但是我发现在VSCode中,无论是Tab还是多个空格,最终显示出来的都是一个空格 使用代码可以实现打…...
I/O 模型学习笔记【全面理解BIO/NIO/AIO】
文章目录 I/O 模型什么是 I/O 模型Java支持3种I/O模型BIO(Blocking I/O)NIO(Non-blocking I/O)AIO(Asynchronous I/O) BIO、NIO、AIO适用场景分析 java BIOJava BIO 基本介绍Java BIO 编程流程一个栗子实现…...
【Python学习笔记】字符编码
1. 字符串编码 Python3语言里面的字符串对象是unicode字符串,在内存中实际存储时,使用的是 UTF16 编码。但通常不会将UTF16编码的内容写到磁盘或者在网络进行传输, 因为utf16编码比较浪费空间。特别是如果文字信息基本都是英文符号的情况下&…...
华为昇腾NPU卡 大模型LLM ChatGLM2模型推理使用
参考:https://gitee.com/mindspore/mindformers/blob/dev/docs/model_cards/glm2.md#chatglm2-6b 1、安装环境: 昇腾NPU卡对应英伟达GPU卡,CANN对应CUDA底层; mindspore对应pytorch;mindformers对应transformers 本…...
Git 拉取远程更新报错
报错内容如下: cannot lock ref refs/remotes/origin/bugfix/bug: refs/remotes/origin/bugfix 已存在,无法创建 refs/remotes/origin/bugfix/bug 来自 gitlab.zhangyue-inc.com:dejian_ios/iReaderDejian! [新分支] bugfix/bug -> ori…...
腾讯云国际站服务器端口开放失败怎么办?
腾讯云服务器是腾讯公司推出的一种云服务,用户能够经过这种方式在互联网上进行数据存储和计算。然而,用户在运用腾讯云服务器时或许会遇到各种问题,其间端口敞开失利是一个常见问题。本文将具体介绍如何解决腾讯云服务器端口敞开失利的问题。…...
一句话解释什么是出口IP
出口 IP 是指从本地网络连接到公共互联网时所使用的 IP 地址。这个 IP 地址是由 Internet 服务提供商(ISP)分配给你的,它可以用来标识你的网络流量的来源。如果你使用的是 NAT(网络地址转换)技术,则在 NAT 设备内部会进行地址转换,使得多个设备可以共享同一个公共 IP 地…...
深入理解强化学习——强化学习的历史:试错学习
分类目录:《深入理解强化学习》总目录 让我们现在回到另一条通向现代强化学习领域的主线上,它的核心则是试错学习思想。我们在这里只对要点做概述,《深入理解强化学习》系列后面的文章会更详细地讨论这个主题。根据美国心理学家R.S.woodworth…...
分享一个用HTML、CSS和jQuery构建的漂亮的登录注册界面
作为一个前端开发人员,我们经常需要构建用户的登录和注册界面。一个漂亮、用户友好的登录注册界面对于提升用户体验和网站形象至关重要。以下我们使用HTML、CSS和jQuery来做一个漂亮的登录注册界面。 首先,我们需要创建一个html文档,定义登录…...
Java学习 习题 1.
一、 1.2. 3. 4. 5. 二、 1. 2. 3. 4. 5. 6. 7. 8....
第六节——Vue中的事件
一、定义事件 Vue 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同 使用修饰符(v-on:的缩写)事件名的方式 给dom添加事件后面跟方法名,方法名可以直接加括号如click"add()"里面进行传参。对应的事件处理函…...
设置GridView单选
/// <summary> /// 设置GridView单选 /// </summary> /// <param name"view"></param> /// <param name"selectCaption"></param> public static void SetGridViewSingleSel…...
[Python从零到壹] 七十二.图像识别及经典案例篇之OpenGL入门及绘制基本图形和3D图
十月太忙,还是写一篇吧!祝大家1024节日快乐O(∩_∩)O 欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解,真心想把自己近十年的编程经验分享给大家,希…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
JavaScript 标签加载
目录 JavaScript 标签加载script 标签的 async 和 defer 属性,分别代表什么,有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...
【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...
更新 Docker 容器中的某一个文件
🔄 如何更新 Docker 容器中的某一个文件 以下是几种在 Docker 中更新单个文件的常用方法,适用于不同场景。 ✅ 方法一:使用 docker cp 拷贝文件到容器中(最简单) 🧰 命令格式: docker cp <…...
【R语言编程——数据调用】
这里写自定义目录标题 可用库及数据集外部数据导入方法查看数据集信息 在R语言中,有多个库支持调用内置数据集或外部数据,包括studentdata等教学或示例数据集。以下是常见的库和方法: 可用库及数据集 openintro库 该库包含多个教学数据集&a…...

