SpringBoot实战(二十五)集成 Shiro

news2024/11/30 2:48:25

目录

    • 一、Shiro 简介
      • 1.1 Shiro 定义
      • 1.2 Shiro 核心组件
      • 1.3 Shiro 认证过程
    • 二、SpringBoot集成
      • 2.1 集成思路
      • 2.2 Maven依赖
      • 2.3 自定义 Realm
      • 2.4 Shiro 配置类
      • 2.5 静态资源映射
      • 2.6 AuthController
      • 2.7 User 实体
      • 2.8 用户接口类
      • 2.9 用户接口实现类
      • 2.10 OrderController(鉴权测试)
    • 三、测试
      • 测试1:跳转登录页面
      • 测试2:注册用户
      • 测试3:登录主页
      • 测试4:权限校验
    • 四、注意

  • 官网地址: https://shiro.apache.org/
  • 中文文档: https://www.docs4dev.com/docs/zh/apache-shiro/1.5.3/reference/introduction.html
  • Spring集成Shiro官方文档: https://shiro.apache.org/spring-framework.html
  • GitHub: https://github.com/apache/shiro

一、Shiro 简介

1.1 Shiro 定义

Apache Shiro:是一款 Java 安全框架,不依赖任何容器,可以运行在 Java 项目中,它的主要作用是做身份认证、授权、会话管理和加密等操作。

其实不用 Shiro,我们使用原生 Java API 就可以实现安全管理,使用过滤器去拦截用户的各种请求,然后判断是否登录、是否拥有权限即可。但是对于一个大型的系统,分散去管理编写这些过滤器的逻辑会比较麻烦,不成体系,所以需要使用结构化、工程化、系统化的解决方案。

与 Spring Security 相比,shiro 属于轻量级框架,相对于 Spring Security 简单的多,也没有那么复杂。

1.2 Shiro 核心组件

Shiro 的运行机制如下图所示:

在这里插入图片描述

1)UsernamePasswordToken:封装用户登录信息,根据用户的登录信息创建令牌 token,用于验证令牌是否具有合法身份以及相关权限。

2)SecurityManager:核心部分,负责安全认证与授权。

3)Subject:一个抽象概念,包含了用户信息。

4)Realm:开发者自定义的模块,根据项目的需求,验证和授权的逻辑在 Realm 中实现。

5)AuthenticationInfo:用户的角色信息集合,认证时使用。

6)AuthorizationInfo:角色的权限信息集合,授权时使用。

7)DefaultWebSecurityManager:安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 中进行管理才能生效。

8)ShiroFilterFactoryBean:过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建一个个 Filter 对象来完成。

1.3 Shiro 认证过程

在这里插入图片描述


二、SpringBoot集成

2.1 集成思路

SpringBoot 集成 Shiro 思路图如下:

在这里插入图片描述

项目包结构如下:

在这里插入图片描述

2.2 Maven依赖

<!--Shiro-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.12.0</version>
</dependency>

2.3 自定义 Realm

自定义 Realm 主要实现了两大模块:

  • 认证:根据用户名,查询密码,然后封装返回 SimpleAuthenticationInfo 认证信息。
  • 授权:根据用户名,查询角色和权限,然后封装返回 SimpleAuthorizationInfo 授权信息。

CustomRealm.java

import com.demo.module.entity.User;
import com.demo.module.service.UserService;
import com.demo.util.SpringUtils;
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.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * <p> @Title CustomRealm
 * <p> @Description 自定义Realm
 *
 * @author ACGkaka
 * @date 2023/10/15 12:42
 */
public class CustomRealm extends AuthorizingRealm {

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取用户名
        String principal = (String) authenticationToken.getPrincipal();
        // 根据用户名查询数据库
        UserService userService = SpringUtils.getBean(UserService.class);
        User user = userService.findByUsername(principal);
        if (user != null) {
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()), this.getName());
        }
        return null;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取用户名
        String principal = (String) principalCollection.getPrimaryPrincipal();
        if ("admin".equals(principal)) {
            // 管理员拥有所有权限
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRole("admin");
            info.addStringPermission("admin:*");
            info.addRole("user");
            info.addStringPermission("user:find:*");
            return info;
        }
        return null;
    }
}

