Shiro认证 -- (Authentication)

Apache Shiro是一个功能强大的Java安全框架,提供了身份验证(Authentication)、授权(Authorization)、加密(Cryptography)、会话管理(Session Management)、与Web集成、缓存(Caching)等核心安全功能。

Shiro认证的核心概念

  1. Subject:代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫、机器人等。它是一个抽象概念,所有Subject都绑定到SecurityManager。
  2. SecurityManager:安全管理器,Shiro的核心组件,所有与安全有关的操作都会与SecurityManager交互。它管理着所有Subject,并负责进行认证和授权、会话及缓存的管理。
  3. Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限)。SecurityManager要验证用户身份,需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。可以把Realm看成DataSource,即安全数据源。
  4. Authenticator:认证器,负责主体认证,是一个扩展点。如果用户觉得Shiro默认的不好,可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。

 

Shiro认证的流程:

1. 添加依赖项

        <!--        shiro认证--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>2.0.1</version></dependency><!--  导入shiro标签      --><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.1.0</version>

2. 收集用户身份/凭证:如用户名/密码。 配置shiro.ini文件

 3.认证测试

编写测试类

@Testpublic void testShiro(){// 1.创建 Realm(安全数据源)IniRealm realm = new IniRealm("classpath:shiro.ini");// 2.配置DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);// 创建注入的 Realm(安全数据源)securityManager.setRealm(realm);SecurityUtils.setSecurityManager(securityManager);// 3.操作 Subject , 进行认证Subject subject = SecurityUtils.getSubject();// 封装一个令牌UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token); // 登录,即认证}catch (AuthenticationException e){System.out.println("认证异常:");e.printStackTrace();}System.out.println("是否通过认证: " + subject.isAuthenticated());// 认证通过后 进行权限认证System.out.println("是否为管理员角色:" + subject.hasRole("管理员"));  // 是否为某角色System.out.println("是否能操作用户查看功能:" + subject.isPermitted("user:view")); // 判断是否拥有某权subject.checkPermission("user:view");System.out.println("身份信息: " + subject.getPrincipal());/*** 18:43:30.420 [main] INFO org.apache.shiro.session.mgt.AbstractValidatingSessionManager -- Enabling session validation scheduler...* 是否通过认证: true* 是否为管理员角色:true* 是否能操作用户查看功能:true* 身份信息: admin*/}

 可以看到我们的一个运行结果 身份认证成功 

 

那如果我们修改密码呢:

  可以看到如果密码和我们的不一样就会 认证异常 !!

  1.  首先通过指定一个ini配置文件来创建一个IniRealm对象;
  2. 接着实例化一个 DefaultSecurityManager,并注入IniRealm 对象;
  3. 再将 DefaultSecurityManager 绑定到 SecurityUtils,这是一个全局设置,设置一次即可通过 SecurityUtils 得到 Subject;
  4. 然后获取身份验证的 Token,如用户名/密码,此处使用UsernamePasswordToken;调用 subject.login 方法进行登录,其会自动委托给 SecurityManager.login 方法进行登录:
  5. 如果身份验证失败请捕获 AuthenticationException或其子类,常见的如:DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException(错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
  6. 如果身份认证成功,后续可以使用subject.isAuthenticated()判断是否认证通过subject.getPrincipal()获得身份信息等。

那么 Shiro认证的流程到低是什么呢?

流程如下:

  1. 首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager, 调用之前必须通过 SecurityUtils.setSecurityManager()设置Security Manager;
  2. SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证:
  3. Authenticator才是真正的身份验证者,ShiroAPI中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm 身份验证默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行Realm 身份验证;
  5. Authenticator会把相应的token传入Realm,从Realm获取身份信息,如果没有返验证;回抛出异常表示身份验证失败了。此处可以配置多个ealm,将按照相应的顺序及策略进行访问。

 

Shiro 的使用方法:

ShiroConfig :

package com.ty.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ty.springbootshiro.entity.Right;
import com.ty.springbootshiro.service.RoleService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.metadata.ManagedOperation;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** ShiroConfig** @aurhor Administrator  whs* @since 2024/10/8*/
@Configuration
public class ShiroConfig {@Resourceprivate RoleService roleService;/*** 开启Shiro注解* @return*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 开启aop注解支持* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() {  // thymeleaf 页面上使用 shiro 标签return new ShiroDialect();}@Beanpublic MyShiroRealm myShiroRealm() {   // 自定义RealmMyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}@Beanpublic SecurityManager securityManager() {  // 安全管理器 SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//注入beansecurityManager.setRealm(myShiroRealm());SecurityUtils.setSecurityManager(securityManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();// 注入securityManagershiroFilterFactory.setSecurityManager(securityManager);// 权限验证 : 使用 Filter 控制资源(URL)的访问shiroFilterFactory.setLoginUrl("/index");shiroFilterFactory.setSuccessUrl("/main");shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 必须使用LinkedHashMap 有序集合// 配置可以匿名访问资源的url: 静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/localcss/**", "anon");filterChainDefinitionMap.put("/localjs/**", "anon");filterChainDefinitionMap.put("/login/**", "anon");filterChainDefinitionMap.put("/logout/**", "anon");  // 注销过滤器 , 自动注销// 配置需要特定权限才能范文的资源的url// 静态授权: 包括全部需要特定权限才能访问的资源URlfilterChainDefinitionMap.put("/user/list", "perms[用户列表]");filterChainDefinitionMap.put("/user/add", "perms[用户添加]");filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");filterChainDefinitionMap.put("/user/del", "perms[用户删除]");// 动态授权List<Right> rights = roleService.findAllRights();for (Right right : rights) {if (right.getRightUrl()!=null && right.getRightUrl().trim().equals("")) { // .startsWith("/user/")filterChainDefinitionMap.put(right.getRightUrl(), "perms["+right.getRightUrl()+"]");}}// 配置认证访问 : 其他资源URl必须认证通过才能访问filterChainDefinitionMap.put("/**", "authc");  // 必须放在过滤器链的最后面shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactory;}}

indexContriller:

package com.ty.springbootshiro.controller;import com.alibaba.druid.sql.visitor.functions.Right;
import com.ty.springbootshiro.entity.Role;
import com.ty.springbootshiro.entity.User;
import com.ty.springbootshiro.service.RoleService;
import com.ty.springbootshiro.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;/*** IndexConfig** @aurhor Administrator  whs* @since 2024/9/13*/
@Controller
public class IndexController {@Resourceprivate UserService userService;@Resourceprivate RoleService roleService;/*** 去登录页*/@GetMapping("/login")public String toLogin() {return "login";}@RequestMapping("/main")public String main() {return "main";}@RequestMapping("/403")public String unauthorized() {return "403";}@RequestMapping("/login")public String login(String usrName, String usrPassword, Model model, HttpSession session) {
//        User loginUser = userService.login(usrName, usrPassword);
//        if (loginUser != null) {
//            session.setAttribute("loginUser", loginUser);
//            return "redirect:/main";
//        }else {
//            model.addAttribute("meg","账号或密码错误");
//            return "login";
//        }try {UsernamePasswordToken token = new UsernamePasswordToken(usrName, usrPassword);Subject subject = SecurityUtils.getSubject();subject.login(token); // 认证登录User user = (User) subject.getPrincipal();System.out.println("user ------ > " + user);session.setAttribute("loginUser", user);//获取权限
//            Role role = user.getRole();
//            List<Right> rights = roleService.findRightsByRole(role);
//            role.getRights().addAll(rights);
//            model.addAttribute("rights", rights);session.setAttribute("loginUser", user);return "redirect:/main";}catch (UnknownAccountException | IncorrectCredentialsException e) {model.addAttribute("msg", "用户名或密码错误,登录失败!");return "login";}catch (LockedAccountException e) {model.addAttribute("msg", "用户被禁用,登录失败!");return "login";}catch (AuthenticationException e) {model.addAttribute("msg", "认证异常,登录失败!");return "login";}}@RequestMapping("/logout")public String logout(HttpSession session) {session.removeAttribute("loginUser");SecurityUtils.getSubject().logout(); // shiro 注销return "redirect:/main";  //重定向 保证删除Cookie}
}

 

ShiroConfig:

package com.ty.springbootshiro.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.ty.springbootshiro.entity.Right;
import com.ty.springbootshiro.service.RoleService;
import jakarta.annotation.Resource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.metadata.ManagedOperation;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** ShiroConfig** @aurhor Administrator  whs* @since 2024/10/8*/
@Configuration
public class ShiroConfig {@Resourceprivate RoleService roleService;/*** 开启Shiro注解* @return*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** 开启aop注解支持* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() {  // thymeleaf 页面上使用 shiro 标签return new ShiroDialect();}@Beanpublic MyShiroRealm myShiroRealm() {   // 自定义RealmMyShiroRealm myShiroRealm = new MyShiroRealm();return myShiroRealm;}@Beanpublic SecurityManager securityManager() {  // 安全管理器 SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//注入beansecurityManager.setRealm(myShiroRealm());SecurityUtils.setSecurityManager(securityManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { // 过滤器 权限验证ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();// 注入securityManagershiroFilterFactory.setSecurityManager(securityManager);// 权限验证 : 使用 Filter 控制资源(URL)的访问shiroFilterFactory.setLoginUrl("/index");shiroFilterFactory.setSuccessUrl("/main");shiroFilterFactory.setUnauthorizedUrl("/403"); //没有权限跳转到403Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 必须使用LinkedHashMap 有序集合// 配置可以匿名访问资源的url: 静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/fonts/**", "anon");filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/localcss/**", "anon");filterChainDefinitionMap.put("/localjs/**", "anon");filterChainDefinitionMap.put("/login/**", "anon");filterChainDefinitionMap.put("/logout/**", "anon");  // 注销过滤器 , 自动注销// 配置需要特定权限才能范文的资源的url// 静态授权: 包括全部需要特定权限才能访问的资源URlfilterChainDefinitionMap.put("/user/list", "perms[用户列表]");filterChainDefinitionMap.put("/user/add", "perms[用户添加]");filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");filterChainDefinitionMap.put("/user/del", "perms[用户删除]");// 动态授权List<Right> rights = roleService.findAllRights();for (Right right : rights) {if (right.getRightUrl()!=null && right.getRightUrl().trim().equals("")) { // .startsWith("/user/")filterChainDefinitionMap.put(right.getRightUrl(), "perms["+right.getRightUrl()+"]");}}// 配置认证访问 : 其他资源URl必须认证通过才能访问filterChainDefinitionMap.put("/**", "authc");  // 必须放在过滤器链的最后面shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactory;}}

 shiro 控制的三种状态:

1.

 

2. 

 

3.

 

 以上便是简单的 shiro 认证 下一章我会讲解什么是shiro 授权

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

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

相关文章

Linux执行source /etc/profile命令报错:权限不够问(已解决)

1.问题 明明以root账号登录Linux系统&#xff0c;在终端执行命令source /etc/profile时 显示权限不够 如下图&#xff1a; 2.问题原因 可能在编辑 /etc/profile 这个文件时不小心把开头的 井号 ‘#’ 给删除了 如图&#xff1a; 这里一定要有# 3.解决办法 进入/etc/pro…

扫雷(C 语言)

目录 一、游戏设计分析二、各个步骤的代码实现1. 游戏菜单界面的实现2. 游戏初始化3. 开始扫雷 三、完整代码四、总结 一、游戏设计分析 本次设计的扫雷游戏是展示一个 9 * 9 的棋盘&#xff0c;然后输入坐标进行判断&#xff0c;若是雷&#xff0c;则游戏结束&#xff0c;否则…

字节内部整理的软件测试面试题(含文档)

常见的面试题汇总 1、你做了几年的测试、自动化测试&#xff0c;说一下 selenium 的原理是什么&#xff1f; 我做了五年的测试&#xff0c;1年的自动化测试&#xff1b; selenium 它是用 http 协议来连接 webdriver &#xff0c;客户端可以使用 Java 或者 Python 各种编程语言…

【网络安全】未加密的F5 BIG-IP Cookie存在严重漏洞将被攻击者利用

文章目录 未加密的F5 BIG-IP Cookie存在严重漏洞将被攻击者利用F5 会话 Cookie推荐阅读 未加密的F5 BIG-IP Cookie存在严重漏洞将被攻击者利用 网络安全和基础设施安全局发布最新警告称&#xff0c;已观察到威胁行为者滥用未加密的持久性F5 BIG-IP Cookie来识别并针对目标网络…

电能质量的危害主要是哪些?我们该如何应对电能质量故障所带来的损失?

电能质量治理在现代配电系统中的必要性日益凸显。随着可再生能源和智能电网技术的广泛应用&#xff0c;电力系统面临着频率波动、谐波污染和电压不稳定等问题。这些问题不仅影响了电力的可靠性和安全性&#xff0c;还可能导致设备损坏和能效降低。因此&#xff0c;实施电能质量…

Pagehelper获取total错误

前言 在使用若依框架的pagehelper时&#xff0c;给分页表设置数据的时候前端只收到了分页的那一页的数据&#xff0c;总记录数不符合要求 我想要的效果如下&#xff0c;可以实现分页&#xff0c;和显示总记录数 但是实际情况为 但是我的数据库有11条记录&#xff0c;他这里明…

QCY开放式耳机值得买吗?南卡、QCY、韶音开放式耳机最全测评!

​开放式耳机最近还挺火的&#xff0c;因为相对于传统的入耳式耳机来说&#xff0c;它佩戴起来更舒适&#xff0c;也更卫生&#xff0c;更加适配运动场景&#xff0c;现在不少的健身或者运动博主都选择了开放式耳机&#xff0c;那么作为一个同样喜欢跑步的数码博主&#xff0c;…

开源一个C缓存库

1 简介 在当下的视频点播应用场景下&#xff0c;端侧对视频缓存的需求可谓刚需&#xff0c;一方面可以为公司节省流量成本&#xff0c;一方面也可以提升用户的播放体验&#xff0c;有一石二鸟之效。 近期&#xff0c;本人用C写了一个缓存库&#xff0c;支持iOS/Android/harmony…

java项目之信息化在线教学平台的设计与实现(源码+文档)

项目简介 信息化在线教学平台的设计与实现实现了以下功能&#xff1a; 信息化在线教学平台的设计与实现的主要使用者管理员功能有个人中心&#xff0c;学生信息管理&#xff0c;教师信息管理&#xff0c;教学信息管理&#xff0c;学生成绩管理&#xff0c;留言板管理&#xf…

番外篇 | 史上最全的关于CV的一些经典注意力机制代码汇总

前言:Hello大家好,我是小哥谈。注意力是人类认知系统的核心部分,它允许我们在各种感官输入中筛选和专注于特定信息。这一能力帮助我们处理海量的信息,关注重要的事物,而不会被次要的事物淹没。受到人类认知系统的启发,计算机科学家开发了注意力机制,这种机制模仿人类的这…

《语音识别芯片选型全攻略》

《语音识别芯片选型全攻略》 一、语音识别芯片性能评估&#xff08;一&#xff09;主控芯片性能评估&#xff08;二&#xff09;接口需求分析&#xff08;三&#xff09;可靠性评估&#xff08;四&#xff09;生产工艺考量&#xff08;五&#xff09;湿敏等级判断 二、语音识别…

倍福TwinCAT程序中遇到的bug

文章目录 问题描述&#xff1a;TwinCAT嵌入式控制器CX5140在上电启动后&#xff0c;X001网口接网线通讯灯不亮&#xff0c;软件扫描不到硬件网口 解决方法&#xff1a;硬件断电重启后&#xff0c;X001网口恢复正常 问题描述&#xff1a;TwinCAT软件点击激活配置后&#xff0c;…

汽车免拆诊断案例 | 2022款大众捷达VS5车行驶中挡位偶尔会锁在D3挡

故障现象  一辆2022款大众捷达VS5汽车&#xff0c;搭载EA211发动机和手自一体变速器&#xff0c;累计行驶里程约为4.5万km。该车行驶中挡位偶尔会锁在D3挡&#xff0c;车速最高约50 km/h&#xff0c;且组合仪表上的发动机故障灯和EPC灯异常点亮。 故障诊断  用故障检测仪检…

SQL语句查询

SQL语句查询 查询产生一个虚拟表 看到的是表形式显示的结果&#xff0c;但结果并不真正存储 每次执行查询只是从数据表中提取数据&#xff0c;并按照表的形式显示出来 查询语法 SELECT <列名> FROM <表名> [WHERE <查询条件表达式>] SELECT …

【python书籍-附电子版】Python入门零基础必看书籍,python编程入门教程指南,从入门到精通,这几本书太牛了!!

今天为大家推荐的“Python 编程三剑客”是新手小白学习编程的不二之选&#xff01;这三本书分别从不同的角度&#xff0c;对 Python 编程进行了详细的解析。 &#xff08;领取方式见文末&#xff09; 一《Python编程&#xff1a;从入门到实践》 第一本为你打下坚实的基础&am…

详解安卓和IOS的唤起APP的机制,包括第三方平台的唤起方法比如微信

网页唤起APP是一种常见的跨平台交互方式&#xff0c;它允许用户从网页直接跳转到移动应用程序。 这种技术广泛应用于各种场景&#xff0c;比如让用户在浏览器中点击链接后直接打开某个应用&#xff0c;或者从网页引导用户下载安装应用。实现这一功能主要依赖于URL Scheme、Univ…

基于Java+SpringBoot+Vue的网上购物商城系统研发

基于JavaSpringBootVue的网上购物商城系统研发 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345…

门店收银系统源码-php+flutter+uniapp

1. 系统开发语言 核心开发语言: PHP、HTML5、Dart 后台接口: PHP7.3 后台管理网站: HTML5vue2.0element-uicssjs 线下收银台&#xff08;安卓/PC收银、安卓自助收银&#xff09;: Dart3 框架&#xff1a;Flutter 3.19.6 移动店务助手: uniapp 线上商城: uniapp 2.线下收…

【虚拟化】内核级虚拟化技术KVM介绍,全/半虚拟化的区别,使用libvirt搭建虚拟化平台(go/java/c++)

【虚拟化】内核级虚拟化技术KVM介绍&#xff0c;全/半虚拟化的区别&#xff0c;使用libvirt搭建虚拟化平台&#xff08;go/java/c&#xff09; 文章目录 1、虚拟化技术分类与架构&#xff08;KVM&#xff0c;Xen&#xff09;&#xff0c;全/半虚拟化的区别2、libvirt介绍3、使用…

同行评审流程详解

同行评审流程中&#xff0c;稿件被拒很正常&#xff0c;可能是由于稿件结构有问题&#xff0c;论据不足&#xff0c;表达不准确等诸多因素&#xff0c;接下来带你走一遍同行评审流程&#xff0c;看论文投稿过程中有哪些拦路虎&#xff0c;掌握拒稿因素&#xff0c;论文投稿才能…