shiro 框架使用学习

news2024/11/26 12:53:06

简介

  • Shiro安全框架是Apache提供的一个强大灵活的安全框架
  • Shiro安全框架提供了认证、授权、企业会话管理、加密、缓存管理相关的功能,使用Shiro可以非常方便的完成项目的权限管理模块开发

Shiro的整体架构

在这里插入图片描述
1、Subject
​ Subject即主体(可以把当前用户理解为主体),外部应用与Subject进行交互,Subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在Shiro中是一个接口,接口中定义了很多认证授权相关的方法,外部程序通过Subject进行认证授,而Subject是通过SecurityManager安全管理器进行认证授权

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

3、Cryptography
​ Cryptography即密码管理,Shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

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

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

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

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

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

9、CacheManager
​ CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

shiro 使用

1.、shiro的配置类

/**
 * shiro权限管理的配置
 */
@Configuration
public class ShiroConfig {

    /**
     * 安全管理器
     */
    @Bean
    public DefaultWebSecurityManager securityManager(ShiroDatabaseRealm shiroDatabaseRealm,
                                                     RememberMeManager rememberMeManager,
                                                     CacheManager cacheManager,
                                                     SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroDatabaseRealm);
        securityManager.setCacheManager(cacheManager);
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

    /**
     * 会话管理器
     */
    @Bean
    public SessionManager sessionManager() {
        return new ServletContainerSessionManager();
    }

    /**
     * 缓存管理器
     */
    @Bean
    public CacheManager getCacheShiroManager(EhCacheManagerFactoryBean ehcache) {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManager(ehcache.getObject());
        return ehCacheManager;
    }


