当前位置: 首页 > news >正文

Springboot整合shiro

导入依赖

   <!--    引入springboot的web项目的依赖        --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
​
<!--    shiro    --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.12.0</version></dependency>

配置类

package com.qf.shiro2302.config;
​
import com.qf.shiro2302.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
import java.util.Arrays;
import java.util.List;
​
@Configuration
@Slf4j
public class ShiroConfig {
​
​
​@Beanpublic Realm realm(){AuthorizingRealm authorizingRealm = new AuthorizingRealm() {
​/**** @param token 这的token就是调用login方法时,传入的token对象* @return AuthenticationInfo 对象中,封装了用户的身份信息(principals),密码(credentials),提供信息的realm的名字* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("===========获取身份信息===============");//查询数据库获取当前用户名对应的User对象String username = (String) token.getPrincipal();System.out.println("username="+username);System.out.println("this.getName()={}"+this.getName());User user=getUserFromDB(username);
​//需要返回shiro规定的AuthenticationInfo类型的对象//这个对象中,包含了用户的身份认证信息// SimpleAuthenticationInfo的构造函数中的第一个参数,principals代表用户的身份信息// 一般可以使用 user对象,或者使用用户名也可以// 第三个参数,代表当前realm的名字,固定写法//Authentication 证明的意思SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
​return authenticationInfo;}
​@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("============获取授权===============");
​// 从数据库表中查询当前用户具有的角色和权限字符串User user = (User) principalCollection.getPrimaryPrincipal();
​List<String> roles= getRolesFromDB(user);List<String> permissions= getPermissionFromDB(user);
​// 按照约定返回AuthorizationInfo对象SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 放入从数据库中查询到的当前用户的角色信息
//                authorizationInfo.addRole("test");authorizationInfo.addRoles(roles);
​// 放入从数据库中查询到的当前用户的权限信息
//             authorizationInfo.addStringPermission("document:read");authorizationInfo.addStringPermissions(permissions);return authorizationInfo;}};
​
​return authorizingRealm;
​
​}
​private List<String> getRolesFromDB(User user) {return Arrays.asList("test","admin");}
​private List<String> getPermissionFromDB(User user) {return Arrays.asList("document:read","document:write");}
​private User getUserFromDB(String username) {User user = new User(100, "数据库用户", "123456", "123@qq.com");return user;}
​@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {//ShiroFilterChainDefinition 此接口就一个实现类  默认Shiro过滤器链定义类DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
​// 让登录接口直接被shiro的过滤器放行//第一个参数:接口的路径//第二个参数:shiro内部的过滤器的名字,anon代表无需登录即可访问的特殊的过滤器,注意过滤器的名字是固定的,不能乱写chainDefinition.addPathDefinition("/login/dologin", "anon");// 放行springboot的错误页面的请求url  这个页面就是个response拼接的页面chainDefinition.addPathDefinition("/error", "anon");
​
​//增加角色或者权限,对于某些请求// logged in users with the 'test' role
//        chainDefinition.addPathDefinition("/test/**", "authc, roles[test,admin], perms[document:read,document:write]");  这个如果改成anon,就不能加后面的角色或者权限,否则,将会被认为需要登录,重定向到登录页
​// all other paths require a logged in userchainDefinition.addPathDefinition("/**", "authc");return chainDefinition;}
}

调用接口

package com.qf.shiro2302.controller;
​
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("/login")
@Slf4j
public class LoginController {
​@PostMapping("/dologin")public String dologin(String username,String password){
​//使用Shiro进行登录处理Subject subject = SecurityUtils.getSubject();//获取shiro核心对象//为了调用shiro的登录方法,需要准备一个Token对象UsernamePasswordToken token = new UsernamePasswordToken(username,password);System.out.println(token);subject.login(token);//使用shiro登录流程
​return "登陆成功 ";
​
​}
​
}

获取ShiroSession中的用户,注解添加权限

package com.qf.shiro2302.controller;
​
import com.qf.shiro2302.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("/test")
public class TestController {
​
​@RequiresRoles({"test","admin"})@RequiresPermissions({"document:read","document:write"})@RequestMapping("/test1")public String hello1(){return "hello shiro !!!";}
​
​@RequestMapping("/test2")public User hello2(){//如果使用shiro获取当前登录用户的身份信息Subject subject = SecurityUtils.getSubject();User principal = (User) subject.getPrincipal();System.out.println(principal);return principal;}
}
shiro:loginUrl: /login.html#配置没登陆的时候重定向的页面。这个请求是可以放行的

优化整合shiro,登录密码MD5Hash加密,内置处理

1.配置类

package com.qf.shiroHomework.config;
​
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qf.shiroHomework.entity.*;
import com.qf.shiroHomework.mapper.TPersMapper;
import com.qf.shiroHomework.mapper.TRoleMapper;
import com.qf.shiroHomework.mapper.TRolePermsMapper;
import com.qf.shiroHomework.mapper.TUserRoleMapper;
import com.qf.shiroHomework.service.ITUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
​
@Configuration
@Slf4j
public class ShiroConfig {
​@Autowiredprivate ITUserService itUserService;@Autowiredprivate TUserRoleMapper tUserRoleMapper;@Autowiredprivate TRolePermsMapper tRolePermsMapper;@Autowiredprivate TPersMapper tPersMapper;@Autowiredprivate TRoleMapper tRoleMapper;
​//将Realm对象放入IOC容器里@Beanpublic Realm realm(){AuthorizingRealm authorizingRealm = new AuthorizingRealm() {
​/**** @param token 这的token就是调用login方法时,传入的token对象* @return AuthenticationInfo 对象中,封装了用户的身份信息(principals),密码(credentials),提供信息的realm的名字* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("===========获取身份信息===============");//获取身份信息String username = (String) token.getPrincipal();
​QueryWrapper<TUser> wrapper = new QueryWrapper<>();wrapper.eq("username",username);TUser user = itUserService.getOne(wrapper);
​// 需要返回shiro规定的AuthenticationInfo类型的对象// 这个对象中,包含了用户的身份认证信息// SimpleAuthenticationInfo的构造函数中的第一个参数,principals代表用户的身份信息// 一般可以使用 user对象,或者使用用户名也可以// 第三个参数: 盐// 第四个参数,代表当前realm的名字,就是一个标识,标识是这个Bean调用的这个方法,没有作用,固定写法
​SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
​return authenticationInfo;}
​@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {TUser user = (TUser) principalCollection.getPrimaryPrincipal();SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();log.info("用户角色={}",getRolesFromDB(user));log.info("用户权限={}",getPermissionFromDB(user));authorizationInfo.addRoles(getRolesFromDB(user));authorizationInfo.addStringPermissions(getPermissionFromDB(user));return authorizationInfo;}};
​// 把HashedCredentialsMatcher对象设置到authorizingRealm对象中authorizingRealm.setCredentialsMatcher(hashedCredentialsMatcher());
​return authorizingRealm;}
​
​@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher(){
​HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();//设置算法名称matcher.setHashAlgorithmName("md5");//设置hash次数matcher.setHashIterations(1024);return matcher;
​
​}
​
​
​
​//配置Shiro过滤器链@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();chainDefinition.addPathDefinition("/register.html","anon");chainDefinition.addPathDefinition("/error/500.html","anon");chainDefinition.addPathDefinition("/error","anon");chainDefinition.addPathDefinition("/user/register","anon");chainDefinition.addPathDefinition("/user/login","anon");chainDefinition.addPathDefinition("/user/**","authc");chainDefinition.addPathDefinition("/**","authc");
​
​return chainDefinition;
​}
​public List<String> getPermissionFromDB(TUser user) {if (user.getPerms()!=null){return user.getPerms();}
​
​Integer id = user.getId();//通过用户ID找到角色
​QueryWrapper<TUserRole> wrapper1 = new QueryWrapper<>();wrapper1.eq("userid",id);TUserRole tUserRole = tUserRoleMapper.selectOne(wrapper1);Integer roleid = tUserRole.getRoleid();System.out.println(roleid);
​
​QueryWrapper<TRolePerms> wrapper = new QueryWrapper<>();wrapper.select("permsid").eq("roleid",roleid);List<Object> permsidList = tRolePermsMapper.selectObjs(wrapper);System.out.println(permsidList);List<Integer> integers = permsidList.stream().map(o -> {return (Integer) o;}).collect(Collectors.toList());List<TPers> tPers = tPersMapper.selectBatchIds(integers);System.out.println(tPers);ArrayList<String> strings = new ArrayList<>();for (TPers tPer : tPers) {strings.add(tPer.getName());}user.setPerms(strings);
​return strings;}
​public List<String> getRolesFromDB(TUser user) {
​if (user.getRoleName()!=null){return user.getRoleName();}
​Integer id = user.getId();//通过用户ID找到角色QueryWrapper<TUserRole> wrapper1 = new QueryWrapper<>();wrapper1.eq("userid",id);TUserRole tUserRole = tUserRoleMapper.selectOne(wrapper1);Integer roleid = tUserRole.getRoleid();QueryWrapper<TRole> wrapper = new QueryWrapper<>();wrapper.select("name").eq("id",roleid);List<Object> roles = tRoleMapper.selectObjs(wrapper);List<String> strings = roles.stream().map(o -> {return (String) o;}).collect(Collectors.toList());user.setRoleName(strings);return strings;}
​
}

实体类

package com.qf.shiroHomework.entity;
​
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.List;
​
import lombok.*;
​
/*** <p>* * </p>** @author jmj* @since 2023-08-10*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@TableName("t_user")
public class TUser implements Serializable {
​private static final long serialVersionUID = 1L;
​@TableId(value = "id", type = IdType.AUTO)private Integer id;
​private String username;
​private String password;
​private String salt;
​@TableField(exist = false) // 说明当前这个属性在数据库表中没有对应的字段,让mp生成sql时忽略这个属性private List<String> roleName;@TableField(exist = false)private List<String> perms;
​
​
​
​
}

Controller

//复杂密码匹配器@PostMapping("/login")public String login(TUser user){//使用Shiro进行登录处理Subject subject = SecurityUtils.getSubject();//获取shiro核心对象//为了调用shiro的登录方法,需要准备一个Token对象
​UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(), user.getPassword());
​subject.login(usernamePasswordToken);
​return "redirect:/show.html";
​}

以前手写MD5处理的方法

//    简单密码匹配器方案
//    @PostMapping("/login")
//    public String login(TUser user){
//        //使用Shiro进行登录处理
//        Subject subject = SecurityUtils.getSubject();//获取shiro核心对象
//        //为了调用shiro的登录方法,需要准备一个Token对象
//        //添加Where 条件
//        QueryWrapper<TUser> wrapper = new QueryWrapper<>();
//        wrapper.eq("username",user.getUsername());
//        TUser u = itUserService.getOne(wrapper);
//        if (u==null){
//            return "redirect:/index.html";
//        }else {
//
//            //MD5加密
//            Md5Hash md5Hash = new Md5Hash(user.getPassword(), u.getSalt(), 1024);
//            String newPassword = md5Hash.toHex();
//
//
//            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUsername(), newPassword);
//
//            subject.login(usernamePasswordToken);
//        }
//
//            return "redirect:/show.html";
//
//    }

相关文章:

Springboot整合shiro

导入依赖 <!-- 引入springboot的web项目的依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> ​ <!-- shiro --><depende…...

阻塞/非阻塞、同步/异步(网络IO)

1.阻塞/非阻塞、同步/异步(网络IO) 【思考】典型的一次 IO 的两个阶段是什么&#xff1f; 数据就绪 和 数据读写 数据就绪 &#xff1a;根据系统 IO 操作的就绪状态 阻塞 非阻塞 数据读写 &#xff1a;根据应用程序和内核的交互方式 同步 异步 陈硕&#xff1a;在处理 IO …...

为什么大家会觉得考PMP没用?

一是在于PMP这套知识体系&#xff0c;是一套底层的项目管理逻辑框架&#xff0c;整体是比较抽象的。大家在学习工作之后&#xff0c;会有人告诉你很多职场的一些做事的规则&#xff0c;比如说对于沟通&#xff0c;有人就会告诉如何跟客户沟通跟同事相处等等&#xff0c;这其实就…...

AVR128单片机 USART通信控制发光二极管显示

一、系统方案 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 void port_init(void) { PORTA 0xFF; DDRA 0x00;//输入 PORTB 0xFF;//低电平 DDRB 0x00;//输入 PORTC 0xFF;//低电平 DDRC 0xFF;//输出 PORTE 0xFF; DDRE 0xfE;//输出 PO…...

为什么5G 要分离 CU 和DU?(4G分离RRU 和BBU)

在 Blog 一文中&#xff0c;5G--BBU RRU 如何演化到 CU DU&#xff1f;_5g rru_qq_38480311的博客-CSDN博客 解释了4G的RRU BBU 以及 5G CU DU AAU&#xff0c;主要是讲了它们分别是什么。但是没有讲清楚 为什么&#xff0c;此篇主要回答why。 4G 为什么分离基站为 RRU 和 BBU…...

Python中的数据输入

获取键盘输入 input语句 使用input()可以从键盘获取输入&#xff0c;使用一个变量来接收 print("你是谁&#xff1f;") name input() print(f"我知道了&#xff0c;你是{name}")# print("你是谁&#xff1f;") name input("你是谁&…...

cms系统稳定性压力测试出现TPS抖动和毛刺的性能bug【杭州多测师_王sir】

一、并发线程数100&#xff0c;分10个阶梯&#xff0c;60秒加载时间&#xff0c;运行1小时进行压测&#xff0c;到10分钟就出现如下 二、通过jstat -gcutil 16689 1000进行监控...

【UE】材质描边、外发光、轮廓线

原教学视频链接&#xff1a; ue4 材质描边、外发光、轮廓线_哔哩哔哩_bilibili 步骤 1. 首先新建一个材质&#xff0c;这里命名为“Mat_outLine” 在此基础上创建一个材质实例 2. 在视口中添加一个后期处理体积 设置后期处理体积为无限范围 点击添加一个数组 选择“资产引用”…...

百模大战,打响AI应用生态的新赛点

点击关注 文&#xff5c;郝鑫 黄小艺&#xff0c;编&#xff5c;刘雨琦 “宇宙中心”五道口&#xff0c;又泛起了昔日的光芒。 十字路口一角的华清嘉园里&#xff0c;各种互联网大佬们&#xff0c;王兴、程一笑、张一鸣等人的创业传说似乎还有余音&#xff0c;后脚搬进来的AI…...

【C++二叉树】进阶OJ题

【C二叉树】进阶OJ题 目录 【C二叉树】进阶OJ题1.二叉树的层序遍历II示例代码解题思路 2.二叉搜索树与双向链表示例代码解题思路 3.从前序与中序遍历序列构造二叉树示例代码解题思路 4.从中序与后序遍历序列构造二叉树示例代码解题思路 5.二叉树的前序遍历&#xff08;非递归迭…...

C++——vector:resize与reserve的区别,验证写入4GB大数据时相比原生操作的效率提升

resize和reserve的区别 reserve&#xff1a;预留空间&#xff0c;但不实例化元素对象。所以在没有添加新的对象之前&#xff0c;不能引用容器内的元素。而要通过调用push_back或者insert。 resize&#xff1a;改变容器元素的数量&#xff0c;且会实例化对象&#xff08;指定或…...

基础配置xml

# 配置端口 server.port8081# 文件上传配置 # 是否支持文件上传 spring.servlet.multipart.enabledtrue # 是否支持文件写入磁盘 spring.servlet.multipart.file-size-threshold0 # 上传文件的临时目录 spring.servlet.multipart.locationd:/opt/tmp # 最大支持上传文件大小 sp…...

win环境安装SuperMap iserver和配置许可

SuperMap iServer是我国北京超图公司研发的基于跨平台GIS内核的云GIS应用服务器产品&#xff0c;通过服务的方式&#xff0c;面向网络客户端提供与专业GIS桌面产品相同功能的GIS服务&#xff0c;能够管理、发布多源服务&#xff0c;包括REST服务、OGC服务等。 SuperMap iserve…...

【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER(一)

文章目录 TASK系列解析文章前言PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER功能介绍PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER相关配置PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER流程确定优化变量定义目标函数定义约束ProcessSetUpStatesAndBoundsOptimizeByQPCheckSpeedLimitF…...

pytest parametrize多参数接口请求及展示中文响应数据

编写登陆接口 app.py from flask import Flask, request, jsonify, Responseapp Flask(__name__)app.route(/login, methods[POST]) def login():username request.form.get(username)password request.form.get(password)# 在这里编写你的登录验证逻辑if username admin…...

电视连续剧 ffmpeg 批量去掉片头片尾

思路&#xff1a; 一、用python获取每集的总时长 二、把每集的时间&#xff0c;拼接成想要的ffmpeg的剪切命令命令。 1、用python获取每集的总时长 1&#xff0c;安装moviepy库&#xff0c;直接安装太慢&#xff0c;换成国内的源 pip install moviepy -i http://mirrors.aliyu…...

二进制搭建kubernetes

二进制搭建kubernetes 一、常见的K8S部署方式1.Minikube2.Kubeadmin3.二进制安装部署 二、二进制搭建K8S(单台master)1.部署架构规划2.系统初始化配置3.部署 docker引擎4.部署 etcd 集群4.部署 Master 组件5.部署 Worker Node 组件6.部署网络组件 三、负载均衡部署1.配置load b…...

TDengine函数大全-系统函数

以下内容来自 TDengine 官方文档 及 GitHub 内容 。 以下所有示例基于 TDengine 3.1.0.3 TDengine函数大全 1.数学函数 2.字符串函数 3.转换函数 4.时间和日期函数 5.聚合函数 6.选择函数 7.时序数据库特有函数 8.系统函数 系统函数 TDengine函数大全DATABASECLIENT_VERSIONSE…...

北京互联网营销服务商浩希数字科技申请1350万美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于北京的互联网营销服务商浩希数字科技&#xff08;Haoxi Health Technology Limited &#xff09;近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯…...

ElementUI浅尝辄止22:Alert 警告

用于页面中展示重要的提示信息。 常见于消息提示或警告框。 1.如何使用&#xff1f; 页面中的非浮层元素&#xff0c;不会自动消失。 //Alert 组件提供四种主题&#xff0c;由type属性指定&#xff0c;默认值为info。<template><el-alerttitle"成功提示的文案&…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

ardupilot 开发环境eclipse 中import 缺少C++

目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...