2.4 Shiro 配置类

配置类中指定了如下内容:

  • 需要进行鉴权的资源路径;
  • 指定自定义 Realm;
  • 加密规则。

ShiroConfig.java

import com.demo.config.shiro.realm.CustomRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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;

/**
 * <p> @Title ShiroConfig
 * <p> @Description Shiro配置类
 *
 * @author ACGkaka
 * @date 2023/10/15 12:44
 */
@Configuration
public class ShiroConfig {

    /**
     * ShiroFilter过滤所有请求
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 给ShiroFilter配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 配置系统公共资源、系统受限资源(公共资源必须在受限资源上面,不然会造成死循环)
        Map<String, String> map = new HashMap<>();
        // 系统公共资源
        map.put("/login", "anon");
        map.put("/register", "anon");
        map.put("/static/**", "anon");
        // 受限资源
        map.put("/**", "authc");

        // 设置认证界面路径
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    /**
     * 创建安全管理器
     */
    @Bean
    public DefaultWebSecurityManager securityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * 创建自定义Realm
     */
    @Bean
    public Realm realm() {
        CustomRealm realm = new CustomRealm();
        // 设置使用哈希凭证匹配
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置使用MD5加密算法
        credentialsMatcher.setHashAlgorithmName("MD5");
        // 设置散列次数:加密次数
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;
    }
}

2.5 静态资源映射

静态资源映射主要将 cssjs 等静态文件夹映射到浏览器端,方便页面加载。

在这里插入图片描述

WebConfiguration.java

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * SpringBoot静态路径配置
 *
 * @author ACGkaka
 * @date 2019/11/27 15:38
 */
@Configuration
@Primary
public class WebConfiguration implements WebMvcConfigurer {

    /**
     * 访问外部文件配置
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/css/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/static/css/");
        registry.addResourceHandler("/static/js/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/static/js/");
        WebMvcConfigurer.super.addResourceHandlers(registry);
    }
}

2.6 AuthController

主要用于进行 thymeleaf 模板引擎的页面跳转,以及登录、注册、退出登录等功能的实现。

AuthController.java

import com.demo.module.entity.User;
import com.demo.module.service.UserService;
import lombok.AllArgsConstructor;
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.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import javax.annotation.Resource;

/**
 * <p> @Title IndexController
 * <p> @Description 鉴权Controller
 *
 * @author ACGkaka
 * @date 2019/10/23 20:23
 */
@Controller
@AllArgsConstructor
public class AuthController {

    @Resource
    private UserService userService;

    /**
     * 默认跳转主页
     */
    @GetMapping("/")
    public String showIndex() {
        return "redirect:/index";
    }

    /**
     * 主页
     */
    @GetMapping("/index")
    public String index() {
        return "index.html";
    }

    /**
     * 主页
     */
    @GetMapping("/login")
    public String login() {
        return "login.html";
    }

    /**
     * 注册页
     */
    @GetMapping("/register")
    public String register() {
        return "/register.html";
    }

    /**
     * 登录
     */
    @PostMapping("/login")
    public String login(String username, String password) {
        // 获取主题对象
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username, password));
            System.out.println("登录成功!!!");
            return "redirect:/index";
        } catch (UnknownAccountException e) {
            System.out.println("用户错误!!!");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误!!!");
        }
        return "redirect:/login";
    }

    /**
     * 注册
     */
    @PostMapping("/register")
    public String register(User user) {
        userService.register(user);
        return "redirect:/login.html";
    }

    /**
     * 退出登录
     */
    @GetMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.html";
    }
}

2.7 User 实体

封装了用户的基本属性。

User.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * <p>
 * 用户表
 * </p>
 *
 * @author ACGkaka
 * @since 2021-04-25
 */
@Data
@TableName("t_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 随机盐
     */
    private String salt;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}

2.8 用户接口类

封装了注册(用户新增)和根据用户名查找接口。

UserService.java

import com.baomidou.mybatisplus.extension.service.IService;
import com.demo.module.entity.User;

/**
 * 用户表 服务类
 */
public interface UserService extends IService<User> {

    /**
     * 注册
     * @param user 用户
     */
    void register(User user);

