shiro(一):shiro基本概念及基本使用(认证、授权)

news2024/11/15 18:39:18

1. 权限的管理

1.1 什么是权限管理

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

权限管理包括用户身份认证授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

1.2 什么是身份认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

1.3 什么是授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的

2. 什么是shiro

Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

3. shiro的核心架构

在这里插入图片描述

3.1 Subject:主体

外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。

Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权

3.2 SecurityManager:安全管理器

对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。

SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

3.3 Authenticator:认证器

对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

3.4 Authorizer:授权器

用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

3.5 Realm:领域

相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

​ 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

3.6 SessionManager:会话管理

shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

3.7 SessionDAO:会话dao

是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

3.8 CacheManager:缓存管理

将用户权限数据存储在缓存,这样可以提高性能。

3.9 Cryptography:密码管理

shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

4. shiro中的认证

4.1 认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

4.2 shiro中认证的关键对象

4.2.1 Subject:主体

访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

4.2.2 Principal:身份信息

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

4.2.2 credential:凭证信息

是只有主体自己知道的安全信息,如密码、证书等。

4.3 认证流程

在这里插入图片描述

  1. shiro将用户名(身份信息)和密码(凭证信息)打包成一个token(令牌)
  2. 然后通过token去通过shiro核心架构中的安全管理器进行认证
  3. 安全管理器调用认证器,认证器调用任务去获取数据
  4. 如果获取到的数据与系统存储(数据库等)的一致,则认证通过,否则认证失败

4.4 认证的简单开发

4.4.1 引入依赖
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.5.3</version>
</dependency>
4.4.2 引入shiro配置文件

配置文件:名称随意,以 .ini 结尾,放在 resources 目录下;后续用了springboot就不需要此配置文件了,是前期方便我们学习shiro书写我们系统中相关权限数据

注意:在实际的项目开发中并不会使用这种方式,这种方法可以用来初学时练手

[users]
zhangsan=123456
lisi=456789
4.4.3 开发认证代码
public class test01 {
    public static void main(String[] args) {
        //1.创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2.给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //3.SecurityUtils给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4.关键对象subject主体
        Subject subject = SecurityUtils.getSubject();
        //5.创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
        try {
            System.out.println("认证状态"+subject.isAuthenticated());//fasle
            //用户认证
            subject.login(token);
            System.out.println("认证状态"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            e.printStackTrace();
            System.out.println("认证失败,用户名不存在");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("认证失败,密码错误");
        }
    }
}
4.4.4 .常见的异常类型
  • DisabledAccountException(帐号被禁用)
  • LockedAccountException(帐号被锁定)
  • ExcessiveAttemptsException(登录失败次数过多)
  • ExpiredCredentialsException(凭证过期)等
4.4.5 认证过程源码解析

可见:shiro相关源码解析

4.5 自定义Realm

通过分析源码可得:

  • 认证:
    1. 最终执行用户名比较是 在SimpleAccountRealm类 的 doGetAuthenticationInfo 方法中完成用户名校验
    2. 最终密码校验是在 AuthenticatingRealm类 的 assertCredentialsMatch方法 中
  • 总结:
    • AuthenticatingRealm 认证realm doGetAuthenticationInfo
    • AuthorizingRealm 授权realm doGetAuthorizationInfo

自定义Realm的作用:放弃使用.ini文件,使用数据库查询

上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。

4.5.1 shiro提供的Realm

在这里插入图片描述

4.5.2 根据认证源码认证使用的是SimpleAccountRealm

在这里插入图片描述
SimpleAccountRealm的部分源码中有两个方法一个是 认证 一个是 授权:

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    UsernamePasswordToken upToken = (UsernamePasswordToken)token;
    SimpleAccount account = this.getUser(upToken.getUsername());
    if (account != null) {
        if (account.isLocked()) {
            throw new LockedAccountException("Account [" + account + "] is locked.");
        }

        if (account.isCredentialsExpired()) {
            String msg = "The credentials for account [" + account + "] are expired";
            throw new ExpiredCredentialsException(msg);
        }
    }

