30分钟学会如何使用Shiro

2023-10-27

http://www.cnblogs.com/learnhow/p/5694876.html

 

一、架构

要学习如何使用Shiro必须先从它的架构谈起,作为一款安全框架Shiro的设计相当精妙。Shiro的应用不依赖任何容器,它也可以在JavaSE下使用。但是最常用的环境还是JavaEE。下面以用户登录为例:

(1)使用用户的登录信息创建令牌

UsernamePasswordToken token = new UsernamePasswordToken(username, password);

token可以理解为用户令牌,登录的过程被抽象为Shiro验证令牌是否具有合法身份以及相关权限。

(2)执行登陆动作

SecurityUtils.setSecurityManager(securityManager); // 注入SecurityManager
Subject subject = SecurityUtils.getSubject(); // 获取Subject单例对象
subject.login(token); // 登陆

Shiro的核心部分是SecurityManager,它负责安全认证与授权。Shiro本身已经实现了所有的细节,用户可以完全把它当做一个黑盒来使用。SecurityUtils对象,本质上就是一个工厂类似Spring中的ApplicationContext。Subject是初学者比较难于理解的对象,很多人以为它可以等同于User,其实不然。Subject中文翻译:项目,而正确的理解也恰恰如此。它是你目前所设计的需要通过Shiro保护的项目的一个抽象概念。通过令牌(token)与项目(subject)的登陆(login)关系,Shiro保证了项目整体的安全。

(3)判断用户

Shiro本身无法知道所持有令牌的用户是否合法,因为除了项目的设计人员恐怕谁都无法得知。因此Realm是整个框架中为数不多的必须由设计者自行实现的模块,当然Shiro提供了多种实现的途径,本文只介绍最常见也最重要的一种实现方式——数据库查询。

(4)两条重要的英文

我在学习Shiro的过程中遇到的第一个障碍就是这两个对象的英文名称:AuthorizationInfo,AuthenticationInfo。不用怀疑自己的眼睛,它们确实长的很像,不但长的像,就连意思都十分近似。

在解释它们前首先必须要描述一下Shiro对于安全用户的界定:和大多数操作系统一样。用户具有角色和权限两种最基本的属性。例如,我的Windows登陆名称是learnhow,它的角色是administrator,而administrator具有所有系统权限。这样learnhow自然就拥有了所有系统权限。那么其他人需要登录我的电脑怎么办,我可以开放一个guest角色,任何无法提供正确用户名与密码的未知用户都可以通过guest来登录,而系统对于guest角色开放的权限极其有限。

同理,Shiro对用户的约束也采用了这样的方式。AuthenticationInfo代表了用户的角色信息集合,AuthorizationInfo代表了角色的权限信息集合。如此一来,当设计人员对项目中的某一个url路径设置了只允许某个角色或具有某种权限才可以访问的控制约束的时候,Shiro就可以通过以上两个对象来判断。说到这里,大家可能还比较困惑。先不要着急,继续往后看就自然会明白了。

二、实现Realm

如何实现Realm是本文的重头戏,也是比较费事的部分。这里大家会接触到几个新鲜的概念:缓存机制、散列算法、加密算法。由于本文不会专门介绍这些概念,所以这里仅仅抛砖引玉的谈几点,能帮助大家更好的理解Shiro即可。

(1)缓存机制

Ehcache是很多Java项目中使用的缓存框架,Hibernate就是其中之一。它的本质就是将原本只能存储在内存中的数据通过算法保存到硬盘上,再根据需求依次取出。你可以把Ehcache理解为一个Map<String,Object>对象,通过put保存对象,再通过get取回对象。

复制代码

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shirocache">
    <diskStore path="java.io.tmpdir" />
    
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>
</ehcache>

复制代码

以上是ehcache.xml文件的基础配置,timeToLiveSeconds为缓存的最大生存时间,timeToIdleSeconds为缓存的最大空闲时间,当eternal为false时ttl和tti才可以生效。更多配置的含义大家可以去网上查询。

(2)散列算法与加密算法

md5是本文会使用的散列算法,加密算法本文不会涉及。散列和加密本质上都是将一个Object变成一串无意义的字符串,不同点是经过散列的对象无法复原,是一个单向的过程。例如,对密码的加密通常就是使用散列算法,因此用户如果忘记密码只能通过修改而无法获取原始密码。但是对于信息的加密则是正规的加密算法,经过加密的信息是可以通过秘钥解密和还原。

(3)用户注册

