权限验证框架之Shiro

news2024/11/24 1:54:07

文章目录

  • 前言
  • shiro 核心
  • 项目构建
    • 默认Session模式
      • 配置
      • 测试接口
      • Realm编写
      • 权限测试
        • 无权限测试
        • 登录测试
        • 权限测试
    • 前后端分离token
      • JWTFilter
      • 重写认证
      • 修改配置
  • 总结

前言

交替换个脑子,一直搞考研的东西,实在是无聊。所以顺便把工程上的东西,拿来遛一遛。你问我,为啥不是机器学习,深度学习,那玩意搞起来头更大,累了。权当是打游戏放松了,那么废话不多说,这里要玩玩的是Shiro,其实一开始我还是喜欢玩这个Security,不过后来,经常用这个人人开源,也就接触这个玩意了,说实话,先前用那个玩意的时候,也是习惯性的把shiro改成security,但是实话实说,太麻烦了,懒得改,所以的话,干脆就是直接使用这个Shiro。

当然关于权限验证,其实我们自己基于RBAC权限管理模型直接做一套都是可以的,基于Spring的AOP,快速做一个简单的这个是非常快的。包括,当初我写的那个WhitHoleV0.7版本其实那个用户端的权限验证都是自己做的。ok,说多了,我们来快速开始吧。

当然自己动手实现一个权限验证其实也不难,shiro只是提供了一个架子而已。后面有时间的话,我们可以直接自己写一个Shiro lite 或者security lite拿过来玩玩。

shiro 核心

ok,我们开始,首先的话,这个shiro由如下模块组成:
在这里插入图片描述

Authentication:身份认证/登录,验证用户是不是拥有相应的身份

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限

Session
Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web
环境的

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储

Web Support:Web 支持,可以非常容易的集成到Web 环境

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率

Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去

Testing:提供测试支持

“Run As”:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

从我们的使用角度来看,它的运行流程大致如下:
在这里插入图片描述
也是分为几个部分:

  1. subject:这个是对User信息的一些封装
  2. SecurityManager: 里面实现了对用户信息授权,认证的一些操作
  3. Realm: 和数据库打交道,比如验证用户权限,这个我们需要查表,那么这个时候,我们就需要这个玩意

也就是说,subject过来之后,通过Manager,去执行对于的执行权限的方法,在进行用户验证的时候,将使用到Realm,去读取数据,之后完成操作。

项目构建

默认Session模式

现在虽然比较流行的是这个前后端分离架构,用的是token,但是,很久以前,还没有分离的时候,还是用的这个,

那么在这边进行整合的时候是这样的:
在这里插入图片描述
然后我们导入一下,配置,我这里的话,还导入了这个web starter。这里做演示,我就不建表了。

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

        <!--引入shrio-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置

那么我们先来看到配置,看看我们看到了流程图,我们其实发现,就是说,我们的请求其实是首先到了一个过滤器,然后在这个过滤器里面进行操作,拦截的,完成权限的认证的。然后,刚刚也说到,完成认证是这样的:

  1. 拦截到请求
  2. 进入到安全管理器
  3. 管理器负责调度对应的认证,其中我们要使用到Realm,去完成这个从数据库,或者说是认证的具体实现。
  4. 然后就是责任链一路放行,比如验证成功,一路放行到资源,如果失败,就怎么怎么样,这里面有一套操作,我们通过传入到下一层的状态,来判断当前的处理器,要不要处理,然后一条链路走下来,直到走完,或者提前结束。

那么其实都说到这里了,没有接触过Shiro但是,项目写多了的朋友,都看到这个份上了,估计手写一个dome都可以了(真的!)当然,里面还是有很多的一些细节不一样是吧,但是大体大致的一定可以写出来了。

所以,首先,我们要用,就需要先写个Realm.

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义Realm
 */