    return account;
}

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String username = this.getUsername(principals);
    this.USERS_LOCK.readLock().lock();

    AuthorizationInfo var3;
    try {
        var3 = (AuthorizationInfo)this.users.get(username);
    } finally {
        this.USERS_LOCK.readLock().unlock();
    }

    return var3;
}
4.5.3 自定义realm
public class CustomRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("==================");
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //在token中获取 用户名
        String principal = (String) authenticationToken.getPrincipal();
        System.out.println(principal);

        //实际开发中应当 根据身份信息使用jdbc mybatis查询相关数据库
        //在这里只做简单的演示
        //假设username,password是从数据库获得的信息
        String username="zhangsan";
        String password="123";
        if(username.equals(principal)){
            //参数1:返回数据库中正确的用户名
            //参数2:返回数据库中正确密码
            //参数3:提供当前realm的名字 this.getName();
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,password,this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}
4.5.4 自定义登录认证
public class TestAuthenticatorCustomRealm {
    public static void main(String[] args) {
        //1.创建安全管理对象 securityManager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2.给安全管理器设置realm(设置为自定义realm获取认证数据)
        defaultSecurityManager.setRealm(new CustomRealm());
        //IniRealm realm = new IniRealm("classpath:shiro.ini");
        //3.给安装工具类中设置默认安全管理器
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //4.获取主体对象subject
        Subject subject = SecurityUtils.getSubject();

        //5.创建token令牌
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        try {
            subject.login(token);//用户登录
            System.out.println("登录成功~~");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误!!");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            System.out.println("密码错误!!!");
        }

    }
}

4.6 使用MD5+SALT+HASH

在这里插入图片描述

4.6.1 MD5
  • 作用:一般用来加密或者签名(校验和)
  • 特点:MD5算法不可逆如何内容相同无论执行多少次md5生成结果始终是一致
  • 网络上提供的MD5在线解密一般是用穷举的方法
  • 生成结果:始终是一个16进制32位长度字符串
4.6.2 SALT
  • 一段自定义字符,通过此字符+原密码,来使密码更加安全
  • 这个自定义字符最好是自定义在代码中
  • 还有一种方式是随机生成SALT,并将此SALT一起保存在数据库中(此方式黑客还需要判断SALT和密码的拼接方式)
4.6.3 加密基本测试
public static void main(String[] args) {
    //使用md5,通过hash算法让结果更加安全
    //通过构造方法将结果加密
    // 通过源码可以看到没有加盐默认盐值是1
    Md5Hash md5Hash = new Md5Hash("123");
    System.out.println(md5Hash.toHex());

    //使用MD5 + salt处理
    // 默认盐值是加在后面
    Md5Hash md5Hash1 = new Md5Hash("123", "java");
    System.out.println(md5Hash1.toHex());

    //使用md5 + salt + hash散列(参数代表要散列多少次,一般是 1024或2048)
    Md5Hash md5Hash2 = new Md5Hash("123", "java", 1024);
    System.out.println(md5Hash2.toHex());
}

结果:

202cb962ac59075b964b07152d234b70
b67e00dd69ba454e68c5e3dd228e19ec
2c5f16f56505041f97ab92c8e73c363d
4.6.4 自定义md5+salt的realm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    //获取 token中的 用户名
    String principal = (String) token.getPrincipal();

    //假设这是从数据库查询到的信息
    String username="zhangsan";
    String password="2c5f16f56505041f97ab92c8e73c363d";//加密后

    //根据用户名查询数据库
    if (username.equals(principal)) {
        //参数1:数据库用户名
        //参数2:数据库md5+salt之后的密码
        //参数3:注册时的随机盐
        //参数4:realm的名字
        return new SimpleAuthenticationInfo(principal,
                password,
                ByteSource.Util.bytes("java"),
                this.getName());
    }
    return null;
}
4.6.5 使用md5+salt 认证
public class TestAuthenticatorCustomMd5Realm {
    public static void main(String[] args) {
        //1.创建安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //2.注入realm
        CustomMd5Realm realm = new CustomMd5Realm();
        //3.设置realm使用hash凭证匹配器
        // 凭证匹配器是对token中密码和用户中密码进行验证的方法,默认是equals
        // 此处是用散列的凭证匹配器告诉shiro,我需要对密码进行md5加密并且设置一个散列
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //声明:使用的算法
        credentialsMatcher.setHashAlgorithmName("md5");
        //声明:散列次数
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);
        defaultSecurityManager.setRealm(realm);

