Spring Security 授权

基于request的授权

HttpSecurity 权限配置

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(authorize -> {
            authorize
                    // 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
                    .requestMatchers("/users/get").hasAuthority("admin")
                    // 放行请求:针对含有 tourist 权限的用户放行 /get 接口
                    .requestMatchers("/test/auth").hasAuthority("tourist")
                    // 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
                    .requestMatchers("/users/list").hasRole("ROLE_admin")
                    // 对所有用户放行
                    .requestMatchers("/login", "/test/**").permitAll()
                    // 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
                    .anyRequest()
                    // 以认证的会自动授权
                    .authenticated();
        });
        http.exceptionHandling(exc -> {
                    // 用户未认证
                    exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
                    // 请求被拒绝
                    exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
                }
        );

        // 开启跨域请求
        http.cors(withDefaults());
        return http.build();
    }

在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:" + username);
        // 用户名称不唯一,这里的账号名account,不是用户名
        // 这里user实现了Security的UserDetails对象
        UsersDO user = usersMapper.selectByAccount(username);
        if (user == null || StringUtils.isEmpty(user.getPassword())) {
            throw new UsernameNotFoundException(username + ":用户不存在");
        }
        // 获取用户角色列表
        List<RoleDO> list = usersMapper.selectByAccountRole(username);
        user.setList(list);
        return user;
    }

获取用户信息

@Mapper
public interface UsersMapper extends BaseMapper<Users> {

    /**
     * 分页查询
     */
    List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);

    @Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user  where ACCOUNT =#{account}")
    UsersDO selectByAccount(@Param("account") String account);

    @Select("select t1.ACCOUNT ,t2.role_name from  tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account  \n" +
            "left  join tm_role t3  on t2.role_name =t3.role_name  where  t1.ACCOUNT =#{account}")
    List<RoleDO> selectByAccountRole(@Param("account") String username);
    
}

实体类:

@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {

    /**
     * 账号
     */
    private String id;

    /**
     * 账号
     */
    private String account;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;


    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String deleted;

    /**
     * 用户角色列表
     */
    private List<RoleDO> list;

    /**
     * 权限列表
     */
    private Set<GrantedAuthority> authorities =new HashSet<>();

    @Override
    public boolean isEnabled() {
        // 通过逻辑删除字段判断是否启用
        return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
    }


    @Override
    public boolean isAccountNonExpired() {
        //用户是否未过期
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 用户凭证是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 用户是否没有被锁定
        return true;
    }

    /**
     * 获取权限列表,暂时以角色名代表权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 权限列表
        for (RoleDO roleDO : list){
            authorities.add(()->roleDO.getRoleName());
        }
        return authorities;
    }

    /**
     * 设置角色 , 这里参考了 Security 的源码
     * @param roles
     */
    public void roles(String... roles) {
        for (String role : roles) {
            // 这里要加上前缀 “ROLE_”
            Assert.isTrue(!role.startsWith("ROLE_"),
                    () -> role + " cannot start with ROLE_ (it is automatically added)");
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
        }

    }

}

用户登录后,如果没有对应权限,默认会抛出403异常

基于request的授权

HttpSecurity 权限配置

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(authorize -> {
            authorize
                    // 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
                    .requestMatchers("/users/get").hasAuthority("admin")
                    // 放行请求:针对含有 tourist 权限的用户放行 /get 接口
                    .requestMatchers("/test/auth").hasAuthority("tourist")
                    // 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
                    .requestMatchers("/users/list").hasRole("ROLE_admin")
                    // 对所有用户放行
                    .requestMatchers("/login", "/test/**").permitAll()
                    // 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
                    .anyRequest()
                    // 以认证的会自动授权
                    .authenticated();
        });
        http.exceptionHandling(exc -> {
                    // 用户未认证
                    exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
                    // 请求被拒绝
                    exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
                }
        );

        // 开启跨域请求
        http.cors(withDefaults());
        return http.build();
    }