public class CustomerRealm extends AuthorizingRealm {
    //实现授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //实现认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

然后呢,我们还要写过Config,给到容器,这里的话,我们使用了这个shiro-starter。所以写完Config之后的话,我们可以就是说可以和SpringBoot一起启动,或者一起注入到Servlet里面,完成运行。

package com.huterox.shirodome.config;

import com.huterox.shirodome.Shiro.CustomerRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    //ShiroFilter过滤所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给ShiroFilter配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //配置那些需要放行,需要拦截,需要怎么怎么样
        /*这里有对应的注解
        *anon:无需认证就可以访问
        *authc:必须认证了才能让问
        *user:必须拥有记住我功能才能用
        *perms:拥有对某个资源的权限才能访问[角色:操作];
        *roLe:拥有某个角色权限才能访问[角色]
    
        * @RequiresAuthentication:必须经过认证才能访问
          @RequiresUser:必须经过认证,并且有记住我功能才能访问
          @RequiresPermissions("permission:operation"):需要拥有指定权限才能访问,其中permission为资源名,operation为操作名
          @RequiresRoles("roleName"):需要拥有指定角色(roleName)才能访问
        * */
        Map<String, String> map = new HashMap<String, String>();
        map.put("/hello","anon");
        map.put("/admin","authc");
//        map.put("/superAdmin","perms[s:p]");
        //去登陆接口,没有通过验证进入
        shiroFilterFactoryBean.setLoginUrl("toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauthor");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
    //创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }
    //创建自定义Realm
    @Bean
    public Realm getRealm() {
        CustomerRealm realm = new CustomerRealm();
        return realm;
    }
    // 对Shiro注解的支持
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;

    }
}


测试接口

ok,那么看完了这个,我们来看到,我们这边准备了那些接口。

package com.huterox.shirodome.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class ShiroHelloController {

    @RequestMapping("/hello")
    public String hello(){
        return "Hello";
    }

    @RequestMapping("/admin")
    public String admin(){
        return "Admin";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "toLogin";
    }

    @RequestMapping("/noauthor")
    public String noauthor(){
        return "木有权限";
    }


    @RequestMapping("/superAdmin")
    @RequiresPermissions("s:p")
    public String SP(){
        return "高贵的SP你好";
    }



    @RequestMapping("/login")
    public String login(String username,String password){
        //获取到用户对象,并且封装起来,方便后面shiro使用
        Subject subject = SecurityUtils.getSubject();
//        如果这里还要采用md5”加密“的话
//        String salt= "Huterox";
//        String passwordSalt = new SimpleHash("MD5", password, salt, 2).toString();
//        UsernamePasswordToken token = new UsernamePasswordToken(username,passwordSalt);
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            subject.login(token);
            System.out.println("登录成功!!!");
            return "OK";
        } catch (UnknownAccountException e) {
            System.out.println("用户错误!!!");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误!!!");
        }
        return "NO";
    }
}

因为我们这边是在做认证和授权,所以的话,我们这边有,公共接口,登录接口,拥有特殊授权才能访问的接口。当然还有未授权返回的接口。

Realm编写

在我们的这个Shiro当中,最重要的其实就是这个玩意的实现,在这里,我们要完成的是用户的Authentication和Authorization。

在这里的话,我们这边有两个角色,一个是admin,还有一个是superAdmin。密码都是admin。其他的话,在代码里面有很详细的注释:

package com.huterox.shirodome.Shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义Realm
 */
public class CustomerRealm extends AuthorizingRealm {
    //实现授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权当中");
        String userName = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if(userName.equals("superAdmin")){
            //只有SuperAdmin才有S:P权限
            info.addStringPermission("s:p");
            //添加角色也可以
//            info.addRole("s");
        }else {
            info.addStringPermission("");
        }
        return info;
    }
    //实现认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证当中");
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //注意,这里假设的是查表得到的username,password,可能是加密了的
        String username = "admin";
        String password = "admin";
        if (token.getUsername().equals(username) || token.getUsername().equals("superAdmin")) {
            //这里完成密码匹配,内部会进行处理,一般情况下,获取到的password是明文,或者“自欺欺人”前端对称加密后的东西
            //所以,在controller里面,我们要在加密一下,然后,和这里从数据库里面的password进行对比
            //ByteSource credentialsSalt = ByteSource.Util.bytes("Huterox");//上面添加账号时候生成的加密盐
            //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,credentialsSalt, getName());
            //这里我把token.getUsername()传入进去了,实际上,你可以传入任何对象,然后,接下来在授权部分获取到,这个玩意
            //进入下一步的解析
            return new SimpleAuthenticationInfo(token.getUsername(),password,"");
        }
        return null;
    }
}

权限测试

ok,现在我们的dome,代码写完了,那么接下来,我们要来看看这个具体的执行过程吧。

无权限测试

首先我们来看到的是第一个接口。

    @RequestMapping("/hello")
    public String hello(){
        return "Hello";
    }

在配置里面,我们写了这个:
在这里插入图片描述
这个玩意是不需要权限的,所以,此时我们进行一个访问:
在这里插入图片描述
一切正常。

登录测试

那么现在,我们来访问admin接口,这个接口,是需要登录才能访问的,现在不登录,进行访问。
在这里插入图片描述
此时发现这里需要进入登录页面。这里我没有写html,懒得写了,就给了个提示。