        //4.将安全管理器注入安全工具
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        //5.通过安全工具类获取subject
        Subject subject = SecurityUtils.getSubject();
        //6.认证
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");

        try {
            subject.login(token);
            System.out.println("登录成功");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户名错误");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }
    }
}

5. shiro中的授权

5.1 授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

5.2 关键对象

授权可简单理解为who对what(which)进行How操作

  • Who,即主体(Subject):主体需要访问系统中的资源。
  • What,即资源(Resource):如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
  • How,权限/许可(Permission):规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。

5.3 授权流程

在这里插入图片描述

5.4 授权方式

5.4.1 基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制

if(subject.hasRole("admin")){
   //用户拥有次角色可以操作什么资源
}
5.4.2 基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制

if(subject.isPermission("user:update:01")){ //资源实例
  //对 资源01 用户具有修改的权限
}
if(subject.isPermission("user:update:*")){  //资源类型
  //对 所有的资源 用户具有修改的权限
}

5.5 权限字符串

​权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。

例子:

  • 用户创建权限:user:create,或user:create:*
  • 用户修改实例001的权限:user:update:001
  • 用户实例001的所有权限:user:*:001

5.6 shiro中授权编程实现方式

  • 编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
	//有权限
} else {
	//无权限
}
  • 注解式
@RequiresRoles("admin")
public void hello() {
	//有权限
}
  • 标签式
JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:
<shiro:hasRole name="admin">
	<!— 有权限—>
</shiro:hasRole>
注意: Thymeleaf 中使用shiro需要额外集成!

5.7 开发授权

5.7.1 realm中授权实现:
public class CustomMd5Realm extends AuthorizingRealm {
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        String primaryPrincipal = (String)principals.getPrimaryPrincipal();
        System.out.println("身份信息: "+primaryPrincipal); //用户名

        //根据身份信息 用户名 获取当前用户的角色信息,以及权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //假设 admin,user 是从数据库查到的 角色信息
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addRole("user");
        //假设 ... 是从数据库查到的 权限信息赋值给权限对象
        simpleAuthorizationInfo.addStringPermission("user:*:01");
        simpleAuthorizationInfo.addStringPermission("product:*");//第三个参数为*省略

        return simpleAuthorizationInfo;
    }
    
    。。。。
}
5.7.2 授权测试:
public class TestAuthenticatorCustomMd5Realm {
    public static void main(String[] args) {
		。。。。
		try {
		    subject.login(token);
		        System.out.println("登录成功");
		    } catch (UnknownAccountException e) {
		        e.printStackTrace();
		        System.out.println("用户名错误");
		    } catch (IncorrectCredentialsException e) {
		        e.printStackTrace();
		        System.out.println("密码错误");
		    }
		
		    //授权
		    if (subject.isAuthenticated()){
		        //基于角色权限控制
		        System.out.println(subject.hasRole("admin"));
		        //基于多角色的权限控制
		        System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));//true
		        System.out.println(subject.hasAllRoles(Arrays.asList("admin", "manager")));//false
		        //是否具有其中一个角色
		        boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "manager"));
		        for (boolean aBoolean : booleans) {
		            System.out.println(aBoolean);
		        }
		
		        System.out.println("====这是一个分隔符====");
		
		        //基于权限字符串的访问控制  资源标识符:操作:资源类型
		        //用户具有的权限 user:*:01  prodect:*
		        System.out.println("权限:"+subject.isPermitted("user:update:01"));
		        System.out.println("权限:"+subject.isPermitted("product:update:02"));
		
		        //分别具有哪些权限
		        boolean[] permitted = subject.isPermitted("user:*:01", "user:update:02");
		        for (boolean b : permitted) {
		            System.out.println(b);
		        }
		
		        //同时具有哪些权限
		        boolean permittedAll = subject.isPermittedAll("product:*:01", "product:update:03");
		        System.out.println(permittedAll);
		    }
		}
	}
}

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

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