    /**
     * 根据用户名查询用户
     * @param principal 用户名
     * @return 用户
     */
    User findByUsername(String principal);
}

2.9 用户接口实现类

实现了注册(用户新增)和根据用户名查找功能。

UserServiceImpl.java

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.module.entity.User;
import com.demo.module.mapper.UserMapper;
import com.demo.module.service.UserService;
import com.demo.util.SaltUtils;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author ACGkaka
 * @since 2021-04-25
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public void register(User user) {
        // 注册用户
        checkRegisterUser(user);

        // 1.生成随机盐
        String salt = SaltUtils.getSalt(8);
        // 2.将随机盐保存到数据库
        user.setSalt(salt);
        // 3.明文密码进行MD5 + salt + hash散列次数
        Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
        user.setPassword(md5Hash.toHex());
        // 4.保存用户
        this.save(user);
    }

    @Override
    public User findByUsername(String principal) {
        // 根据用户名查询用户
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, principal);
        return this.getOne(queryWrapper);
    }


    // ------------------------------------------------------------------------------------------
    // 内部方法
    // ------------------------------------------------------------------------------------------

    /**
     * 校验注册用户
     * @param user 用户
     */
    private void checkRegisterUser(User user) {
        if (user == null) {
            throw new RuntimeException("用户信息不能为空");
        }
        if (user.getUsername() == null || "".equals(user.getUsername())) {
            throw new RuntimeException("用户名不能为空");
        }
        if (user.getPassword() == null || "".equals(user.getPassword())) {
            throw new RuntimeException("密码不能为空");
        }
        // 判断用户名是否已存在
        User existUser = this.getOne(new UpdateWrapper<User>().eq("username", user.getUsername()));
        if (existUser != null) {
            throw new RuntimeException("用户名已存在");
        }
    }
}

2.10 OrderController(鉴权测试)

在自定义 Realm 配置好用户权限后,用于测试对用户权限和角色权限的控制。

OrderController.java

import com.demo.common.Result;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p> @Title OrderController
 * <p> @Description 订单控制器
 *
 * @author ACGkaka
 * @date 2023/10/15 17:15
 */
@RestController
@RequestMapping("/order")
public class OrderController {

    /**
     * 新增订单(代码实现权限判断)
     */
    @GetMapping("/add")
    public Result<Object> add() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.hasRole("admin")) {
            return Result.succeed().setData("新增订单成功");
        } else {
            return Result.failed().setData("操作失败,无权访问");
        }
    }

    /**
     * 编辑订单(注解实现权限判断)
     */
    @RequiresRoles({"admin", "user"}) // 用来判断角色,同时拥有admin和user角色才能访问
    @RequiresPermissions("user:edit:01") // 用来判断权限,拥有user:edit:01权限才能访问
    @GetMapping("/edit")
    public String edit() {
        System.out.println("编辑订单");
        return "redirect:/index";
    }
}

三、测试

测试1:跳转登录页面

访问地址:http://localhost:8081,我们配置了根路径默认访问主页,由于用户没有登录,会默认跳转登录页面。

在这里插入图片描述

测试2:注册用户

访问地址:http://localhost:8081/register,输入用户名和密码后,系统会在数据库中新增用户信息,自动跳转登录页面,输入刚才的用户名和密码即可登录。

在这里插入图片描述

测试3:登录主页

输入正确的用户名密码,即可登录至主页。

在这里插入图片描述

测试4:权限校验

我们在主页中加入了 OrderController 中的接口,用于测试权限校验。

在这里插入图片描述

当使用非 admin 用户进行登录的时候,代码实现权限判断的 /add 接口,报错如下:

在这里插入图片描述

Shiro 注解实现权限判断的 /edit 接口,报错如下:

在这里插入图片描述

从提示信息可以看到,返回了状态码 500,报错信息为:当前用户的主体没有 admin 权限。

由此可见,两种鉴权结果均成功生效,具体使用哪一种由业务场景来定。

四、注意

由于 Shiro 核心是通过 Session 来实现用户登录的,所以有很多场景不支持,比如:

1)Session 默认存储在内存中,重启应用后所有用户登录状态会失效

2)默认配置不支持分布式,需要分布式部署的时候,可以通过 Redis数据库方式来同步 Session 会话。