在这里,我们配置了没有登录要调用的接口,和没有授权,或者权限不够要调用的接口
在这里插入图片描述

现在,我们登录一下:
在这里插入图片描述
登录成功,那么接下来,我们再访问一下:
在这里插入图片描述
可以看到成功。

权限测试

ok,接下来,我们来看到权限测试。
现在我们去访问需要超级管理员才能访问的页面。
在这里插入图片描述
这里报错了,在终端可以看到是没有权限的错:
在这里插入图片描述

这里需要注意的是,我在接口处使用的是注解模式,如果你是在配置里面写好了:
在这里插入图片描述
那么就可以跳转到/noauthor里面。
这个时候,我们就需要使用到全局异常处理器了,拦截这些Controller的错误,这里还是在Session模式下,还不是用token的,也就是前后端分离的,所以这里这样很正常。
现在登录超级管理员:
在这里插入图片描述
可以看到一切正常:
在这里插入图片描述

前后端分离token

现在我们来用用前后端分离的,这里的话,我们需要做的就是结合jwt,然后进行处理了,操作和security是类似的,其实。

我们要做的其实就是在基础上集成JWT。

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>

然后的话,我们修改一下过滤器。
这里JWT是啥,怎么用,后面怎么用我就不说了,这个需要结合你实际的项目,而且默认你是有基础的,只是想要玩玩shiro,而已。

JWTFilter

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
    // 如果请求头带有token,则对token进行检查;否则,直接放行
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 判断请求头是否带有 token
        if (isLoginAttempt(request, response)) {
            // 如果存在 token ,则进入executeLogin()方法执行登入,并检测 token 的正确性
            try {
                executeLogin(request, response);
            } catch (Exception e) {
                log.error("Error! {}", e.getMessage());
                responseError(response, e.getMessage());
            }
        }
        // 如果不存在 token ,则可能是执行登录操作/游客访问状态,所以直接放行
        return true;
    }

    // 检测 header中是否包含 token
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return getTokenFromRequest(request) != null;
    }

   // 执行登入操作
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        String token = getTokenFromRequest(request);
        JwtToken jwtToken = new JwtToken(token);
        // 提交给 realm 进行登入,如果错误,会抛出异常并捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常,则代表登入成功,返回 true
        return true;
    }

    // 从请求中获取 token
    private String getTokenFromRequest(ServletRequest request) {
        HttpServletRequest req = (HttpServletRequest) request;
        return req.getHeader("Token");
    }

    // 非法请求将跳转到 "/unauthorized/**"
    private void responseError(ServletResponse response, String message) {
        try {
            HttpServletResponse resp = (HttpServletResponse) response;
            // 设置编码,否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            resp.sendRedirect("/noauthori/" + message);
        } catch (UnsupportedEncodingException e) {
            log.error("Error! {}", e.getMessage());
        } catch (IOException e) {
            log.error("Error! {}", e.getMessage());
        }
    }
}

这里的话,我们还可以再对JwtToken封装一下,方便后面拿东西。

public class JwtToken implements AuthenticationToken {
    private String token;
    
    public JwtToken(String token) {
        this.token = token;
    }
    
    @Override
    public Object getPrincipal() {
        return token;
    }
    @Override
    public Object getCredentials() {
        return token;
    }
}

重写认证

 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       
        // 这里的 token从 JWTFilter 的 executeLogin() 方法传递过来,先前我们封装了jwttoken
        //如果验证通过,我们把这个JwtToken往下传递了
        String token = (String) authenticationToken.getCredentials();
     	//然后这里还是查表那一套

        return new SimpleAuthenticationInfo(token, token, getName());
    }

同样的授权也是一样的。

那么之后的话,我们的流程就是,登录完之后,前端拿到token,我们设置需要验证的地方,就会通过我们的过滤器,然后执行这一套逻辑。

修改配置

最后我们重新修改配置:


@Configuration
public class ShiroConfig {

    //ShiroFilter过滤所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给ShiroFilter配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //配置那些需要放行,需要拦截,需要怎么怎么样
        /*这里有对应的注解
        *anon:无需认证就可以访问
        *authc:必须认证了才能让问
        *user:必须拥有记住我功能才能用
        *perms:拥有对某个资源的权限才能访问[角色:操作];
        *roLe:拥有某个角色权限才能访问[角色]

        * @RequiresAuthentication:必须经过认证才能访问
          @RequiresUser:必须经过认证,并且有记住我功能才能访问
          @RequiresPermissions("permission:operation"):需要拥有指定权限才能访问,其中permission为资源名,operation为操作名
          @RequiresRoles("roleName"):需要拥有指定角色(roleName)才能访问
        * */