相关文章

算法刷题打卡第76天:判断矩阵是否是一个 X 矩阵

判断矩阵是否是一个 X 矩阵 难度&#xff1a;简单 如果一个正方形矩阵满足下述 全部 条件&#xff0c;则称之为一个 X 矩阵 &#xff1a; 矩阵对角线上的所有元素都 不是 0 矩阵中所有其他元素都是 0 给你一个大小为 n x n 的二维整数数组 grid &#xff0c;表示一个正方形矩…

CV——day72:从零开始学YOLO——YOLO-v3(可以在我的资源里下载完整的v1到v3的笔记啦!)

YOLO-v36. YOLO-v36.1 YOLO-v3 改进综述6.2 多scale方法改进与特征融合6.3 经典变换方法对比分析6.4 残差连接方法解读6.5 整体网络模型架构分析6.6 先验框设计改进6.7 softmax层改进6. YOLO-v3 **tips&#xff1a;**作者本人因为美军广泛运用于军事领域&#xff0c;所以决定不…

基于php电影点播平台/电影网站

摘要网络技术给生活带来了十分的便利。所以把电影点播平台与现在网络相结合。在点播平台发展的整个过程中&#xff0c;电影信息管理担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类电影信息管理程序也在不断改进。本课题所设计的电影点播平台&#xff0c;使…

张艺谋《满江红》起诉自媒体人,杨语莲推荐周兆成意在下个谋女郎

伴随着兔年春节的到来&#xff0c;又迎来一波贺岁剧热潮&#xff0c;著名导演张艺谋的《满江红》&#xff0c;也如期和观众朋友见面。随着春节的逐渐结束&#xff0c;贺岁电影《满江红》票房&#xff0c;也再次创下了新高&#xff0c;关于这部电影的话题也多了起来。 最引人关注…

中国电子学会2021年12月份青少年软件编程Python等级考试试卷一级真题(含答案)

青少年软件编程&#xff08;Python&#xff09;等级考试试卷&#xff08;一级&#xff09; 一、单选题(共25题&#xff0c;共50分) 1. 昨天的温度是5摄氏度&#xff0c;今天降温7摄氏度&#xff0c;今天的温度是多少摄氏度&#xff1f;&#xff08; &#xff09; A. 12 …

利用ChatGPT自动编写下载高德地图poi数据的代码

最近ChatGPT很火&#xff0c;它自己对于自己的解释如下图。我们可以让它来帮我们写代码&#xff0c;属于是薅机器人羊毛了。 首先注册账号&#xff0c;可百度&#xff0c;如&#xff1a;【教程】ChatGPT 保姆级注册教程&#xff0c;但中国大陆手机号不支持OpenAI的注册服务&am…

进程概念(PCB、进程创建、进程状态等)

进程是一个运行的程序&#xff0c;是所有计算机的基础。这个过程与计算机代码不一样&#xff0c;尽管它们非常相似。程序通常被认为是 “被动的” 实体&#xff0c;而进程则是 “主动的” 实体。硬件状态、RAM、CPU和其它属性都是进程持有的属性。下面我们就来了解更多关于进程…

活体识别1:近红外(NIR)图像特性

说明 最近在接触活体识别&#xff0c;在网上找到一个介绍近红外光&#xff08;NIR&#xff09;特性的论文&#xff0c;我简单做个笔记。原文的全文在文末参考资料里。 来自&#xff1a;[1]隋孟君,茅耀斌,孙金生.基于近红外图像特征的活体人脸检测[J].自动化与仪器仪表,2021(0…

Win10下使用WSL2

打包 wsl --export Ubuntu-20.04 E:\Ubuntu\ubuntu.tar.gz 注销之前 wsl --unregister Ubuntu-20.04 导入 wsl --import Ubuntu-20.04 E:\Ubuntu\ E:\Ubuntu\ubuntu.tar.gz --version 2 设置默认登陆用户为安装时用户名 ubuntu2004.exe config --default-user dwb 更新清…

vue 预览 word