在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:" + username);
        // 用户名称不唯一,这里的账号名account,不是用户名
        // 这里user实现了Security的UserDetails对象
        UsersDO user = usersMapper.selectByAccount(username);
        if (user == null || StringUtils.isEmpty(user.getPassword())) {
            throw new UsernameNotFoundException(username + ":用户不存在");
        }
        // 获取用户角色列表
        List<RoleDO> list = usersMapper.selectByAccountRole(username);
        user.setList(list);
        return user;
    }

获取用户信息

@Mapper
public interface UsersMapper extends BaseMapper<Users> {

    /**
     * 分页查询
     */
    List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);

    @Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user  where ACCOUNT =#{account}")
    UsersDO selectByAccount(@Param("account") String account);

    @Select("select t1.ACCOUNT ,t2.role_name from  tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account  \n" +
            "left  join tm_role t3  on t2.role_name =t3.role_name  where  t1.ACCOUNT =#{account}")
    List<RoleDO> selectByAccountRole(@Param("account") String username);
    
}

实体类:

@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {

    /**
     * 账号
     */
    private String id;

    /**
     * 账号
     */
    private String account;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;


    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String deleted;

    /**
     * 用户角色列表
     */
    private List<RoleDO> list;

    /**
     * 权限列表
     */
    private Set<GrantedAuthority> authorities =new HashSet<>();

    @Override
    public boolean isEnabled() {
        // 通过逻辑删除字段判断是否启用
        return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
    }


    @Override
    public boolean isAccountNonExpired() {
        //用户是否未过期
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 用户凭证是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 用户是否没有被锁定
        return true;
    }

    /**
     * 获取权限列表,暂时以角色名代表权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 权限列表
        for (RoleDO roleDO : list){
            authorities.add(()->roleDO.getRoleName());
        }
        return authorities;
    }

    /**
     * 设置角色 , 这里参考了 Security 的源码
     * @param roles
     */
    public void roles(String... roles) {
        for (String role : roles) {
            // 这里要加上前缀 “ROLE_”
            Assert.isTrue(!role.startsWith("ROLE_"),
                    () -> role + " cannot start with ROLE_ (it is automatically added)");
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
        }

    }

}

用户登录后,如果没有对应权限,默认会抛出403异常

基于request的授权

HttpSecurity 权限配置

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.authorizeHttpRequests(authorize -> {
            authorize
                    // 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
                    .requestMatchers("/users/get").hasAuthority("admin")
                    // 放行请求:针对含有 tourist 权限的用户放行 /get 接口
                    .requestMatchers("/test/auth").hasAuthority("tourist")
                    // 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
                    .requestMatchers("/users/list").hasRole("ROLE_admin")
                    // 对所有用户放行
                    .requestMatchers("/login", "/test/**").permitAll()
                    // 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
                    .anyRequest()
                    // 以认证的会自动授权
                    .authenticated();
        });
        http.exceptionHandling(exc -> {
                    // 用户未认证
                    exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
                    // 请求被拒绝
                    exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
                }
        );

        // 开启跨域请求
        http.cors(withDefaults());
        return http.build();
    }

在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username:" + username);
        // 用户名称不唯一,这里的账号名account,不是用户名
        // 这里user实现了Security的UserDetails对象
        UsersDO user = usersMapper.selectByAccount(username);
        if (user == null || StringUtils.isEmpty(user.getPassword())) {
            throw new UsernameNotFoundException(username + ":用户不存在");
        }
        // 获取用户角色列表
        List<RoleDO> list = usersMapper.selectByAccountRole(username);
        user.setList(list);
        return user;
    }

获取用户信息

@Mapper
public interface UsersMapper extends BaseMapper<Users> {

    /**
     * 分页查询
     */
    List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);

    @Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user  where ACCOUNT =#{account}")
    UsersDO selectByAccount(@Param("account") String account);

    @Select("select t1.ACCOUNT ,t2.role_name from  tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account  \n" +
            "left  join tm_role t3  on t2.role_name =t3.role_name  where  t1.ACCOUNT =#{account}")
    List<RoleDO> selectByAccountRole(@Param("account") String username);
    
}

实体类:

@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {

    /**
     * 账号
     */
    private String id;

    /**
     * 账号
     */
    private String account;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;


    /**
     * 逻辑删除
     */
    @TableLogic
    @TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
    private String deleted;

    /**
     * 用户角色列表
     */
    private List<RoleDO> list;

    /**
     * 权限列表
     */
    private Set<GrantedAuthority> authorities =new HashSet<>();

    @Override
    public boolean isEnabled() {
        // 通过逻辑删除字段判断是否启用
        return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
    }


    @Override
    public boolean isAccountNonExpired() {
        //用户是否未过期
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // 用户凭证是否未过期
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // 用户是否没有被锁定
        return true;
    }

    /**
     * 获取权限列表,暂时以角色名代表权限
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 权限列表
        for (RoleDO roleDO : list){
            authorities.add(()->roleDO.getRoleName());
        }
        return authorities;
    }

    /**
     * 设置角色 , 这里参考了 Security 的源码
     * @param roles
     */
    public void roles(String... roles) {
        for (String role : roles) {
            // 这里要加上前缀 “ROLE_”
            Assert.isTrue(!role.startsWith("ROLE_"),
                    () -> role + " cannot start with ROLE_ (it is automatically added)");
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
        }

    }

}

用户登录后,如果没有对应权限,默认会抛出403异常

请添加图片描述

可以自定义请求被拒绝的返回结果:

@Configuration
@Slf4j
public class MyAuthenticationSuccessHandler implements  AccessDeniedHandler {
        
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.info("请求被拒绝-------------->");
        response.setContentType("application/json;charset=UTF-8");
        Map<String, Object> res = new HashMap<>();
        res.put("code", "-1");
        res.put("message", "请求被拒绝");
        Map<String, Object> data = new HashMap<>();
        res.put("data", data);
        response.getWriter().println(JSONUtil.toJsonStr(res));

    }
    
}

RBAC模型

权限模型

  • RBAC(Role Based Access Controll):基于角色的权限控制,简单来说就是:用户-角色-权限-资源,涉及的表有:
    • 用户(t_user)
    • 用户_角色(t_user_role)【N对N关系需要中间表】
    • 角色(t_role)
    • 角色_权限(t_role_perm)
    • 权限(t_permission)
  • ACL(Access Controll List):基于用户的权限控制,涉及的表有:
    • 用户(t_user)
    • 用户_权限(t_user_perm)
    • 权限(t_permission)

通过 RBAC 获取到用户的具体权限后,再通过 Security 的 用户-权限-资源 来进行权限控制

// 针对某个资源,对某个权限来放行
.requestMatchers("/users/get").hasAuthority("function_id")

基于方法的授权

使用注解,针对某个方法开启授权保护

开启配置:

@Configuration
// 开启spring Security的自定义配置,在springBoot项目中可以省略此注解,因为Security-stater默认开启了自定义配置
@EnableWebSecurity
// 开启基于方法的授权
@EnableMethodSecurity
public class SecurityConfig {

}

给方法增加权限保护

    @PreAuthorize("hasRole('admin')")
    @GetMapping("/apply")
    public BaseResultModel apply(){

        return BaseResultModel.success("授权返回成功");
    }


注解及含义

  • @PostAuthorize 在目标方法执行之后进行权限校验
  • @PostFilter 在目标方法执行之后对返回结果进行过滤
  • @PreAuthorize 在目标方法执行之前进行权限校验
  • @PreFilter 在目标方法执行之前对方法参数进行过滤
  • @Secured 访问目标方法必须具备对应的角色
  • @DenyAll 拒绝所有访问
  • @PermitAll 允许所有访问
  • @RolesAll 访问目标方法必须具备对应的角色

授权的原理分析

  • ConfigAttribute在springsecurity中,用户请求一个资源(通常是一个接口或者Java方法)需要的角色会被封装成一个ConfigAttribute对象,在ConfigAttribute中只有一个getAttribute方法,该方法赶回一个String字符串(角色名称)。一般的角色名称都带有一个ROLE_前缀,投票器AccessDecisionVoter所做的事情,其实就是比较用户所具有的角色和请求某个资源所需要的ConfigAttribute之间的关系。
  • AccessDecisionVoter和AccessDecisionManager都有众多实现类。在AccessDecisionManager中会挨个遍历AccessDecisionVoter,进而决定是否允许用户方法,因而AccessDecisionVoter和AccessDecisionManager俩者关系类似于AuthenticationProvicder和ProviderManager的关系。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