        // 设置自定义的拦截器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, String> map = new HashMap<String, String>();
        map.put("/hello","anon");
        map.put("/admin","authc");
//        map.put("/superAdmin","perms[s:p]");
        //去登陆接口,没有通过验证进入
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauthor");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        // 关闭 shiro 自带的 session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(evaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }
    //创建自定义Realm
    @Bean
    public Realm getRealm() {
        CustomerRealm realm = new CustomerRealm();
        return realm;
    }


    // 对Shiro注解的支持
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;

    }


}


总结

okey,这些就是全部内容了。没啥东西其实,就是简单换个脑子,过过。

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

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

相关文章

软件测试面试大全(全800+题)

1、B/S架构和C/S架构区别 B/S 只需要有操作系统和浏览器就行&#xff0c;可以实现跨平台&#xff0c;客户端零维护&#xff0c;维护成本低&#xff0c;但是个性化能力低&#xff0c;响应速度较慢 C/S响应速度快&#xff0c;安全性强&#xff0c;一般应用于局域网中&#xff0c…

C++ [STL之list模拟实现]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT STL之list模拟实现 前言正文基本框架节点类迭代器类list类 迭代器类功能实现list迭代器迭代器设计思想迭代器操作设计 list类功能实现默认成员函数容量查询数据访问节点插删相关头尾插删任意位置…

MySQL数据库索引的种类、创建、删除

目录 一&#xff1a;MySQL 索引 1、MySQL 索引介绍 2、 索引的作用 3、索引的副作用 4、 创建索引的原则依据 二、索引的分类和创建 1、 普通索引 &#xff08;1&#xff09; 直接创建索引 &#xff08;2&#xff09; 修改表方式创建 &#xff08;3&#xff09; 创建表的…

【Turfjs】几何计算,计算地理空间上点坐标的经纬度,距离,围成的闭合空间面积等工作,都可以通过Turfkjs来实现

​​​​​​Turf.js中文网 几何计算&#xff1a; 1. 前端js就用这个 Turfjs的类库。参考网站&#xff1a; 计算两线段相交点 | Turf.js中文网 2. 后端java语言就可以用 JTS这个类库&#xff0c;参考网站&#xff1a;https://locationtech.github.io/jts/ https://github.com…

MVCC和undo log

MVCC多版本并发控制 MVCC是多版本并发控制&#xff08;Multi-Version Concurrency Control&#xff0c;简称MVCC&#xff09;&#xff0c;是MySQL中基于乐观锁理论实现隔离级别的方式&#xff0c;用于实现已提交读和可重复读隔离级别的实现&#xff0c;也经常称为多版本数据库…

面向对象接口

生活中大家每天都在用 USB 接口&#xff0c;那么 USB 接口与我们今天要学习的接口有什 么相同点呢&#xff1f; 在Java程序设计中的接口 接口就是规范&#xff0c;定义的是一组规则&#xff0c;体现了现实世界中“如果你是/要…则必须 能…”的思想。继承是一个"是不是&…

几款GB28181流媒体平台的详细介绍和使用整理

随着监控行业国标GB28181的应用范围越来越广泛&#xff0c;成熟的GB28181接入平台越来越多&#xff0c;本文梳理一下目前各大成熟的流媒体服务器平台及实际应用效果供各位参考。 1)NTV GBS NTV GBS是一款成熟、功能完善、产品化程度很高的GB28181服务平台&#xff0c;从2022年…

原点安全助力金融机构消费者个人信息保护合规

数字经济的发展进一步加速了金融业务与生活场景之间的融合&#xff0c;数亿民众在享受金融数字化便利服务的同时&#xff0c;也更容易遭受个人信息泄露、权益侵害等事件。在实际业务开展过程中&#xff0c;部分金融机构仍存在各种侵害消费者个人信息权益的乱象。 我国对数据安…

工作经验--产品季节性分析

产品季节性分析 1.了解季节性的重要性2.如何发现季节性产品统计方法&#xff1a;季节性指数法&#xff1a;傅里叶分析法&#xff1a;其他&#xff1a; 1.了解季节性的重要性 产品是否存在季节性变化&#xff0c;对于卖家来说相当重要&#xff0c;旺季提前备货、淡季防止库存冗余…

工业无监督缺陷检测,提升缺陷检测能力,解决缺陷样品少、不平衡等问题(二)

