shiro入门实战

news2024/11/25 23:25:29

​​​​​​​Apache Shiro | Simple. Java. Security.

java语言编写

架构

 shiro认证流程

使用

添加shiro依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>

SimpleAccountRealm

SimpleAccountRealm只支持role的授权 hasRole、checkRole 

授权是认证之后的操作 

public void authen() {
        //认证的发起者(subject),   SecurityManager,   Realm
        //1. 准备Realm(基于内存存储用户信息)
        SimpleAccountRealm realm = new SimpleAccountRealm();
        realm.addAccount("admin", "admin", "超级管理员", "商家");

        //2. 准备SecurityManager
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        //3. SecurityManager和Realm建立连接
        securityManager.setRealm(realm);

        //4. subject和SecurityManager建立联系
        SecurityUtils.setSecurityManager(securityManager);

        //5. 声明subject
        Subject subject = SecurityUtils.getSubject();

        //6. 发起认证
        subject.login(new UsernamePasswordToken("admin", "admin"));
        // 如果认证时,用户名错误,抛出:org.apache.shiro.authc.UnknownAccountException异常
        // 如果认证时,密码错误,抛出:org.apache.shiro.authc.IncorrectCredentialsException:

        //7. 判断是否认证成功
        System.out.println(subject.isAuthenticated());

        //8. 退出登录后再判断
        //        subject.logout();
        //        System.out.println("logout方法执行后,认证的状态:" + subject.isAuthenticated());

        //9. 授权是在认证成功之后的操作!!!
        // SimpleAccountRealm只支持角色的授权
        System.out.println("是否拥有超级管理员角色:" + subject.hasRole("超级管理员"));
        subject.checkRole("商家");
        // check方法校验角色时,如果没有指定角色,会抛出异常:org.apache.shiro.authz.UnauthorizedException: Subject does not have role [角色信息]
    }

IniRealm

基于文件存储用户名,密码,角色等信息

支持权限校验

public void authen(){
        //1. 构建IniRealm
        IniRealm realm = new IniRealm("classpath:shiro.ini");

        //2. 构建SecurityManager绑定Realm
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        securityManager.setRealm(realm);

        //3. 基于SecurityUtils绑定SecurityManager并声明subject
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        //4. 认证操作
        subject.login(new UsernamePasswordToken("admin","admin"));

        //5. 角色校验
        // 超级管理员
        System.out.println(subject.hasRole("超级管理员"));
        subject.checkRole("运营");

        //6. 权限校验
        System.out.println(subject.isPermitted("user:update"));
        // 如果没有响应的权限,就抛出异常:UnauthorizedException: Subject does not have permission [user:select]
        subject.checkPermission("user:delete");
    }

shiro.ini

[users]
username=password,role1,role2
admin=admin,超级管理员,运营
[roles]
role1=perm1,perm2
超级管理员=user:add,user:update,user:delete

JdbcRealm

通过数据库存储对应的用户、角色、权限信息

推荐使用经典五张表来存储

    public void authen(){
        //1. 构建JdbcRealm
        JdbcRealm realm = new JdbcRealm();

        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///shiro");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        realm.setDataSource(dataSource);

        // 开启权限校验
        realm.setPermissionsLookupEnabled(true);

        //2. 构建SecurityManager绑定Realm
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        securityManager.setRealm(realm);

        //3. 基于SecurityUtils绑定SecurityManager并声明subject
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        //4. 认证操作
        subject.login(new UsernamePasswordToken("admin","admin"));

        //5. 授权操作(角色)
        System.out.println(subject.hasRole("超级管1理员"));

        //6. 授权操作(权限)
        System.out.println(subject.isPermitted("user:add"));

    }

jdbcRealm默认不支持权限校验,需要手动开启setPermissionLookupEnabled(true)

表需要按照它内部的结构来进行定义,需要表结构不一致,也可以使用自定义的校验sql

CustomRealm(自定义)推荐