最近做的项目要求实现预览word, pdf&#xff0c;png等文件功能&#xff0c;pdf以及png都很简单&#xff0c;轻松百度搞定&#xff0c;但是word预览研究了好久&#xff0c;所以特此记录分享。前端实现预览word分为两种&#xff0c;一种是上传前预览&#xff08;也就是前端使用in…

SpringBoot笔记:统一请求参数修改(HttpServletRequest流复制),加解密参数也可参考处理

文章目录需求实现思路实战演练实现过滤器Filter继承 HttpServletRequestWrapper实现 RequestBodyAdvice 统一处理请求参数测试代码测试效果需求 需要进行统一的解密请求 header 头里面的关键字 encryKey &#xff0c;将解密出来的值赋给 provinceId 并传递给后端的每一个请求接…

23年 yolov5车辆识别+行人识别+车牌识别+车速检测代码(python)

行人识别yolov5和v7对比yolo车距yolo车距1代码&#xff1a;yolov5车辆检测代码 已有1503人下载 代码无需更改&#xff0c;直接可以预测&#xff01;&#xff01;&#xff01; 流程&#xff1a; 版本与配置声明 # YOLOv5 requirements # Usage: pip install -r requirements.tx…

C语言--结构体初阶

目录前言结构体类型的声明什么是结构结构的声明结构体变量的定义结构成员的类型结构体变量的初始化结构体的成员访问结构体传参函数调用的参数压栈前言 在前面的C语言学习中&#xff0c;我们学习了形如char&#xff0c;short&#xff0c;int&#xff0c;float等的不同类型的变…

活动星投票臻我风采评选视频投票的功能在线投票程序

“臻我风采评选”网络评选投票_线上小程序的投票方式_视频投票的功能_在线投票程序用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务…

即时通讯开发之详解TCP/IP中的IP选路、动态选路

静态 IP 选路 1一个简单的路由表 选路是 IP 层最重要的一个功能之一。前面的部分已经简单的讲过路由器是通过何种规则来根据 IP 数据包的 IP 地址来选择路由。 这里就不重复了。 对于一个给定的路由器,可以打印出五种不同的 flag&#xff1a;  U 表明该路由可用。  G…

Qlik Sense Enterprise Windows版(非集群)——详细安装步骤

Qlik Sense分为客户端(Desktop)和服务器(Server)&#xff0c;本文主要介绍Qlik Sense Server的图形化界面安装操作。 安装方式也分为两种&#xff0c;一种是图形化界面安装&#xff0c;另一种是静默安装。一般只有在特殊报错情况下我们才使用静默安装&#xff0c;静默安装的方…

实现分页查询

实现分页查询一、分页要素与语法二、例&#xff1a;博客系统文章列表页2.1 原始前端代码2.2 实现&#xff1a;查询分页数据验证2.3 实现 “首页” “上一页” “下一页” “末页”前端代码添加得到总页数实现 四个点击效果验证一、分页要素与语法 分页要素&#xff1a; 1&…

Qt音视频开发13-视频解码线程基类的设计

一、前言 这个解码线程基类的设计&#xff0c;是到目前为止个人觉得自己设计的最好的基类之一&#xff0c;当然也不是一开始就知道这样设计&#xff0c;没有个三五年的摸爬滚打以及社会的毒打&#xff0c;是想不到要这样设计的&#xff0c;一方面是要不断提炼各种视频类视频组…

伪逆矩阵 的使用

伪逆矩阵&#xff08;pseudo-inverse&#xff09;_Uglyduckling911的博客-CSDN博客_伪逆矩阵 一、什么是伪逆矩阵 若任意矩阵Am*n,B 为A的广义逆矩阵&#xff0c;若B满足下述条件&#xff08;Moore-penrose条件&#xff09;&#xff1a; &#xff08;1&#xff09;BAB B&am…

4.门面模式,组合模式,享元模式

门面模式 为了保证接口的可复用性&#xff08;或者叫通用性&#xff09;&#xff0c;我们需要将接口尽量设计得细粒度一点&#xff0c;职责单一一点。但是&#xff0c;如果接口的粒度过小&#xff0c;在接口的使用者开发一个业务功能时&#xff0c;就会导致需要调用 n 多细粒度…