训练营第十一天 | 150. 逆波兰表达式求值

150. 逆波兰表达式求值 做题思路 遇到操作符&#xff0c;出栈&#xff0c;从栈口取出俩元素&#xff1b;遇到数字&#xff0c;入栈 栈的应用场景&#xff1a;相邻元素的消除 逆波兰表达式&#xff1a;即后缀表达式 来自二叉树的后序遍历&#xff1a;左右中 代码细节 class …

有限元中弱形式的一些数学基础

有限元方法在求解PED时&#xff0c;一般先将控制方程转化为等效的若积分形式&#xff0c;本文试图总结一下这一过程的一些数学基础&#xff0c;本文主要从工程的角度出发和理解&#xff0c;不探讨严谨的数学证明过程。 PDE强形式 强形式是PDE及其边界条件的原始形式。求解强…

Java巅峰之路---基础篇---综合练习(面向对象)

目录 文字版格斗游戏 基础版 souf输出语句 进阶版 键盘录入的说明 复杂对象数组练习 需求&#xff1a; 添加和遍历 删除和遍历 修改和遍历 文字版格斗游戏 基础版 格斗游戏&#xff0c;每个游戏角色的姓名&#xff0c;血量&#xff0c;都不相同&#xff0c;在选定人…

[BJDCTF2020]Mark loves cat

黑盒直接扫 dirsearch -u http://bba9a212-64d3-4a16-88b4-3605fe3ef749.node5.buuoj.cn:81/ -w /home/kali/Desktop/dirsearch/db/dicc.txt我们用GitHack拿一下源码 没有的去下载一下&#xff0c;开源代码 cd GitHackpython GitHack.py http://bba9a212-64d3-4a16-88b4-3605…

排序算法3_冒泡排序、快速排序

一、冒泡排序 1.1 冒泡排序定义和思路 冒泡排序的基本思想是&#xff1a;通过相邻两个元素之间的比较和交换&#xff0c;使较大的元素逐渐从前面移向后面&#xff08;升序&#xff09;&#xff0c;就像水底下的气泡一样逐渐向上冒泡&#xff0c;所以被称为“冒泡”排序。  在…

垃圾收集篇

文章目录 垃圾收集算法垃圾的概念对象存活的判断引用计数器法可达性分析算法 算法标记清除算法复制算法标记压缩算法 垃圾收集的相关概念STW安全点安全区域 垃圾收集器重要指标吞吐量停顿时间 垃圾收集器的分类Serial 收集器&#xff1a;串行回收ParNew 收集器&#xff1a;并行…

【可视化大屏系列】Echarts之饼图绘制

本文为个人近期学习总结&#xff0c;若有错误之处&#xff0c;欢迎指出&#xff01; Echarts之饼图绘制 前言1.需求2.实现效果3.大概思路4.代码实现子组件写法父组件写法 5.附加&#xff08;1&#xff09;圆环饼图的绘制&#xff08;2&#xff09;南丁格尔玫瑰饼图A.半径展示数…

新手小白的pytorch学习第三弹-------tensor的基本操作

reshape, view, stacking, squeeze(), unsqueeze(),permute()torch.tensor 和 numpy 的 array切片&#xff0c;张量里面获取元素值随机种子 1 导入torch import torch2 reshape() tensor_A torch.arange(1, 11) tensor_Atensor_A.reshape(2, 5) tensor_A.reshape(2, 5)tenso…

浮点数存储方法(float,double,long double)

前言&#xff1a; 浮点数家族包括float、double、long double 类型。 如果你打出3.14&#xff0c;编译器默认是double类型的。若想让他为float类型&#xff0c;则要在前面加f&#xff1b; 1E10是科学计数法&#xff0c;代表1.010^10 (1) 浮点型如何在内存中存放&#xff1f;…

husky 和 lint-staged 构建代码项目规范