需要手动创建CustomRealm,并且继承AuthorizingRealm ,

认证

重写doGetAuthenticationInfo方法完成自定义Realm认证

public class CustomRealm extends AuthorizingRealm {


    /**
     * 认证方法,只需要完成用户名校验即可,密码校验由Shiro内部完成
     * @param token  用户传入的用户名和密码
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1. 基于Token获取用户名
        String username = (String) token.getPrincipal();

        //2. 判断用户名(非空)
        if(StringUtils.isEmpty(username)){
            // 返回null,会默认抛出一个异常,org.apache.shiro.authc.UnknownAccountException
            return null;
        }

        //3. 如果用户名不为null,基于用户名查询用户信息
        User user = this.findUserByUsername(username);

        //4. 判断user对象是否为null
        if(user == null){
            return null;
        }

        //5. 声明AuthenticationInfo对象,并填充用户信息
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"CustomRealm!!");
   
        //6. 返回info
        return info;
    }

校验同其他real基本相同

认证(密码加密加盐)

虽然MD5加密不可逆,但是又一些网站可以把大量常用的密码加密后的结果存储起来,这样MD5的加密也可能会被破解

密码存储的时候需要加密加盐,还需要把对应的salt存储起来,认证时需要拿到对应的salt进行加密然后比较

public class CustomRealm extends AuthorizingRealm {

    {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(1024);
        this.setCredentialsMatcher(matcher);
    }


    /**
     * 认证方法,只需要完成用户名校验即可,密码校验由Shiro内部完成
     * @param token  用户传入的用户名和密码
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1. 基于Token获取用户名
        String username = (String) token.getPrincipal();

        //2. 判断用户名(非空)
        if(StringUtils.isEmpty(username)){
            // 返回null,会默认抛出一个异常,org.apache.shiro.authc.UnknownAccountException
            return null;
        }

        //3. 如果用户名不为null,基于用户名查询用户信息
        User user = this.findUserByUsername(username);

        //4. 判断user对象是否为null
        if(user == null){
            return null;
        }

        //5. 声明AuthenticationInfo对象,并填充用户信息
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"CustomRealm!!");
        // 设置盐!
        info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
        //6. 返回info
        return info;
    }

授权

授权是在认证之后的操作,授权操作需要重写doGetAuthorizationInfo方法

    // 授权方法,授权是在认证之后的操作
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //1. 获取认证用户的信息
        User user = (User) principals.getPrimaryPrincipal();

        //2. 基于用户信息获取当前用户拥有的角色。
        Set<String> roleSet = this.findRolesByUser();

        //3. 基于用户拥有的角色查询权限信息
        Set<String> permSet = this.findPermsByRoleSet(roleSet);

        //4. 声明AuthorizationInfo对象作为返回值,传入角色信息和权限信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roleSet);
        info.setStringPermissions(permSet);

        //5. 返回
        return info;
    }

 shiro整合web的流程

shiro不太适合前后端分离的项目,前后端分离的项目,推荐使用JWT

shiro整合springboot

pom

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.0</version>
        </dependency>

    </dependencies>

application.yml

shiro:
  loginUrl: /login.html
  unauthorizedUrl: /401.html   # 针对过滤器链生效,针对注解是不生效的

配置类

@Configuration
public class ShiroConfig {

    @Bean
    public SessionManager sessionManager(RedisSessionDAO sessionDAO) {
        DefaultRedisWebSessionManager sessionManager = new DefaultRedisWebSessionManager();
        sessionManager.setSessionDAO(sessionDAO);
        return sessionManager;
    }



    @Bean
    public DefaultWebSecurityManager securityManager(ShiroRealm realm, SessionManager sessionManager, RedisCacheManager redisCacheManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setSessionManager(sessionManager);
        // 设置CacheManager,提供与Redis交互的Cache对象
        securityManager.setCacheManager(redisCacheManager);
        return securityManager;
    }

    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition shiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/login.html","anon");
        filterChainDefinitionMap.put("/user/logout","logout");
        filterChainDefinitionMap.put("/user/**","anon");
        filterChainDefinitionMap.put("/item/rememberMe","user");
        filterChainDefinitionMap.put("/item/authentication","authc");
        filterChainDefinitionMap.put("/item/select","rolesOr[超级管理员,运营]");
        filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]");
        filterChainDefinitionMap.put("/**","authc");

        shiroFilterChainDefinition.addPathDefinitions(filterChainDefinitionMap);

        return shiroFilterChainDefinition;
    }

    @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }")
    protected String loginUrl;

    @Value("#{ @environment['shiro.successUrl'] ?: '/' }")
    protected String successUrl;

    @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }")
    protected String unauthorizedUrl;


    @Bean
    protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {

        //1. 构建ShiroFilterFactoryBean工厂
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        //2. 设置了大量的路径
        filterFactoryBean.setLoginUrl(loginUrl);
        filterFactoryBean.setSuccessUrl(successUrl);
        filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        //3. 设置安全管理器
        filterFactoryBean.setSecurityManager(securityManager);

        //4. 设置过滤器链
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());

        //5. 设置自定义过滤器 , 这里一定要手动的new出来这个自定义过滤器,如果使用Spring管理自定义过滤器,会造成无法获取到Subject
        filterFactoryBean.getFilters().put("rolesOr",new RolesOrAuthorizationFilter());



        //6. 返回工厂
        return filterFactoryBean;
    }


}

shiro的过滤器

 角色校验使用roles

权限校验使用perms

自定义过滤器

写自定义过滤器

public class RolesOrAuthorizationFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        // 获取主体subject
        Subject subject = getSubject(request, response);
        // 将传入的角色转成数组操作
        String[] rolesArray = (String[]) mappedValue;
        // 健壮性校验
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }
        // 开始校验
        for (String role : rolesArray) {
            if(subject.hasRole(role)){
                return true;
            }
        }

        return false;
    }
}

将自定义过滤器配置给shiro

shiro配置文件中将自定义过滤器配置进去

    @Bean
    protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {

        //1. 构建ShiroFilterFactoryBean工厂
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        //2. 设置了大量的路径
        filterFactoryBean.setLoginUrl(loginUrl);
        filterFactoryBean.setSuccessUrl(successUrl);
        filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        //3. 设置安全管理器
        filterFactoryBean.setSecurityManager(securityManager);

        //4. 设置过滤器链
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());

        //5. 设置自定义过滤器 , 这里一定要手动的new出来这个自定义过滤器,如果使用Spring管理自定义过滤器,会造成无法获取到Subject
        filterFactoryBean.getFilters().put("rolesOr",new RolesOrAuthorizationFilter());



        //6. 返回工厂
        return filterFactoryBean;
    }

加了@Bean注解的方法的参数值也都是从spring容器中获取

springboot项目中,默认有一个过滤器

使用注解授权

@RequiresRoles(value={"role1","role2"}) 

注解进行授权时,是基于对Controller类进行代理,在前置增强中对请求进行权限校验

 在SpringBoot中注解默认就生效,是因为自动装配中,已经配置好了对注解的支持

注解的形式无法将错误页面的信息定位到401.html,因为配置的这种路径,只针对过滤器链有效,注解无效。为了实现友好提示的效果,可以配置异常处理器,@RestControllerAdvice,@ControllerAdvice  

记住我,remember me

springboot自动装配

 rememberMe是基于user过滤器实现的,适用于安全等级较低的页面

只要登陆过,不需要再次登录

认证登录时,添加rememberMe

UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(rememberMe != null && "on".equals(rememberMe));
subject.login(token);

认证后,需要以浏览器的cookie和后台的user对象绑定,进行持久化,所以需要user序列化(实现Serializable)

需要在realm授权方法前重新鉴权(因为cookie绑定的是认证成功后,返回的第一个参数,而第一个参数和授权方法中参数能获得到的用户信息是一个内容。直接在授权方法中先做认证判断 )

Shiro在认证成功后,可以不依赖Web容器的Session,也可以依赖!

在SpringBoot自动装配之后,Shiro默认将HttpSession作为存储用户认证成功信息的位置。

但是SpringBoot也提供了一个基于JVM内存(HashMap)存储用户认证信息的位置。

使用springboot提供的MemorySession来存储用户认证信息:

修改Shiro默认使用的SessionDAO,修改为默认构建好的MemorySessionDAO

// 构建管理SessionDAO的SessionManager
@Bean
public SessionManager sessionManager(SessionDAO sessionDAO) {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setSessionDAO(sessionDAO);
    return sessionManager;
}

@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm realm,SessionManager sessionManager){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);
	// 将使用MemorySessionDAO的SessionManager注入到SecurityManager
    securityManager.setSessionManager(sessionManager);
    return securityManager;
}

将认证信息存储在redis中,可以实现分布式系统的功能

重写SessionDAO (extends AbstractSessionDAO),实现redis的相关操作

@Component
public class RedisSessionDAO extends AbstractSessionDAO {

    @Resource
    private RedisTemplate redisTemplate;

    // 存储到Redis时,sessionId作为key,Session作为Value
    // sessionId就是一个字符串
    // Session可以和sessionId绑定到一起,绑定之后,可以基于Session拿到sessionId
    // 需要给Key设置一个统一的前缀,这样才可以方便通过keys命令查看到所有关联的信息

    private final String SHIOR_SESSION = "session:";

    @Override
    protected Serializable doCreate(Session session) {
        System.out.println("Redis---doCreate");
        //1. 基于Session生成一个sessionId(唯一标识)
        Serializable sessionId = generateSessionId(session);

        //2. 将Session和sessionId绑定到一起(可以基于Session拿到sessionId)
        assignSessionId(session, sessionId);

        //3. 将 前缀:sessionId 作为key,session作为value存储
        redisTemplate.opsForValue().set(SHIOR_SESSION + sessionId,session,30, TimeUnit.MINUTES);

        //4. 返回sessionId
        return sessionId;
    }

 	@Override
    protected Session doReadSession(Serializable sessionId) {
        //1. 基于sessionId获取Session (与Redis交互)
        if (sessionId == null) {
            return null;
        }
        Session session = (Session) redisTemplate.opsForValue().get(SHIOR_SESSION + sessionId);
        if (session != null) {
            redisTemplate.expire(SHIOR_SESSION + sessionId,30,TimeUnit.MINUTES);
        }
        return session;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        System.out.println("Redis---update");
        //1. 修改Redis中session
        if(session == null){
            return ;
        }
        redisTemplate.opsForValue().set(SHIOR_SESSION + session.getId(),session,30, TimeUnit.MINUTES);
    }

    @Override
    public void delete(Session session) {
        // 删除Redis中的Session
        if(session == null){
            return ;
        }
        redisTemplate.delete(SHIOR_SESSION + session.getId());
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set keys = redisTemplate.keys(SHIOR_SESSION + "*");

        Set<Session> sessionSet = new HashSet<>();
        // 尝试修改为管道操作,pipeline(Redis的知识)
        for (Object key : keys) {
            Session session = (Session) redisTemplate.opsForValue().get(key);
            sessionSet.add(session);
        }
        return sessionSet;
    }
}

将RedisSessionDAO交给SessionManager

@Bean
public SessionManager sessionManager(RedisSessionDAO sessionDAO) {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setSessionDAO(sessionDAO);
    return sessionManager;
}

将SessionManager注入到SecurityManager

@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm realm,SessionManager sessionManager){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);
    securityManager.setSessionManager(sessionManager);
    return securityManager;
}

一次请求,访问了多次redis

解决方案:把请求结果放到request中

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

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

相关文章

V2.0-在记事本功能上添加fork和wait

第一篇只是简单使用了open&#xff0c;read,write,lseek实现了基本的记事本功能&#xff1b; 但是当前的系统是linux&#xff0c;应该发挥他的多进程&#xff0c;多线程的作用&#xff1b; 所以&#xff0c;本篇添加创建子进程和父进程等待子进程退出的功能。 有几个注意点&a…

如何创建新一代Web3企业

日前&#xff0c;我们对话了Sui基金会的增长负责人Koh Kim&#xff0c;对如何成功构建持续发展的企业等话题展开讨论。 您在Sui基金会的工作重点帮助开发者&#xff0c;让他们从产品开发的早期阶段成长为强大且具有潜力的企业领导者。可以简单分享一下您为此目标创建的计划吗&…

Linux进程地址空间——下篇

目录 一.深入了解进程地址空间&#xff1a; 单个进程与进程地址空间与物理内存之间的联系图&#xff1a; 多个进程与进程地址空间与物理内存之间的联系图&#xff1a; 二.为什么会存在进程地址空间呢&#xff1f; 作用1&#xff1a;进程地址空间的存在&#xff0c;保证了其他…

Flutter 笔记 | Flutter 文件IO、网络请求、JSON、日期与国际化

文件IO操作 Dart的 IO 库包含了文件读写的相关类&#xff0c;它属于 Dart 语法标准的一部分&#xff0c;所以通过 Dart IO 库&#xff0c;无论是 Dart VM 下的脚本还是 Flutter&#xff0c;都是通过 Dart IO 库来操作文件的&#xff0c;不过和 Dart VM 相比&#xff0c;Flutte…

6.1 进程的创建和回收

目录 进程概念 程序 进程 进程内容 进程控制块 进程类型 进程状态 常用命令 查看进程信息 进程相关命令 进程的创建和结束 子进程概念 子进程创建-fork 父子进程 进程结束-exit/_exit 进程结束-exit-示例1 进程结束-exit-示例2 进程回收 进程回收-wait 进程回…

企业数字化转型,为什么会加快商业智能BI的发展

对于企业数字化转型来说&#xff0c;数据是其中提到最多的词汇。当今世界&#xff0c;随着人们认识到数据的重要性&#xff0c;明白了数据发挥价值的方式及其意义&#xff0c;数据资产就成为数字化转型企业需要掌握利用的关键。 数据可视化 - 派可数据商业智能BI可视化分析平台…

服务windows服务+辅助角色服务

1、vs2022新建一个windows服务项目 2、修改服务参数 &#xff08;1&#xff09;AutoLog: 是否将事件写入到windows的事件日志中。 &#xff08;2&#xff09;canpauseandContinue:服务是否可以暂停和继续 3、添加服务安装程序 在界面内右击鼠标 新建一个服务、新建后如下图&a…

【运维】speedtest测试

目录 docker 布署 布署云端 docker布署 云端放置于已有容器里 librespeed/speedtest: Self-hosted Speedtest for HTML5 and more. Easy setup, examples, configurable, mobile friendly. Supports PHP, Node, Multiple servers, and more (github.com) docker 布署 获取…

探讨生产环境下缓存雪崩的几种场景及解决方案

本文首发自「慕课网」&#xff08;www.imooc.com&#xff09;&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"或慕课网公众号&#xff01; 作者&#xff1a;大能 | 慕课网讲师 缓存我们经常使用&#xff0c;但是有时候我们…

如何撤消 Git 中最新的本地提交?

在使用Git进行版本控制时&#xff0c;有时我们可能会犯下错误或者想要撤销最新的本地提交。Git提供了一些强大的工具和命令&#xff0c;使我们能够轻松地撤消最近的提交并修复错误。 本文将详细介绍如何在Git中撤消最新的本地提交。 步骤1&#xff1a;查看提交历史 在撤消最新…

Centos7安装Java8(在线安装避坑详细安装)

开篇语&#xff1a; 喜欢在一个明媚阳光的午后 坐在那夕阳斑驳的南墙下 听着风起 闻着花香 望着远山 身边是你 如此便觉得很好 1.查看目前环境 rpm -qa|grep jdk在这里我们会发现&#xff0c;原有系统安装有jdk&#xff0c;如果对于jdk有要求&#xff0c;我们就需要重新安装jdk…

Liunx网络基础(3)传输层(TCP/UDP)可靠传输、字节流传输等

传输层协议 传输层协议解析: 负责两端之间的数据传输; TCP/ UDP 1. UDP UDP: 用户数据报协议&#xff0c;无连接&#xff0c;不可靠&#xff0c;面向数据报传输 重点: 协议格式&#xff0c;协议特性&#xff0c;特性对于编程的影响 协议格式&#xff1a; 16位源端口 & 16位…

2023-05-29 用 fltk gui库编写一个打字练习程序

用 fltk gui库编写一个打字练习程序 前言一、FLTK GUI 库二、使用步骤1.引入库2.使用代码 总结 前言 给孩子练习键盘打字, 发现终端还是欠点意思, 研究了一下gui, 最终用 fltk库弄了一个. 对于没有接触过gui的人, 发现, 编程的逻辑和终端区别很大, 很繁琐, 可能需要适应适应,…

Windows远程Centos7图形化界面

一、centos7服务器安装tigervnc 1、更新yum源 yum update 2、安装tigervnc yum -y install tigervnc* 3、启动vnc vncserver &#xff08;1&#xff09;执行命令后需要输入密码 &#xff08;2&#xff09;再次输入密码 注意&#xff1a;密码一定要记住&#xff0c;方便以…

链表反转方法汇总

反转范围之前有节点&#xff0c;prev就指向该节点&#xff0c;没有就prevnull&#xff1b; 一、头插法 class Solution {public ListNode reverseList(ListNode head) {ListNode header new ListNode(-1);ListNode cur head;while(cur ! null) {ListNode tmp cur.next;cur.…

LabVIEWCompactRIO 开发指南第六章41 同步模块

同步模块 同时运行的模块每个通道有一个ADC&#xff0c;并且采集数据时通道之间没有明显的偏差。同步模块的两个子类别&#xff0c;按需和三角积分&#xff0c;通过SPI总线传输数据&#xff0c;并受到其他SPI总线模块的所有规格和挑战的约束。 按需转换 表6.1.具有按需转换的…

Postgresql源码(104)子连接提升过程pull_up_sublinks

1 场景构造 drop table student; create table student(sno int primary key, sname varchar(10), ssex int); insert into student values(1, stu1, 0); insert into student values(2, stu2, 1); insert into student values(3, stu3, 1); insert into student values(4, st…

模块化

一、目标 能够说出模块化的好处能够知道CommonJS规定了哪些内容能够说出Node.js中模块的三大分类各自是什么能够使用npm管理包能够了解什么是规范的包结构能够了解模块的加载机制 二、目录 模块化的基本概念Node.js中模块的分类npm与包模块的加载机制 1.模块化的基本概念 …

spring源码解读

深入了解Spring Bean Java bean和spring bean区别 Java bean的属性私有&#xff0c;只能通过get和set方法来对属性进行操作。Spring bean是由spring容器生成和管理的对象。 spring Bean的定义方式 xml文件 。 声明式。 bean注解。 声明式。 component注解。声明式。 Bea…

房产中介APP开发功能有哪些?

房产中介APP开发功能有哪些&#xff1f; 1. 发布信息。中介或房东通过房地产中介APP客户端发布出租房屋的相关信息。 2. 房屋搜查。根据不同类型的房源进行分类&#xff0c;如公寓、整租、合租、写字楼、办公楼等&#xff0c;也可以根据不同的位置信息、商圈、距…