    /**
     * rememberMe管理器
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));
        manager.setCookie(rememberMeCookie);
        return manager;
    }

    /**
     * 记住密码Cookie
     */
    @Bean
    public SimpleCookie rememberMeCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天
        return simpleCookie;
    }

    /**
     * Shiro的过滤器链
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        /**
         * 默认的登陆访问url
         */
        shiroFilter.setLoginUrl("/login");
        /**
         * 登陆成功后跳转的url
         */
        shiroFilter.setSuccessUrl("/");
        /**
         * 没有权限跳转的url
         */
        shiroFilter.setUnauthorizedUrl("/global/error");

        /**
         * 自定义过滤器
         */
        HashMap<String, Filter> myFilters = new HashMap<>();
        myFilters.put("user", new ShiroUserFilter());
        shiroFilter.setFilters(myFilters);

        /**
         * 配置shiro拦截器链
         *
         * anon  不需要认证
         * authc 需要认证
         * user  验证通过或RememberMe登录的都可以
         *
         * 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的
         *
         * 顺序从上到下,优先级依次降低
         *
         * api开头的接口,走rest api鉴权,不走shiro鉴权
         *
         */
        Map<String, String> hashMap = new LinkedHashMap<>();
        //NONE_PERMISSION_RES是一个集合里边存储了所有不需要过滤的路径,包括登录路径,错误路径等,将里边所有路径标志为anon,即访问不需要权限。
        for (String nonePermissionRe : NONE_PERMISSION_RES) {
            hashMap.put(nonePermissionRe, "anon");
        }
        //  剩下的路径  走自定义过滤器
        hashMap.put("/**", "user");
        shiroFilter.setFilterChainDefinitionMap(hashMap);
        return shiroFilter;
    }

    /**
     * Shiro生命周期处理器:
     * 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm)
     * 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager)
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    /**
     * 启用shrio授权注解拦截方式,AOP式方法级权限检查
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

2.在login controller中进行登录操作

在login controller中需要使用一个UsernamePasswordToken类型的token来存储用户名、密码、和remeberme ,token相当于一个令牌。
SecurityUtils.getSubject()得到subject, subject是一个非常重要的对象,包含了登录、注销、获取当前用户、检查角色、权限等操作的方法。
调用subject.login()方法将token作为参数会进入Shiro的安全管理器,并调用 Realm 中的 doGetAuthenticationInfo 方法。

 @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String loginVali(String username, String password, String remember) {
        Subject subject = SecurityUtils.getSubject();
        // 1) TODO 准备 Token 数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray());
        // 2) TODO 开启“记住我”功能
        if(remember!=null&&remember.equals("on")){
            token.setRememberMe(true);
        }
        else {
            token.setRememberMe(false);
        }
        // 3) TODO 利用 subject 进行登录
        subject.login(token);
        // 4) TODO 记录登录日志 (暂时不做)
        return REDIRECT + "/";
    }

3.实现Realm类

需要实现Realmedia中的三个方法

  • doGetAuthenticationInfo()方法 ,在该方法中需要完成认证信息的准备。
  • setCredentialsMatcher()方法,需要在该方法中重新实现一个CredentialsMatcher()类中的接口用来提供验证密码的正确性,然后将新的CredentialsMatcher对象当作参数调用父类的setCredentialsMatcher()方法。
  • doGetAuthorizationInfo()方法,该方法中需要进行授权信息准备,准备好用户的权限信息,将来使用AOP进行权限过滤时会用到。

下边是自己实现的ShiroDatabaseRealm类

  • doGetAuthenticationInfo()方法中返回了一个SimpleAuthenticationInfo对象SimpleAuthenticationInfo 表示认证信息
    • principal 表示用户信息(一般就是一个用户对象,里面会包含角色信息)
    • credentail 表示密码信息(从数据库获取,一般是加密后的)
    • realmName表示当前的Realm的名字
  • SimpleAuthenticationInfo第一个参数传递了一个ShiorUser对象而不是直接的User对象是因为在ShiorUser对象中多了一些存储权限路径等信息的属性。
  • doGetAuthorizationInfo()方法返回一个SimpleAuthorizationInfo 对象,使用setRoles设置其角色集合,setStringPermissions设置权限集合(url访问列表)
@Component
@DependsOn({"userService", "deptService", "roleService"})
@Slf4j
public class ShiroDatabaseRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private DeptService deptService;

    @Autowired
    private RoleService roleService;

  
    // TODO 1. 完成 认证信息 准备
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // a. TODO 此 token 就是登录时封装的 token 对象
        UsernamePasswordToken token1=(UsernamePasswordToken)token;
        // b. TODO 可以利用 userService 中的方法获取 用户对象
        User user = userService.selectByAccount(token1.getUsername());
        // c. TODO 记得密码验证不需要在这里做,你要做的判断有两件事:

        // d. TODO 第一,判断 数据库中该用户是否存在,不存在,则抛出 shiro 的 UnknownAccountException 异常
        if(user==null) {
            throw new UnknownAccountException();
        }

        // e. TODO 第二,判断 该用户是否被冻结,如被冻结,则抛出 shiro 的 LockedAccountException 异常
        if(user.getStatus()=="FREEZE"){
            throw new LockedAccountException();
        }
        // f. TODO 将数据库用户信息 User 对象转换 为 ShiroUser 对象
        ShiroUser shiroUser=toShiroUser(user);
        // g. TODO 将认证信息:ShiroUser 对象、数据库密码、realm 名称(通过 getName() 得到) 封装至 SimpleAuthenticationInfo 并返回
        SimpleAuthenticationInfo root = new SimpleAuthenticationInfo(shiroUser, user.getPassword(), this.getName());
        /*
            注意
            * 验证密码的操作,是 shiro 框架完成的,不需要主动调用,只需要提供密码验证的 CredentialsMatcher 对象
            * 验证成功,进入 successUrl 验证失败进入 loginUrl
            * 成功后会将 SimpleAuthenticationInfo 中的 principal 信息存入 session, 以后可以通过 Subject 对象获得
            * 成功后还会做一些 RemeberMe cookie 的生成并返回操作,也无需我们干预
         */
        return root;
    }

    /* ShiroUser 的作用
    1. 用来保存认证信息中的用户数据,即 principal,将来存入 session,以便在登录期间使用
    2. 其中除了用户数据,还包括了用户的角色信息,其属性都是根据需要自定义的
    3. 为什么不直接用 User ? 是因为认证过程中的很多属性(包括将来页面要显示的属性) User 对象中没有,因此用 ShiroUser 来保存更多的信息
     */

    private ShiroUser toShiroUser(User user) {
        ShiroUser shiroUser = new ShiroUser();
        shiroUser.setId(user.getUserId());
        shiroUser.setAccount(user.getAccount());
        shiroUser.setDeptId(user.getDeptId());
        shiroUser.setDeptName(deptService.selectName(user.getDeptId()));
        shiroUser.setName(user.getName());
        shiroUser.setEmail(user.getEmail());
        shiroUser.setAvatar(user.getAvatar());

        String[] split = user.getRoleId().split(",");
        List<Long> roleIds = new ArrayList<>();
        List<String> roleNames = new ArrayList<>();
        for (String s : split) {
            Long roleId = Long.valueOf(s);
            roleIds.add(roleId);
            String roleName = roleService.selectName(roleId);
            roleNames.add(roleName);
        }
        shiroUser.setRoleList(roleIds);
        shiroUser.setRoleNames(roleNames);
        log.debug("==============> user {} roles: {}", user.getName(), shiroUser.getRoleNames());
        return shiroUser;
    }

    // TODO 2. 提供密码验证 CredentialsMatcher
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        CredentialsMatcher matcher = new CredentialsMatcher() {
            @Override
            public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
                // TODO token 是表单提交过来的数据, info 是在 doGetAuthenticationInfo 步骤返回的认证信息
                UsernamePasswordToken token1=(UsernamePasswordToken)token;
                char[] password = token1.getPassword();
                String pass_user = new String(password);
                String pass_databases = info.getCredentials().toString();
                // TODO 使用 BCrypt 算法验证密码的正确性,返回值即表示验证是否通过
                return  BCrypt.checkpw(pass_user, pass_databases);
                // 验证不通过会由框架抛出 IncorrectCredentialsException 异常
            }
        };
        super.setCredentialsMatcher(matcher);
    }


  	// TODO 3. 完成 授权信息 准备
    // 当需要进行权限验证时,会调用 doGetAuthorizationInfo 获得当前用户(已认证)的权限信息,只会执行一次,放入缓存当中
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        // a. TODO 获取用户的所有 url 访问权限,可以通过 roleService.selectPermissionURL 获取一个角色的 url 访问列表
        // 注意,一个用户可能会有多个角色
        LinkedHashSet<String> set = new LinkedHashSet<>();
        for (Long roleId : shiroUser.getRoleList()) {
            List<String> strings = roleService.selectPermissionURL(roleId);
            set.addAll(strings);
        }
        // b. TODO 准备一个 SimpleAuthorizationInfo 对象,应设置其角色集合,权限集合(url访问列表),并返回
        /* 注意,这些集合中都是字符串表示的角色和权限
            后续可以通过 Subject 对象中的相关方法来使用这里准备的数据,如:
            * subject.hasRole(角色名) 判断用户是否有某个角色
            * subject.isPermitted(权限名) 判断用户是否有某个权限
            * 更多方法,参考 shiro 的 Subject 接口说明
        */
        LinkedHashSet<String> roleNames = new LinkedHashSet<>();
        roleNames.addAll(shiroUser.getRoleNames());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roleNames);
        info.setStringPermissions(set);
        return info;
    }
}