整理完毕,完结撒花~ 🌻





参考地址:

1.SpringBoot之整合Shiro(最详细),https://blog.csdn.net/Yearingforthefuture/article/details/117384035

2.超详细 Spring Boot 整合 Shiro 教程!https://cloud.tencent.com/developer/article/1643122

3.springboot整合shiro(完整版),https://blog.csdn.net/w399038956/article/details/120434244

4.Shiro安全框架【快速入门】就这一篇!https://zhuanlan.zhihu.com/p/54176956

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

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

相关文章

手把手教你使用Python从零开始搭建感知器

大家好&#xff0c;今天本文将展示如何从零开始实现神经网络的最基本要素&#xff08;感知器&#xff09;&#xff0c;以及人工智能的基本模块背后的数学原理。 虽然人工智能和机器学习等术语已经成为流行词汇&#xff0c;每天都会听到或谈论这些概念&#xff0c;但它们背后的…

软件开发介绍

一、软件开发整体介绍 作为一名软件开发工程师&#xff0c;我们需要了解在软件开发过程中的开发流程&#xff0c;以及软件开发过程中涉及到的岗位角色&#xff0c;角色的分工、职责&#xff0c;并了解软件开发中涉及到的三种软件环境。 1.1 软件开发流程 第一阶段&#xff1a…

MQTT解读【全网最易懂】

目录 前言 一、MQTT相比于TCP长连接的优势 1、协议更标准 2、MQTT协议制定好了很多利于物联网的功能 3、理解数据内容&#xff0c;用数据产生价值 二、选择MQTT还是TCP长连接透传 1、原始的业务场景 2、端对端M2M场景——无人汽车 3、APP控制设备端场景——智能家居、智…

RK3588的GPU驱动和桌面环境

这里主要是以orange pi 5 plus为对象作一个简单的笔记 首先看rk3588的gpu介绍&#xff0c;它用的是ARM的GPU&#xff0c;支持openGL ES和openCL&#xff08;支持什么其实跟GPU驱动有关&#xff0c;arm官方闭源GPU驱动就只支持这两个&#xff09; opi官方提供了debian的xfce和…

Linux网络编程系列之服务器编程——多路复用模型

Linux网络编程系列 &#xff08;够吃&#xff0c;管饱&#xff09; 1、Linux网络编程系列之网络编程基础 2、Linux网络编程系列之TCP协议编程 3、Linux网络编程系列之UDP协议编程 4、Linux网络编程系列之UDP广播 5、Linux网络编程系列之UDP组播 6、Linux网络编程系列之服务器编…

ROS-6.参数的使用

参数的使用 参数服务结构命令行的使用方式运行小海龟命令介绍查看参数获取参数值设置参数保存参数到文件从文件导入参数 通过程序操作创建节点修改cmake编译运行 参数服务结构 ros中存在参数服务管理服务&#xff0c;管理这所有参数&#xff0c;所有节点剋订阅和发布这些节点 …

第三章 内存管理 三、覆盖与交换

目录 一、覆盖技术 二、交换技术 三、总结 一、覆盖技术 1、在覆盖技术中&#xff0c;我们要找到程序的调用结构。 2、因为这些程序不可能同时被调用&#xff08;互斥调用&#xff09;&#xff0c;所以我们只需要选出需要空间最大的程序。 3、在物理内存中开拓一片与最大程…

ABB机器人关于重定位移动讲解

关于机器人如何重定位移动&#xff0c;首先来看一下示教器上的重定位移动是在哪。 从图中所示的坐标位置和操纵杆方向得知&#xff0c;重定位的本质是绕X、Y、Z轴的旋转。那么实现跟摇杆一样的操作&#xff0c;就可以通过改变当前位置的欧拉角来实现&#xff0c;参考Rapid指令…

小米笔记本Pro 15.6“频繁蓝屏解决办法

一、事情的缘起 2020年3月&#xff0c;我在小米官网购买这个笔记本&#xff0c;型号为&#xff1a;小米笔记本Pro 15.6" 2019款 四核i5 8G MX250 深灰。当时买这款笔记本&#xff0c;也是考虑到它屏幕比较大&#xff0c;而且配置也不错&#xff0c;四核8G的内存也足够我办…