请注意,虽然我们一直在谈论用户登录的安全性问题,但是说到用户登录首先就是用户注册。如何保证用户注册的信息不丢失,不泄密也是项目设计的重点。

复制代码

public class PasswordHelper {
    private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
    private String algorithmName = "md5";
    private final int hashIterations = 2;

    public void encryptPassword(User user) {
        // User对象包含最基本的字段Username和Password
        user.setSalt(randomNumberGenerator.nextBytes().toHex());
        // 将用户的注册密码经过散列算法替换成一个不可逆的新密码保存进数据,散列过程使用了盐
        String newPassword = new SimpleHash(algorithmName, user.getPassword(),
                ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex();
        user.setPassword(newPassword);
    }
}

复制代码

如果你不清楚什么叫加盐可以忽略散列的过程,只要明白存储在数据库中的密码是根据户注册时填写的密码所产生的一个新字符串就可以了。经过散列后的密码替换用户注册时的密码,然后将User保存进数据库。剩下的工作就丢给UserService来处理。

那么这样就带来了一个新问题,既然散列算法是无法复原的,当用户登录的时候使用当初注册时的密码,我们又应该如何判断?答案就是需要对用户密码再次以相同的算法散列运算一次,再同数据库中保存的字符串比较。

(4)匹配

CredentialsMatcher是一个接口,功能就是用来匹配用户登录使用的令牌和数据库中保存的用户信息是否匹配。当然它的功能不仅如此。本文要介绍的是这个接口的一个实现类:HashedCredentialsMatcher

复制代码

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
    // 声明一个缓存接口,这个接口是Shiro缓存管理的一部分,它的具体实现可以通过外部容器注入
    private Cache<String, AtomicInteger> passwordRetryCache;

    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        AtomicInteger retryCount = passwordRetryCache.get(username);
        if (retryCount == null) {
            retryCount = new AtomicInteger(0);
            passwordRetryCache.put(username, retryCount);
        }
        // 自定义一个验证过程:当用户连续输入密码错误5次以上禁止用户登录一段时间
        if (retryCount.incrementAndGet() > 5) {
            throw new ExcessiveAttemptsException();
        }
        boolean match = super.doCredentialsMatch(token, info);
        if (match) {
            passwordRetryCache.remove(username);
        }
        return match;
    }
}

复制代码

可以看到,这个实现里设计人员仅仅是增加了一个不允许连续错误登录的判断。真正匹配的过程还是交给它的直接父类去完成。连续登录错误的判断依靠Ehcache缓存来实现。显然match返回true为匹配成功。

(5)获取用户的角色和权限信息

说了这么多才到我们的重点Realm,如果你已经理解了Shiro对于用户匹配和注册加密的全过程,真正理解Realm的实现反而比较简单。我们还得回到上文提及的两个非常类似的对象AuthorizationInfo和AuthenticationInfo。因为Realm就是提供这两个对象的地方。

复制代码

public class UserRealm extends AuthorizingRealm {
    // 用户对应的角色信息与权限信息都保存在数据库中,通过UserService获取数据
    private UserService userService = new UserServiceImpl();