4. 在AOP中进行权限过滤

在需要进行权限过滤的controller方法上加@Permission注解。
若该方法必须某个角色才能访问则@Permission(“角色名”);

  • 得到subject
    Subject subject = SecurityUtils.getSubject();
  • 判断是否含有所有角色
    subject.hasAllRoles()
  • 判断是否含有URL
    subject.isPermitted()

/**
 * 权限检查的aop
 */
// 0. TODO 打开 @Aspect 注解
@Aspect
@Component
@Order(200)
@Slf4j
public class ShiroPermissionAop {
    // 1. TODO 控制器内需要权限控制的方法上都加了 @Permission 自定义注解,添加合适的切点
   @Around("@annotation(cn.stylefeng.guns.core.common.annotion.Permission)")
    public Object doPermission(ProceedingJoinPoint pjp) throws Throwable {
        // a. 获取当前的请求路径(已实现)
        String requestURI = getRequestURI();
        // b. 获取当前的控制器方法对象(已实现)
        Method method = getMethod(pjp);
        // c. TODO 拿到方法的 Permission 注解,做进一步判断
        Permission annotation= method.getAnnotation(Permission.class);
        String[] roleNames = annotation.value();
        // d. TODO 分支1,如果 Permission 上有角色,调用 checkRoles 进一步判断
        if(annotation.value().length>0){
            boolean pass= checkRoles(roleNames);;
            if(!pass){
                throw new NoPermissionException("没有权限");
            }
        }
        // e. TODO 分支2,如果没有角色,那么进行所有路径匹配检查,调用 checkPermission 进一步判断
        else{
            boolean pass= checkPermission(requestURI);
            if(!pass){
                throw new NoPermissionException("没有权限");
            }
        }
        // f. TODO 以上分支,检查通过,使用 pjp 放行, 不通过抛出 NoPermissionException
        // 注意放行的话应该 将 pjp 执行目标方法的结果返回
        return pjp.proceed();
    }