1. 工业缺陷检测简介 在工业生产中,质量保证是一个很重要的话题, 因此在生产中细小的缺陷需要被可靠的检出。工业异常检出旨在从正常的样本中检测异常的、有缺陷的情况。工业异常检测主要面临的挑战: 难以获取大量异常样本正常样本和异常样本差异较小异常的类型不能预先得知…

UNITY3D回合制游戏开发教程案例

UNITY3D实现回合制游戏 &#xff0c;类似梦幻西游&#xff0c;口袋妖怪&#xff0c;阴阳师。 先上效果 UNITY3D 回合制游戏案例源码开发教程 普通攻击 AOE技能 游戏概述 回合制游戏是一种策略游戏&#xff0c;玩家需要在自己的回合内进行决策&#xff0c;然后等待对手的回合…

redis学习 -- 常用指令

应用场景 String &#xff1a;缓存&#xff0c;限流&#xff0c;计数器&#xff0c;分布式锁&#xff0c;分布式session Hash&#xff1a;存储y用户信息&#xff0c;用户主页访问量&#xff0c;组合查询 List&#xff1a;关注人时间轴列 Set&#xff1a;点赞&#xff0c;标签&…

【在人间】关于网吧的记忆

高考完的暑假当了两个月夜班的网管&#xff0c;挣得一台小米6&#xff0c;也见识了不少社会人。 乡镇网吧&#xff0c;店里有老虎机&#xff0c;挣的钱比网吧一晚上收入多得多&#xff0c;最狠的一次有人一下输了3000个币(一个币一块钱)&#xff0c;半夜喊老板下楼哭爹喊娘的要…

Docker:启动,停止,删除

1.启动一个容器: docker run 可选参数 镜像名 [COMMAND] [ARG...] docker run -it ubuntu /bin/bash ,启动一个使用ubuntu的docker,并使用/bin/bash做为dcoker中执行的命令。 其中818d5a1c32ac为容器ID 在宿主机上,可以通过docker ps查看容器的状态: 启动容器时常用的可选…

程序员疯抢的 Java 面试宝典(PDF 版)限时开源

Java 面试 2023 届高校毕业生规模预计 1076 万人&#xff0c;同比增加 367 万人&#xff0c;对于 23 届的同学们来说&#xff0c;今年下半年大规模进行的秋招是获得全职 Offer 的最重要的途径&#xff01;对于程序员来说&#xff0c;大家都知道校招难度相对于社招来说会有所降…

解析 HashMap 源码:深入探究核心方法的实现与原理

前言数据结构类属性构造方法核心方法阈值&#xff1a;tableSizeFor插入元素&#xff1a;put树化&#xff1a;treeifyBin扩容&#xff1a;resize获取元素&#xff1a;get删除元素&#xff1a;remove遍历元素&#xff1a;keySet、entrySet 方法 总结 前言 一切的源头从类注释开始…

【Java se】集合——迭代器(Iterator接口)的实现原理

目录 一、迭代器的应用——遍历集合 步骤1&#xff1a;通过集合获取迭代器 步骤2&#xff1a;使用while循环 案例展示&#xff1a; 二、跟踪源代码 #1. 通过集合获取迭代器 #2. 通过成员方法next( ) 获取每一个集合元素对象 #3. 通过成员方法hasNext( )判断是否进行下一次…

计算机组成原理 | 理解二进制编码

二进制的转换 二进制——> 十进制&#xff1a; 从右到左的第 N 位&#xff0c;乘上一个 2 的 N 次方&#xff0c;然后加起来&#xff0c;就变成了一个十进制数例如二进制数&#xff1a;0011&#xff0c;对应的十进制表示&#xff0c;就是 0 2 3 0 2 2 1 2 1 1 2 0…

阿里云斩获 4 项年度云原生技术服务优秀案例

日前&#xff0c;在 ICT 中国2023 高层论坛-云原生产业发展论坛上&#xff0c;由阿里云容器服务提供技术支持的 “数禾科技”和“智联招聘” 两大案例以及阿里云云原生 AI 套件、云原生 FinOps 成本分析套件两大产品技术方案&#xff0c;共同获得 2023 年度云原生应用实践先锋—…

oai核心网启动多切片自动生成方法

简介 启动一个切片需要&#xff1a; 核心网侧&#xff1a; 启动核心网yaml文件及相关配置文件&#xff08;datebase conf healthscripts&#xff09; 对应业务的sever &#xff08;如&#xff09;基站侧&#xff1a; 虚拟机 启动ueransim的yaml文件及相关配置 代理程序&#…