测试需要写测试用例吗?

如何理解软件的质量 我们都知道&#xff0c;一个软件从无到有要经过需求设计、编码实现、测试验证、部署发布这四个主要环节。 需求来源于用户反馈、市场调研或者商业判断。意指在市场行为中&#xff0c;部分人群存在某些诉求或痛点&#xff0c;只要想办法满足这些人群的诉求…

并行Stream的性能测试

final long count 200_000_000;Random random new Random();//创建2亿条的listList<Integer> list Stream.generate(() -> random.nextInt(20)).limit(count).collect(Collectors.toList());// 顺序处理long startTime System.currentTimeMillis();list.stream().…

C语言联合体和枚举

C语言联合体和枚举 文章目录 C语言联合体和枚举一、联合体①联合体简介②联合体大小的计算 二、枚举 一、联合体 ①联合体简介 union Un {char c;int i; };像结构体一样&#xff0c;联合体也是由⼀个或者多个成员构成&#xff0c;这些成员可以不同的类型。但是编译器只为最大…

【Python数据分析工具】

文章目录 概要整体架构流程技术名词解释 概要 数据分析是一种通过收集、处理、分析和解释大量数据&#xff0c;以发现有价值信息、洞察趋势、制定决策并解决问题的过程。在现代科技和互联网的推动下&#xff0c;数据分析变得日益重要。它不仅仅是对数字和图表的简单解释&#…

GCOV覆盖率分析

安全之安全(security)博客目录导读 覆盖率分析汇总 目录 一、GCOV简介 二、GCOV使用示例 三、GCOV编译命令 四、运行并生成覆盖率报告 五、覆盖率报告分析 一、GCOV简介 因为动态代码分析可能只覆盖部分代码&#xff0c;所以我们需要一个代码覆盖工具&#xff0c;以了解…

APP备案避坑指南,值得收藏

目录 什么时间节点前需完成备案&#xff1f; APP/小程序一定要做备案吗&#xff1f; 涉及前置审批的APP有哪些&#xff1f; APP 支持安卓、IOS 多个运行平台&#xff0c;应该备案多少次&#xff1f; 企业是自有服务器&#xff0c;该如何进行APP备案&#xff1f; APP备案可…

探索服务器的无限潜能:创意项目、在线社区与更多可能

文章目录 1. 创意项目的孵化器1.1 托管你的应用1.2 测试和开发1.3 制定和实施你的计划 2. 构建在线社区2.1 自定义社交网络2.2 数据控制2.3 扩展和改进 3. 其他创意可能性3.1 博客和媒体网站3.2游戏服务器3.3 云存储3.4 数据分析3.5 远程办公 结论 &#x1f389;欢迎来到Java学…

你不一定知道的七种进程间通信方式

一、前言 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到&#xff0c;所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷贝到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信…

Easysearch压缩模式深度比较:ZSTD+source_reuse的优势分析

引言 在使用 Easysearch 时&#xff0c;如何在存储和查询性能之间找到平衡是一个常见的挑战。Easysearch 具备多种压缩模式&#xff0c;各有千秋。本文将重点探讨一种特别的压缩模式&#xff1a;zstd source_reuse&#xff0c;我们最近重新优化了 source_reuse,使得它在吞吐量…

【ROS2RUN源码解析:解决ROS2 run命令找不到问题的详细流程】

文章目录 概要整体架构流程技术名词解释小结 概要 当你在使用ROS2时遇到找不到可执行文件的错误时&#xff0c;首先需要执行以下步骤来诊断问题。首先&#xff0c;使用命令printenv AMENT_PREFIX_PATH&#xff08;或者ros2 pkg prefix加上包的名称&#xff09;来检查你的功能包…

AI :微软推出 AutoGen 框架,帮开发者创建基于大语言模型的复杂应用

本心、输入输出、结果 文章目录 AI :微软推出 AutoGen 框架,帮开发者创建基于大语言模型的复杂应用前言AutoGen 简介快速入门AutoGen 安装相关支持相关代码相关架构图弘扬爱国精神AI :微软推出 AutoGen 框架,帮开发者创建基于大语言模型的复杂应用 编辑:简简单单 Online z…