    /**
     * 提供用户信息返回权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 根据用户名查询当前用户拥有的角色
        Set<Role> roles = userService.findRoles(username);
        Set<String> roleNames = new HashSet<String>();
        for (Role role : roles) {
            roleNames.add(role.getRole());
        }
        // 将角色名称提供给info
        authorizationInfo.setRoles(roleNames);
        // 根据用户名查询当前用户权限
        Set<Permission> permissions = userService.findPermissions(username);
        Set<String> permissionNames = new HashSet<String>();
        for (Permission permission : permissions) {
            permissionNames.add(permission.getPermission());
        }
        // 将权限名称提供给info
        authorizationInfo.setStringPermissions(permissionNames);

        return authorizationInfo;
    }

    /**
     * 提供账户信息返回认证信息
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        User user = userService.findByUsername(username);
        if (user == null) {
            // 用户名不存在抛出异常
            throw new UnknownAccountException();
        }
        if (user.getLocked() == 0) {
            // 用户被管理员锁定抛出异常
            throw new LockedAccountException();
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
                user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
        return authenticationInfo;
    }
}

复制代码

根据Shiro的设计思路,用户与角色之前的关系为多对多,角色与权限之间的关系也是多对多。在数据库中需要因此建立5张表,分别是用户表(存储用户名,密码,盐等)、角色表(角色名称,相关描述等)、权限表(权限名称,相关描述等)、用户-角色对应中间表(以用户ID和角色ID作为联合主键)、角色-权限对应中间表(以角色ID和权限ID作为联合主键)。具体dao与service的实现本文不提供。总之结论就是,Shiro需要根据用户名和密码首先判断登录的用户是否合法,然后再对合法用户授权。而这个过程就是Realm的实现过程。

(6)会话

用户的一次登录即为一次会话,Shiro也可以代替Tomcat等容器管理会话。目的是当用户停留在某个页面长时间无动作的时候,再次对任何链接的访问都会被重定向到登录页面要求重新输入用户名和密码而不需要程序员在Servlet中不停的判断Session中是否包含User对象。启用Shiro会话管理的另一个用途是可以针对不同的模块采取不同的会话处理。以淘宝为例,用户注册淘宝以后可以选择记住用户名和密码。之后再次访问就无需登陆。但是如果你要访问支付宝或购物车等链接依然需要用户确认身份。当然,Shiro也可以创建使用容器提供的Session最为实现。

三、与SpringMVC集成

有了注册模块和Realm模块的支持,下面就是如何与SpringMVC集成开发。有过框架集成经验的同学一定知道,所谓的集成基本都是一堆xml文件的配置,Shiro也不例外。

(1)配置前端过滤器

先说一个题外话,Filter是过滤器,interceptor是拦截器。前者基于回调函数实现,必须依靠容器支持。因为需要容器装配好整条FilterChain并逐个调用。后者基于代理实现,属于AOP的范畴。

如果希望在WEB环境中使用Shiro必须首先在web.xml文件中配置

复制代码

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <display-name>Shiro_Project</display-name>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- 将Shiro的配置文件交给Spring监听器初始化 -->
        <param-value>classpath:spring.xml,classpath:spring-shiro-web.xml</param-value>
    </context-param>
    <context-param>
        <param-name>log4jConfigLoaction</param-name>
        <param-value>classpath:log4j.properties</param-value>
    </context-param>
    <!-- shiro配置 开始 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- shiro配置 结束 -->
</web-app>

复制代码

熟悉Spring配置的同学可以重点看有绿字注释的部分,这里是使Shiro生效的关键。由于项目通过Spring管理,因此所有的配置原则上都是交给Spring。DelegatingFilterProxy的功能是通知Spring将所有的Filter交给ShiroFilter管理。

接着在classpath路径下配置spring-shiro-web.xml文件

复制代码

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd    
                        http://www.springframework.org/schema/context    
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd    
                        http://www.springframework.org/schema/mvc    
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 缓存管理器 使用Ehcache实现 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
    </bean>

    <!-- 凭证匹配器 -->
    <bean id="credentialsMatcher" class="utils.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager" />
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="2" />
        <property name="storedCredentialsHexEncoded" value="true" />
    </bean>

    <!-- Realm实现 -->
    <bean id="userRealm" class="utils.UserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
    </bean>

    <!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/" />
        <property name="unauthorizedUrl" value="/" />
        <property name="filterChainDefinitions">
            <value>
                /authc/admin = roles[admin]
                /authc/** = authc
                /** = anon
            </value>
        </property>
    </bean>

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>

复制代码

需要注意filterChainDefinitions过滤器中对于路径的配置是有顺序的,当找到匹配的条目之后容器不会再继续寻找。因此带有通配符的路径要放在后面。三条配置的含义是: /authc/admin需要用户有用admin权限、/authc/**用户必须登录才能访问、/**其他所有路径任何人都可以访问。

说了这么多,大家一定关心在Spring中引入Shiro之后到底如何编写登录代码呢。

复制代码

@Controller
public class LoginController {
    @Autowired
    private UserService userService;

    @RequestMapping("login")
    public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password) {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (IncorrectCredentialsException ice) {
            // 捕获密码错误异常
            ModelAndView mv = new ModelAndView("error");
            mv.addObject("message", "password error!");
            return mv;
        } catch (UnknownAccountException uae) {
            // 捕获未知用户名异常
            ModelAndView mv = new ModelAndView("error");
            mv.addObject("message", "username error!");
            return mv;
        } catch (ExcessiveAttemptsException eae) {
            // 捕获错误登录过多的异常
            ModelAndView mv = new ModelAndView("error");
            mv.addObject("message", "times error");
            return mv;
        }
        User user = userService.findByUsername(username);
        subject.getSession().setAttribute("user", user);
        return new ModelAndView("success");
    }
}

复制代码

登录完成以后,当前用户信息被保存进Session。这个Session是通过Shiro管理的会话对象,要获取依然必须通过Shiro。传统的Session中不存在User对象。

复制代码

@Controller
@RequestMapping("authc")
public class AuthcController {
    // /authc/** = authc 任何通过表单登录的用户都可以访问
    @RequestMapping("anyuser")
    public ModelAndView anyuser() {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getSession().getAttribute("user");
        System.out.println(user);
        return new ModelAndView("inner");
    }

    // /authc/admin = user[admin] 只有具备admin角色的用户才可以访问,否则请求将被重定向至登录界面
    @RequestMapping("admin")
    public ModelAndView admin() {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getSession().getAttribute("user");
        System.out.println(user);
        return new ModelAndView("inner");
    }
}

复制代码

四、总结

Shiro是一个功能很齐全的框架,使用起来也很容易,但是要想用好却有相当难度。完整项目的源码就不在这里提供了,需要交流的同学可以给我留言或直接查阅张开涛的博客。如果大家感觉我写的还可以,也希望能给我一些反馈意见。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

30分钟学会如何使用Shiro 的相关文章

  • 大一统,windows下安装linux的最新方式

    抛弃虚拟机和双系统 在windows下使用linux 为啥要执着于linux 入坑指南 安装windows terminal 开启子系统功能 下载Ubuntu系统 更改命令主题 把oh my zsh 项目 Clone 下来 复制 zshrc
  • RFC2367 PF_KEY键管理API

    组织 中国互动出版网 http www china pub com RFC文档中文翻译计划 http www china pub com compters emook aboutemook htm E mail ouyang china p
  • 基于C++的非线性方程的解法研究及实现

    基于C 的非线性方程的解法研究及实现 前言 关于非线性方程f x 0的根的解法目前分为三类 二分法 试值法和不动点迭代法 本文实例为寻找f x x2 2x在区间 1 3 内精度在0 05以内的实根 代码中的set用于保存答案 1 二分法 二
  • 华为云服务器最新信息,云服务器拉新

    云服务器拉新 内容精选 换一换 当云服务器密码即将过期 密码泄露或首次登录时 首次登录云服务器建议您修改初始密码 您可以参考本节操作在操作系统内部修改云服务器密码 优先推荐您参考在控制台重置云服务器密码 在控制台重置实例的登录密码 可以登录
  • 手把手教你如何自己设计实现一个深度学习框架(附代码实现)

    本文首先从深度学习的流程开始分析 对神经网络中的关键组件抽象 确定基本框架 然后再对框架里各个组件进行代码实现 最后基于这个框架实现了一个 MNIST 分类的示例 并与 Tensorflow 做了简单的对比验证 喜欢本文 就请点赞 收藏 关
  • HBase写吞吐场景资源消耗量化分析及优化

    一 概述 HBase 是一个基于 Google BigTable 论文设计的高可靠性 高性能 可伸缩的分布式存储系统 网上关于 HBase 的文章很多 官方文档介绍的也比较详细 本篇文章不介绍HBase基本的细节 本文从 HBase 写链路
  • Jupyter 如何使用 args = parser.parse_args()

    最近在女朋友的催促下 啃代码啃得脑壳疼 遇到了一个问题 python文件里面的参数不能直接在jupyter中使用 放在jupyter中的时候就会报错 如下所示 parser argparse ArgumentParser parser ad
  • Vscode配置C/C++环境出现报错,导致不能运行代码,报错如下:

    Vscode配置C C 环境出现报错 导致不能运行代码 报错如下 问题描述 gcc 无法将 gcc 项识别为 cmdlet 函数 脚本文件或可运行程序的名称 请检查名称的拼写 如果包括路径 请确保路径正确 然后再试一次 所在位置 行 1 字
  • YUV学习,详解

    YUV 格式详解 只看这一篇就够了 分类标准 首先 我们可以将YUV格式按照数据大小分为三个格式 YUV420 YUV422 YUV444 由于人眼对Y的敏感度远超于对u和v的敏感度 所以有时候可以多个Y分量共用一组uv 这样既可以极大的节
  • Dreaming to Distill: Data-free Knowledge Transfer via DeepInversion

    Dreaming to Distill Data free Knowledge Transfer via DeepInversion 1 论文信息 论文标题 Dreaming to Distill Data free Knowledge T
  • java基础语法(精简版)

    文章目录 目录 文章目录 一 运算符 1 算数运算符 2 赋值运算符 3 关系运算符 比较运算符 4 逻辑运算符 5 三元运算符 6 字符的 操作 7 字符串的 操作 二 流程控制语句 1 顺序结构 2 分支结构 1 if语句 2 swit
  • Win10 系统下VisualStudio2019 配置点云库 PCL1.11.0

    目录 一 下载PCL1 11 0 二 安装PCL1 11 0 三 VS2019相关设置 四 配置PCL1 11 0 五 测试代码 六 附录 获取自己的链接库列表 一 下载PCL1 11 0 Github下载地址 https github c
  • docker: Error response from daemon: driver failed programming external connectivity on endpoint ranc

    docker Error response from daemon driver failed programming external connectivity on endpoint rancher server f62779d6c72
  • oracle--查询练习

    查询练习 1 查询工资大于12000的员工姓名和工资 select last name salary from employees where salary gt 12000 2 查询员工号为176的员工的姓名和部门号 select las
  • Qt基础-使用QSettings保存读取配置文件

    Qt基础 使用QSettings保存读取配置文件 QSettings类简介 存储 读取 QSettings类简介 QSettings可以用来存储或读取配置文件 不同于普通文本格式的字符串存取方式 QSettings可以存储bool值 字体Q
  • mysql 子查询

    一 mysql子查询 1 1 子查询定义 子查询 Sub Query 或者说内查询 Inner Query 也可以称作嵌套查询 Nested Query 是一种嵌套在其他 SQL 查询的 WHERE 子句中的查询 子查询用于为主查询返回其所
  • Java中的 BigDecimal正常使用

    点击关注 芋道源码 2022 04 10 10 33 收录于话题 芋道源码688个 点击上方 芋道源码 选择 设为星标 管她前浪 还是后浪 能浪的浪 才是好浪 每天 10 33 更新文章 每天掉亿点点头发 源码精品专栏 原创 Java 20
  • js内存泄漏及排查详解

    js内存泄漏及排查详解 常见内存泄漏及解决方案 内存泄漏 Memory Leak 是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放 造成系统内存的浪费 导致程序运行速度减慢甚至系统崩溃等严重后果 隐式全局变量 在局部作用域中 等
  • android read file system,(转载)Android出现“Read-only file system”解决办法

    下面介绍一篇Android出现 Read only file system 解决办法 有碰到这类问题的朋友可参考参考 Android 出现Read only file system的解决方法 输入命令 代码如下 复制代码 mount o r
  • .spec文件详解

    http blog sina com cn s blog 43b39e250100nnu4 html

随机推荐

  • 树莓派python opencv中用VideoCapture 处理rtsp视频流跳帧(丢帧)问题处理

    对于树莓派处理能力低 无法满足实时逐帧处理rtsp视频流 而opencv的VideoCapture又无法清除缓存直接找到最新帧问题 导致处理延时不断积累 查找了网上一些资料 有人提出建立自己的缓存队列 然后抽帧处理 较为复杂 不适于树莓派
  • python3.0实例_python3.0 模拟用户登录,三次错误锁定的实例

    python用户登录三次锁定怎么修改 1 normal user是存放用户id及密码的文件 2 lock file是存放被锁定的用户id的文档 默认为空 3 程序会对normal user里的合法用户id进行判断 若连续输入用户id错误达三
  • win10开机后电脑磁盘占用百分之百解决方法

    1 我的电脑 管理 服务与应用程序 服务 superfetch 禁用 重启 2 控制面板 管理工具 服务 找到 HomeGroupListener 家庭组 服务 禁用 3 关闭自动维护计划任务 选中磁盘C 属性 工具 对驱动器进行优化和碎片
  • 小程序监听页面的返回按钮方法

    不论是微信小程序还是支付宝小程序中都没有这个生命周期函数 但是老板要求实现这个方法 只好百度 想办法解决问题 具体用户的操作流程是 从第一个页面出发 走到第二个页面 然后又返回第一个页面 以下是我具体实现的代码 在第二个页面的 onShow
  • Jmeter(十三) - 从入门到精通 - JMeter定时器 - 上篇(详解教程)

    1 简介 用户实际操作时 并非是连续点击 而是存在很多停顿的情况 例如 用户需要时间阅读文字内容 填表 或者查找正确的链接等 为了模拟用户实际情况 在性能测试中我们需要考虑思考时间 若不认真考虑思考时间很可能会导致测试结果的失真 例如 估计
  • 人工智能芯片

    发展趋势 人工智能技术研究的催化剂 新型高性能计算架构 参考寒武纪芯片 华为NPU 高通5G芯片 计算芯片发展趋势 计算芯片架构逐渐向深度学习应用优化的趋势发展 从传统的CPU为主GPU为辅的因特尔处理器转变为GPU为主CPU为辅的结构 性
  • JS逆向之HOOK注入、调试、插桩

    一 使用油猴脚本进行注入 获取Cookie的加密参数 新建油猴脚本文件 UserScript name cookie的HOOK namespace http tampermonkey net version 0 1 description
  • 写论文和做项目中遇到的问题及其解决办法

    相比Microsoft Office Word 我更推荐用WPS Office Word编写论文 感觉后者用起来更轻松 1 利用Word自带的的标题样式快速给文档设置一到三级标题 2 Word文档中具有编辑器自带标题样式的文字前面都有小黑方
  • 【YOLOv5-6.x】设置可学习权重结合BiFPN(Add操作)

    文章目录 前言 修改yaml文件 以yolov5s为例 只修改一处 将Concat全部换成BiFPN Add 打印模型参数 修改common py 修改yolo py 修改train py 1 向优化器中添加BiFPN的权重参数 2 查看B
  • 疼失鸡鸡,吊唁鸡鸡

    为了忘却的纪念 今天是我痛失鸡鸡的日子 一个好鸡鸡是很难找的 一个集群的鸡鸡跟难找的 为了吊唁我的鸡鸡 我觉得为它写一篇文章
  • Matplot pyplot绘制单图,多子图不同样式详解,这一篇就够了

    Matplot pyplot绘制单图 多子图不同样式详解 这一篇就够了 1 单图单线 2 单图多线不同样式 红色圆圈 蓝色实线 绿色三角等 2 1 4 支持的所有颜色及样式 3 使用关键字字符串绘图 data 可指定依赖值为 numpy r
  • 【BLE】蓝牙抓包器 Ellisys 使用说明

    BLE 蓝牙抓包器 Ellisys 使用说明 常用功能 设备过滤 抓包的类型 添加观察的项目 协议解析 连接过程 Connection Indication LLCP Feature Request Response LLCP Length
  • Java多线程通信-CountDownLatch(闭锁)

    一 CountDownLatch 闭锁 闭锁是一个同步工具类 它可以延迟线程的进度直到其到达终止状态 闭锁的作用相当于一扇门 在到达结束状态之前 这扇门是关闭的 并且不允许任何进程通过 当到达结束状态时 这扇门会打开并允许所有的线程通过 当
  • wangeditor3.0上传本地图片和本地视频至服务器

    1 效果 2 注意 我下载的3 0版本 3 组件 在components文件里创建一个wangEditoe vue文件
  • Java基于opencv实现图像数字识别(一)

    Java基于opencv实现图像数字识别 一 最近分到了一个任务 要做数字识别 我分配到的任务是把数字一个个的分开 当时一脸懵逼 直接百度java如何分割图片中的数字 然后就百度到了用BufferedImage这个类进行操作 尝试着做了一下
  • Vue生命周期和钩子函数详解

    Vue生命周期和钩子函数详解 Vue生命周期介绍 组件每个阶段它的内部构造是不一样的 所以一般特定的钩子做特定的事 比如Ajax获取数据就可以在mounted阶段 从Vue实例被创建开始到该实例最终被销毁的整个过程叫做VUE的生命周期 在这
  • A*算法学习笔记

    1 算法思路 1 Dijkstra算法与A 算法 1 Dijkstra算法 贪心策略 优先队列 集合S 已确定的顶点集合 初始只含源点s 集合T 尚未确定的顶点集合 算法反复从集合T中选择当前到源点s最近的顶点u 将u加入集合S 然后对所有
  • 使用myisamchk命令修复表 只能修复myisam表 速度块

    快速检查 myisamchk im var lib mysql db1 只检查没有正常关闭的表 myisamchk iFm var lib mysql db1 仅显示标的重要信息 myisamchk eis var lib mysql db
  • 【Markdown】Typora配置图片上传

    文章目录 0 前言 1 确定需求 2 开始配置 2 1 软件储备 2 2 插件安装 2 3 gitee配置 3 其他配置 3 1 获取SMMS token 参考链接 0 前言 对于喜欢写Markdown文档的人来说 Typora无疑是一个写
  • 30分钟学会如何使用Shiro

    http www cnblogs com learnhow p 5694876 html 一 架构 要学习如何使用Shiro必须先从它的架构谈起 作为一款安全框架Shiro的设计相当精妙 Shiro的应用不依赖任何容器 它也可以在JavaS