    private Method getMethod(ProceedingJoinPoint point) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        return ms.getMethod();
    }

    private String getRequestURI() {
        HttpServletRequest request = HttpContext.getRequest();
        String requestURI = request.getRequestURI().replaceFirst(ConfigListener.getConf().get("contextPath"), "");
        log.debug("===============> current uri: {}", requestURI);
        return requestURI;
    }

    public boolean checkRoles(Object[] permissions) {

        String[] roleNames = (String[]) permissions;
        Subject subject = SecurityUtils.getSubject();
        return subject.hasAllRoles(Arrays.asList(roleNames));
    }

    private boolean checkPermission(String requestURI) {
        Subject subject = SecurityUtils.getSubject();
        return  subject.isPermitted(requestURI);
    }
}


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

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

相关文章

0X02

web9 阐释一波密码&#xff0c;依然没有什么 发现&#xff0c;要不扫一下&#xff0c;或者看一看可不可以去爆破密码 就先扫了看看&#xff0c;发现robots.txt 访问看看,出现不允许被访问的目录 还是继续尝试访问看看 就可以下载源码&#xff0c;看看源码 <?php $fl…

Nacos-2.2.2源码修改集成高斯数据库GaussDB,postresql

一 &#xff0c;下载代码 Release 2.2.2 (Apr 11, 2023) alibaba/nacos GitHub 二&#xff0c; 执行打包 mvn -Prelease-nacos -Dmaven.test.skiptrue -Drat.skiptrue clean install -U 或 mvn -Prelease-nacos ‘-Dmaven.test.skiptrue’ ‘-Drat.skiptrue’ clean instal…

强化学习的动态规划三

一、策略的改进 假设新的贪婪策略π0与旧的策略π效果相当&#xff0c;但并不优于π。由此得出vπvπ0&#xff0c;且根据之前的推导可以得出&#xff1a;对于所有的s∈S 这与贝尔曼最优方程相同&#xff0c;因此&#xff0c;vπ0是v∗&#xff0c;π和π0是最佳策略。因此&…

笔记本360wifi,wifi共享大师掉线,

笔记本上搭建的wifi热点&#xff0c;例如360wifi、wifi共享大师等&#xff0c;手机连接wifi后总是隔一段时间掉线。原因:网卡驱动和无线驱动有问题&#xff0c;需要更新或换成稳定的网卡驱动和无线驱动。 解决方案: ① 安装驱动精灵: ②点击驱动管理 ③ ④ ⑤进行阻止windo…

AI:60-基于深度学习的瓜果蔬菜分类识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

线扫相机DALSA软件开发套件有哪些

Win10和Win7系统完整SDK目录截图&#xff1a; Sapera Configuration 缓存与内存管理&#xff0c;以及通信端口配置工具&#xff0c;部分功能等效于Detection(查找相机)内的Settings。 Sapera Log Viewer 打开Log Viewer后会显示之前发生过的所有与Sapera LT软件有关的运行信息…

SSTI模板注入入门

