• Shiro框架-史上详解


    Shiro

    1.权限管理概述

    2.Shiro权限框架
    2.1 概念
    2.2 Apache Shiro 与Spring Security区别

    3.Shiro认证
    3.1 基于ini认证
    3.2 自定义Realm --认证

    4.Shiro授权
    4.1 基于ini授权
    4.2 自定义realm – 授权

    5.项目集成shiro 认证-授权注意点
    5.1 认证
    5.2 授权
    5.3 注解@RequiresPermissions()
    5.4 标签式权限验证

    6.Shiro加密

    7.Shiro缓存

    权限管理概述


    RBAC(Role Based Access Control) :某个用户拥有什么角色,被允许做什么事情(权限)

    用户登录—>分配角色---->(权限关联映射)---->鉴权(拥有什么什么权限)

    熟悉框架流程

    • 概念 能做什么
    • 架构是怎样
    • 代码怎么实现
    • 项目运用

    Shiro权限框架


    • 概念: Apache Shiro 是一个强大且易用的 Java 安全框架
    • 能做什么:Shiro可以帮我们完成 :认证授权、加密、会话管理、与 Web 集成、缓存等。
    • 架构是怎样的

    在这里插入图片描述

    主要认识:

    • Subject(用户):当前的操作用户 获取当前用户Subject currentUser = SecurityUtils.getSubject()
    • SecurityManager(安全管理器):Shiro的核心,负责与其他组件进行交互,实现 subject 委托的各种功能
    • Realms(数据源) :Realm会查找相关数据源,充当与安全管理间的桥梁,经过Realm找到数据源进行认证,授权等操作
    • Authenticator(认证器): 用于认证,从 Realm 数据源取得数据之后进行执行认证流程处理。
    • Authorizer(授权器):用户访问控制授权,决定用户是否拥有执行指定操作的权限。
    • SessionManager (会话管理器):支持会话管理
    • CacheManager (缓存管理器):用于缓存认证授权信息
    • Cryptography(加密组件):提供了加密解密的工具包

    Apache Shiro 与Spring Security区别

    Shiro::用于中小型项目比较常见,简单易上手,可以支持多种环境
           Shiro 可以不跟任何的框架或者容器绑定,可独立运行
    Spring Security:一般多用于spring环境,中大型项目,更强大
                    Spring Security 则必须要有Spring环境
    
    • 1
    • 2
    • 3
    • 4

    Shiro认证


    基于ini认证

    1.新建项目 --导入依赖
               --新建 配置文件ini
        
    2. shiro帮我们创建用户
        创建令牌 ,类比页面传入的账号密码
        密码错误--  IncorrectCredentialsException
        账号错误--  UnknownAccountException
        
       源码分析--
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    • 导入依赖

      commons-logging commons-logging 1.1.3 org.apache.shiro shiro-core 1.5.2 junit junit 4.13.2 org.projectlombok lombok 1.16.22 provided
    • 编写ini,shiro默认支持的是ini配置的方式(只是演示) shiro-au.ini,真实项目使用xml

      #用户的身份、凭据
      [users]
      zhangsan=555
      xiaoluo=666

    • 使用 Shiro 相关的 API 完成身份认证

      @Test
      public void testLogin(){
      //创建Shiro的安全管理器,是shiro的核心
      DefaultSecurityManager securityManager = new DefaultSecurityManager();
      //加载shiro.ini配置,得到配置中的用户信息(账号+密码)
      IniRealm iniRealm = new IniRealm(“classpath:shiro-au.ini”);
      securityManager.setRealm(iniRealm);
      //把安全管理器注入到当前的环境中
      SecurityUtils.setSecurityManager(securityManager);
      //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
      Subject subject = SecurityUtils.getSubject();
      System.out.println(“认证状态:”+subject.isAuthenticated());
      //创建令牌(携带登录用户的账号和密码)
      UsernamePasswordToken token = new UsernamePasswordToken(“xiaoluo”,“666”);
      //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
      subject.login(token);
      System.out.println(“认证状态:”+subject.isAuthenticated());
      //登出
      //subject.logout();
      //System.out.println(“认证状态:”+subject.isAuthenticated());
      }

    自定义Realm–认证

    模拟

    @Getter
    @Setter
    public class Employee {
        private String username;
        private String password;}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    思路:

    //继承 AuthorizingRealm
    //参数的token ,subject进行登录匹配传入的token : UsernamePasswordToken
    1.获取用户名
        方式一:将token强转为UsernamePasswordToken-->getUsername()
        方式二:通过token.getPrincipal()    
        
    2.以用户名为条件,查询mysql数据库,得到用户对象(用户名/密码)
            模拟从数据库查询的对象 ------->自己new一个
            
    3.将用户对象封装成认证info对象返回
            //存在两种可能
            1.用户对象为null-----> return null
            2.不为空 ---->封装数据 return new SimpleAuthenticationInfo(
                  有3个参数 
                   1.employee ,  //从数据库查询出来需要进行密码匹配的用户对象
                   2.employee.getPassword(),  //匹配对象密码
                   3.super.getName()  //指定realm的名称 ,可自定义
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    实现:

    public class Realm extends AuthorizingRealm {
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //1.获取登录用户名
            String username = (String) token.getPrincipal();
            //2.以用户名为条件查询mysql数据库,得到用户对象
            //模拟数据
            Employee employee = new Employee();
            employee.setUsername("xiaoluo");
            employee.setPassword("123");
    
            //封装成一个认证info对象
            //判断用户是否为空
            if (employee!=null){
                return new SimpleAuthenticationInfo(
                        employee,
                        employee.getPassword(),
                        super.getName()
                );
            }
            return null;
        }
    }
    
    
    @Test
    public void testLogin(){
        //创建Shiro的安全管理器,是shiro的核心
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
      
            //自定义Realm,查出用户信息
            Realm realm = new Realm();
            securityManager.setRealm(realm);
        
        //把安全管理器注入到当前的环境中
        SecurityUtils.setSecurityManager(securityManager);
        //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
        Subject subject = SecurityUtils.getSubject();
        System.out.println("认证状态:"+subject.isAuthenticated());
        //创建令牌(携带登录用户的账号和密码)
        UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","123");
        //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
        subject.login(token);
        System.out.println("认证状态:"+subject.isAuthenticated());
        //登出
        //subject.logout();
        //System.out.println("认证状态:"+subject.isAuthenticated());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    Shiro授权


    基于ini授权

    • 登录

    • 分配

    • 鉴权

      权限表达式   资源:操作
      admin=*:*  //超级管理员
      emp=employee:*
      
      • 1
      • 2
      • 3

      //判断是否有某个角色
      subject.hasRole(“role”);
      //用户拥有所有指定角色返回true
      subject.hasAllRoles(Arrays.aList(“role1”,“role2”));
      //判断用户是否有指定角色,将结果返回,封装到boolean数组中
      boolean[] booleans=subject.hasRoles(Arrays.aList(“role1”,“role2”));
      //check开头的是没有返回值,当没有权限时就会抛出异常
      subject.checkRole(“role”);

      //判断用户是否有某个权限
      subject.isPermitted(“权限表达式”)
      boolean[] booleans=subject.isPermitted(“权限表达式1”,“权限表达式2”)

    在这里插入图片描述

    自定义realm — 授权

    思路:

    1.获取当前登录用户的id/name
        //获取当前登录用户对象 ,登录传过来的第一个参数
        方式一 :principals.getPrimaryPrincipal();
        方式二:SecurityUtils.getSubject().getPrincipal();
    
    2.以用户id作为条件查询mysql数据,查询该用户拥有角色/权限
        //假装查数据
        //List roles= roleService.queryByEmployee(Employee.getId());
       //List    //List roles= permissionService.queryByEmployee(Employee.getId());
    = permissionService.queryByEmployee(Employee.getId());
        
        
    3.将角色与权限封装到授权info对象中 
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles) //添加角色
        info.addStringPermission(permission);//添加权限    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实现:

     //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取当前用户对象
            Employee employee = (Employee)principalCollection.getPrimaryPrincipal();
    
            //以用户id查询数据库,判断该角色/用户是否拥有权限  模拟
            List roles= Arrays.asList("seller");
            List permissions= Arrays.asList("customer:list","customer:save");
    
            //封装到info对象
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRoles(roles);
            info.addStringPermissions(permissions);
    
            return info;
        }
    
    
    public class ShiroDemo {
        @Test
        public void testLogin(){
            //创建Shiro的安全管理器,是shiro的核心
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            //加载shiro.ini配置,得到配置中的用户信息(账号+密码)
            IniRealm iniRealm = new IniRealm("classpath:shiro-author.ini");
    
            //自定义Realm,查出用户信息
            Realm realm = new Realm();
            securityManager.setRealm(realm);
    
            //把安全管理器注入到当前的环境中
            SecurityUtils.setSecurityManager(securityManager);
            //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
            Subject subject = SecurityUtils.getSubject();
    
            System.out.println("认证状态:"+subject.isAuthenticated());
    
            //创建令牌(携带登录用户的账号和密码)
            UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","12");
            //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
            subject.login(token);
    
            System.out.println("认证状态:"+subject.isAuthenticated());
    
            //登出
            //subject.logout();
            //System.out.println("认证状态:"+subject.isAuthenticated());
    
            //判断用户是否有某个角色
            System.out.println("hr:"+subject.hasRole("hr"));
            System.out.println("seller:"+subject.hasRole("seller"));
    
            //是否同时拥有多个角色
            System.out.println("是否同时拥有role1和role2:"+subject.hasAllRoles(Arrays.asList("hr", "seller")));
            boolean[] booleans = subject.hasRoles(Arrays.asList("hr", "seller"));
            //System.out.println(booleans);
    
            //check开头的是没有返回值的,当没有权限时就会抛出异常
            subject.checkRole("seller");
    
            //判断用户是否有某个权限
            System.out.println("user:delete:"+subject.isPermitted("user:delete"));
            subject.checkPermission("customer:list");
        }
    }
    
    
    	//如果用户是超级管理员 
        //设置超级管理员角色
       info.addRole("admin")
        //设置超级管理员权限
    info.addStringPermission("*:*");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    项目集成shiro 认证-授权注意点


    认证

    1.添加对应依赖
    2.配置代理过滤器
       为什么不用配置拦截器?
       Shiro是选择使用filter过滤器来进行拦截的,因为Shiro不依赖Spring容器,所以当没有springmvc时意味着不能用拦截器,但过滤器则不同,只要是web项目都可以使用
    3.创建shiro.xml
    4.配置shiro过滤器
       
       
       
    5.配置安全管理器  DefaultWebSecurityManager
    6.修改LoginController
    7.配置自定义Realm
    8.将自定义Realm交给容器管理
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    **

    shiro中的过滤器**

    过滤器的名称

    Java

    anon

    org.apache.shiro.web. lter.authc.AnonymousFilter

    authc

    org.apache.shiro.web. lter.authc.FormAuthenticationFilter

    authcBasic

    org.apache.shiro.web. lter.authc.BasicHttpAuthenticationFilter

    roles

    org.apache.shiro.web. lter.authz.RolesAuthorizationFilter

    perms

    org.apache.shiro.web. lter.authz.PermissionsAuthorizationFilter

    user

    org.apache.shiro.web. lter.authc.UserFilter

    logout

    org.apache.shiro.web. lter.authc.LogoutFilter

    port

    org.apache.shiro.web. lter.authz.PortFilter

    rest

    org.apache.shiro.web. lter.authz.HttpMethodPermissionFilter

    ssl

    org.apache.shiro.web. lter.authz.SslFilter

    • anon: 匿名处理过滤器,即不需要登录即可访问;一般用于静态资源过滤;/static/**=anon
    • authc: 表示需要认证(登录)才能使用;(放最后) /**=authc
    • logout: 注销过滤器 /logout=logout
    • roles: 角色授权过滤器,验证用户是否拥有资源角色; /employee/input=perms["user:update"]

    授权

    • 没有权限的异常 :org.apache.shiro.authz.UnauthorizedException

      shiro注解鉴权操作方式:
      类比RBAC:
      1.自定义权限注解
      2.将注解贴在请求映射方法上面
      3.将注解标注的权限表达式加载到数据库中
      4.将这些表达式根据用户角色进行权限分配
      5.当用户登录之后,访问某个请求映射方法时,先经过权限拦截器,进行鉴权操作
      1.获取当前登录用户权限表达式集合
      2.获取当前请求映射方法头顶上权限表达式
      3.判断用户权限表达式集合中是否包含该表达式

    Shiro 权限验证三种方式

    • 编程式

    • 注解式

    • 页面标签式

      1.编程式
      Subject subject = SecurityUtils.getSubject();
      if(subject.hasRole(“hr”)) {
      //有权限
      } else {
      //无权限
      }

      2.注解式
      @RequiresRoles(“admin”)
      @RequiresPermissions(“user:create”)
      public void hello() {
      //有权限
      }

      3.页面标签式
      <@shiro.hasPermission name=“employee:list”>

      /@shiro.hasRole

    授权步骤

    1.贴注解
    2.开启 Shiro 注解扫描器
    3.查询数据库真实数据 自定义realm    
    
    • 1
    • 2
    • 3

    注解@RequiresPermissions()

    • value属性: 这个属性是一个数组

    • Logical.AND: 必须同时拥有value配置所有权限才允许访问

    • Logical.OR:只需要拥有value配置所有权限中一个即可允许访问

      约定:权限表达式,第一值权限表达式,第二值为权限名称
      @RequiresPermissions(value={“employee:list”,“员工列表”},logical=Logical.OR)
      逻辑为OR,value满足其中一个条件成立

    标签式权限验证

    **
    拓展FreeMarker标签**

    默认的freemarker是不支持shiro标签,所以需要做功能拓展
     ----------------   
      public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer {
        @Override
        public void afterPropertiesSet() throws IOException, TemplateException {
            //继承之前的属性配置,这不能省
            super.afterPropertiesSet();
            Configuration cfg = this.getConfiguration();
            cfg.setSharedVariable("shiro", new ShiroTags());//注册shiro 标签
        }
    }  
    --------------------------让之前的配置文件
        中的FreeMarkerConfigurer修改为自定义的ShiroFreeMarkerConfig类
        
        
            
            
            
            
            
                
                    
                    true
                
            
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    使用shiro标签

    • authenticated 标签:已认证通过的用户。

      <@shiro.authenticated> /@shiro.authenticated

    • notAuthenticated 标签:未认证通过的用户。

      <@shiro.notAuthenticated>/@shiro.notAuthenticated

    • principal 标签 :输出当前用户信息,通常为登录帐号信息

      <@shiro.principal property=“name” /> //对应name属性

    • hasRole 标签:验证当前用户是否拥有该角色

      <@shiro.hasRole name=“admin”>我是管理员/@shiro.hasRole

    • hasAnyRoles 标签:验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔

      <@shiro.hasAnyRoles name=“admin,user,hr”>Hello admin/@shiro.hasAnyRoles

    • hasPermission 标签:验证当前用户是否拥有该权限

      <@shiro.hasPermission name=“department:delete”>删除/@shiro.hasPermission

    Shiro加密


    MD5

    //参数1 :原文,参数2:盐 ,参数3:散列次数(加密次数)
    Md5Hash hash=new Md5Hash("1","kent",3);
    
    • 1
    • 2

    盐一般要求是固定长度的字符串,且每个用户的盐不同。

    一般盐的选择的是用户的唯一数据(账号名等),盐是要求不能改变的,不然下次加密结果就对应不上了
    
    • 1

    Shiro缓存


    当我们登录时,授权信息是要从数据库中查询的,如果每次刷新刷新都需要获取你到底有没有权限,对性能影响不好,用户登录后,授权信息一般很少改动,所以,我们可以将第一次授权后,将信息存在缓存中,下次直接再缓存中获取,就很好的避免了多次访问数据库

    Shiro没有实现自己的缓存机制,只提供了支持缓存的API接口,这里使用的是EhCache

    mybatis 一级  二级缓存------百度(ing)补充
    
    • 1

    集成EhCache

    第三方EhCache
      1.配置缓存管理器并引用缓存管理器
        
    
        
        
        
        
    
    
    
     
        
        
    
        ----------------------------------------
        2.添加缓存配置文件  shiro-ehcache.xml
        
        
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    配置缓存文件属性说明

    • maxElementsInMemory: 缓存对象最大个数。
    • eternal:对象是否永久有效 ,一般为false
    • timeToIdleSeconds: 对象空闲时间
    • timeToLiveSeconds:对象存活时间
    • memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。
    • LFU(较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存)默认

    拓展 统一全局异常

    @ControllerAdvice 控制器功能增强注解

    1.在进入请求映射方法之前做功能增强,经典用法:date日期格式化
    2.在进入请求映射方法之后做功能增强,经典用法:统一异常处理 
    
    3.处理异常的方法,方法需要贴ExceptionHandler注解
    
    • 1
    • 2
    • 3
    • 4

    配置文件

    配置代理过滤器

    
        shiroFilter
        
            org.springframework.web.filter.DelegatingFilterProxy
        
    
    
        shiroFilter
        /*
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    配置shiro.xml

    
    
    
        
        
        
    
        
        
            
            
            
        
    
        
        
            
            
            
            
            
            
                
                    /userLogin=anon
                    /css/**=anon
                    /js/**=anon
                    /img/**=anon
                    /upload/**=anon
                    /userLogout=logout
                    /**=authc
                
            
        
    
    
        
        
        
        
        
            
        
    
    
        
        
            
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    关联mvc.xml

    
    
    • 1

    loginController

    @RequestMapping("/userLogin")
        @ResponseBody
        public JsonResult login(String username, String password){
            try {
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                SecurityUtils.getSubject().login(token);
                return new JsonResult();
            } catch (UnknownAccountException e) {
                return new JsonResult(false, "账号不存在");
            } catch (IncorrectCredentialsException e) {
                return new JsonResult(false, "密码错误");
            } catch (Exception e) {
                e.printStackTrace();
                return new JsonResult(false, "登录异常,请联系管理员");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    关联mvc.xml

    
    
    • 1

    loginController

    @RequestMapping("/userLogin")
        @ResponseBody
        public JsonResult login(String username, String password){
            try {
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                SecurityUtils.getSubject().login(token);
                return new JsonResult();
            } catch (UnknownAccountException e) {
                return new JsonResult(false, "账号不存在");
            } catch (IncorrectCredentialsException e) {
                return new JsonResult(false, "密码错误");
            } catch (Exception e) {
                e.printStackTrace();
                return new JsonResult(false, "登录异常,请联系管理员");
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    Python每日一练 05
    “无法为保留分区分配驱动器号”的解决
    关于python中round方法在一些四舍五入时丢失精度
    嘉绩咨询:八位一体产业创新,赋能品牌新零售
    【数据结构】什么是算法
    采用一种估值方法,自动化评估某个股票价格合理性
    简单工厂模式
    BadNets:基于数据投毒的模型后门攻击代码(Pytorch)以MNIST为例
    【GESP考级C++】1级样题 闰年统计
    Reverse Engineering Preliminary – ASM Instructions
  • 原文地址:https://blog.csdn.net/qq_46416934/article/details/126114060