目录 前言 最简单的方法 过 scripts 来解决如果检测工具多&#xff0c;需要多次处理 通过 husky(哈士奇)来解决容易遗忘的问题 1. 安装 2. husky init 3. 试一试​ lint-stadge 只 lint 改动的 1. 安装 2. 修改 package.json 配置 3. 添加 npm 脚本: 4.使用 Husky…

Linux的load(负载)

负载(load)是Linux机器的一个重要指标&#xff0c;直观了反应了机器当前的状态。 在Linux系统中&#xff0c;系统负载是对当前CPU工作量的度量&#xff0c;被定义为特定时间间隔内运行队列中的平均线程数。 Linux的负载高&#xff0c;主要是由于CPU使用、内存使用、10消…

【AI】目标检测算法【R-CNN:Regions with CNN features】

1. 常用目标检测算法介绍 目标检测是计算机视觉领域的一个重要分支&#xff0c;它旨在识别并定位图像中的各种对象。以下是一些流行的目标检测算法&#xff1a; 1.1 二阶段目标检测算法 R-CNN (Regions with CNN features): 通过选择性搜索算法选取候选区域&#xff0c;然后…

【C语言】详解结构体(上)

文章目录 前言1. 结构体类型的含义2.结构体的声明2.1 结构体声明的语法2.2 结构体变量的创建和初始化 3.结构体的特殊声明4. 结构体的自引用5.小结 前言 C语言的数据类型分为内置数据类型和自定义的数据类型。所谓的内置的数据类型可以认为是C语言自带的数据类型&#xff08;c…

【网络安全】基于PHP study的DVWA靶场搭建教程

PHP study的安装本文略过 DVWA安装地址 https://github.com/digininja/DVWA?tabreadme-ov-file将zip文件安装至PHP study的www目录下&#xff1a; 解压&#xff0c;进入config目录中&#xff0c;将.dist后缀删除&#xff1a; 接着打开该php文件&#xff0c;将用户名、密码改为…

程序包不存在【java: 程序包org.springframework.boot不存在】

1、问题提示&#xff1a;java: 程序包org.springframework.boot不存在 注意&#xff1a;已经下载好了程序包&#xff0c;就是提示不存在 2、解决办法

Qt5离线安装包无法下载问题解决办法

Qt5离线安装包无法下载问题解决办法 文章目录 Qt5离线安装包无法下载问题解决办法1、前言2、Qt5安装包下载办法 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;Qt开发经验 &#x1f448; 1、前言 Qt安装包官方下载地址 Qt5离线安装包目前在国内已经被墙…

链表的回文结构(链表的中间节点+反转链表)

链表的回文结构 一.链表的中间节点思路1&#xff1a;暴力求解思路2&#xff1a;快慢指针 二.返回倒数第k个节点思路1&#xff1a;暴力求解思路2&#xff1a;快慢指针 三.反转链表思路1&#xff1a;头插法思路2&#xff1a;反转指针的指向 四.链表的回文结构思路1&#xff1a;利…

react + redux 状态管理操作

目录 1 概念2 Redux 安装3 创建子模块并导入4 中间件为 react 注入 store5 在组件中使用 store 数据6 修改 store 数据7 提交 action 传参8 异步状态操作9 redux 调试工具 1 概念 Redux 是一个全局状态管理的 JS 库 2 Redux 安装 在react中使用redux&#xff0c;官方要求安…

css设置弹性flex后,如果设置100vh高度不撑满的原因

问题 父元素设置height为100%&#xff0c;有两个子元素&#xff0c;第一个设置height:100vh&#xff0c;第二个设置flex:1&#xff0c;此时第一个高度无法撑满盒子 原因解决方式 当父元素设置display为flex,第一个div设置高度64px,剩一个div设置高度为flex&#xff1a;1,这时…

DROO论文笔记

推荐文章DROO源码及论文学习 读论文《Deep Reinforcement Learning for Online Computation Offloading in Wireless Powered Mobile-Edge Computing Networks》的笔记 论文地址&#xff1a;用于无线移动边缘计算网络在线计算卸载的深度强化学习 论文代码地址&#xff1a;DR…