一.关系&#xff1a;子类->父类 class A:passclass B(A):passclass C(B):passclass D(B):passcC() 1.__class__查看当前类 c&#xff1a;当前类 print(c.__class__) 2.__base__查看当前类的父类 print(c.__class__.__base__) c的父类的父类 print(c.__class__.__base_…

重生奇迹mu召唤师怎么加点?

召唤师在重生奇迹mu游戏里面是一个智力型的职业&#xff0c;所以智力自然就成为主要加点属性&#xff0c;但是此职业却又算是近身攻击&#xff0c;因为她的技能范围并不算远&#xff0c;而且还是呈现出一种半径趋势&#xff0c;一方面是攻击伤害&#xff0c;另一方面则是辅助造…

AI:58-基于深度学习的猫狗图像识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

AI:59-基于深度学习的行人重识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

Go的json序列化:Marshal与Unmarshal

简介 Json(Javascript Object Nanotation)是一种数据交换格式&#xff0c;常用于前后端数据传输。任意一端将数据转换成json 字符串&#xff0c;另一端再将该字符串解析成相应的数据结构&#xff0c;如string类型&#xff0c;strcut对象等。 1.Json Marshal&#xff1a;将数据编…

2、Sentinel基本应用限流规则(2)

2.2.1 是什么 Sentinel 是阿里中间件团队开源的&#xff0c;面向分布式服务架构的轻量级高可用流量控制组件&#xff0c;主要以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。 2.2.2 基本概念 • 资源 (需要被保护的东西…

分享81个工作总结PPT,总有一款适合您

分享81个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/13hyrlZo2GhRoQjI-6z31-w?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知识付…

二叉搜索树专题1 二叉查找树的建立

题目&#xff1a; 样例&#xff1a; 输入 6 5 2 3 6 1 8 输出 5 2 1 3 6 8 思路&#xff1a; 二叉搜索树的建立&#xff0c;需要了解二叉搜索树的性质。 二叉搜索树的性质 左孩子结点&#xff1a; 比根节点小 右孩子结点&#xff1a; 比根节点大 根据所给的序列进行按序…

【数据结构】排序算法复杂度 及 稳定性分析 【图文详解】

排序算法总结 前言[ 一 ] 小数据基本排序算法&#xff08;1&#xff09;冒泡排序&#xff08;2&#xff09;直接插入排序 [ 二 ] &#xff08;由基本排序衍生的用作&#xff09;处理大数据处理排序&#xff08;1&#xff09;堆排序&#xff08;2&#xff09;希尔排序 [ 三 ] 大…

Leetcode-88 合并两个有序数组

使用内置排序函数&#xff0c;时间复杂度On^2 class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {int j0,im;while(j<n){nums1[i]nums2[j];}Arrays.sort(nums1);} }新建一个临时数组用于放排序后的元素&#xff0c;再将临时数组赋值给nums1&…

Technology strategy Pattern 学习笔记3-Creating the Strategy-Industry context

Creating the Strategy-Industry context 1 SWOT 1.1 create steps 1.与内部各方沟通 了解企业的人、流程和技术&#xff0c;包括与其它企业的不同了解哪些创新可以做竞争者及市场信息企业可以支撑的类似业务 按SWOT四象限分类&#xff0c;先做列表后放入象限 1.2 四象限…

C++ 代码实例:多项式除法简单计算工具

文章目录 前言代码仓库代码说明核心片段 结果总结参考资料作者的话 前言 C 代码实例&#xff1a;多项式除法简单计算工具。 代码仓库 yezhening/Programming-examples: 编程实例 (github.com)Programming-examples: 编程实例 (gitee.com) 代码 说明 由于代码篇幅较多&#…

AI:57-基于机器学习的番茄叶部病害图像识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

linux之按键中断

查看原理图确认引脚 可以看到按键有两个&#xff0c;分别对应GPIO5_1和GPIO4_14 配置pinctrl&#xff0c;配置成GPIO模式 1.使用官方工具&#xff0c;配置下引脚 2.将生成的代码复制到设备树里 创建设备节点 生成二进制设备树文件 在工具链表下使用 make dtbs 或者使…