SpringBoot系列——使用Spring Cache和Redis实现查询数据缓存

文章目录

    • 1. 前言
    • 2. 缓存
      • 2.1 什么是缓存
      • 2.2 使用缓存的好处
      • 2.3 缓存的成本
      • 2.4 使用Spring Cache和Redis的优点
    • 3. Spring Cache基础知识
      • 3.1 Spring Cache的核心概念
      • 3.2 Spring Cache的注解
        • 3.2.1 SpEL表达式
        • 3.2.2 @Cacheable
        • 3.2.3 @CachePut
        • 3.2.4 @CacheEvict
    • 4. 实现查询数据缓存
      • 4.1 准备工作
      • 4.2 添加依赖
      • 4.3 修改配置文件
      • 4.4 配置缓存管理器
      • 4.5 使用Spring Cache注解
      • 4.6 测试
        • 4.6.1 查询测试
        • 4.6.2 更新、删除测试
    • 5. 总结

1. 前言

在现代应用程序中,查询缓存的使用已经变得越来越普遍。它不仅能够显著提高系统的性能,还能提升用户体验。缓存通过在内存中存储频繁访问的数据,减少对数据库或其他存储系统的访问,从而加快数据读取速度。在这篇文章中,我们将探讨缓存的基本概念、重要性以及如何使用Spring Cache和Redis实现查询数据缓存 。

2. 缓存

2.1 什么是缓存

缓存是一种临时存储机制,用于在内存中保存频繁访问的数据。它可以是硬件(如CPU缓存)或软件(如应用程序缓存)。缓存的主要目的是通过减少数据访问的延迟,提高系统的响应速度。以下是缓存的一些关键特性:

  • 临时性:缓存中的数据通常是临时的,会在一段时间后失效或被替换。
  • 快速访问:由于缓存数据存储在内存中,访问速度非常快。
  • 空间有限:缓存的存储空间通常有限,因此需要有效的管理策略,如LRU(最近最少使用)策略。

2.2 使用缓存的好处

  1. 提高性能:缓存可以显著减少数据读取的时间,因为内存访问速度比硬盘或网络存储快很多。
  2. 减轻数据库负载:缓存可以减少数据库的查询次数,从而减轻数据库的负载,提升整体系统的稳定性和可扩展性。
  3. 节省资源:通过减少对后端系统的访问,缓存可以帮助节省带宽和计算资源。
  4. 提高用户体验:快速的数据访问可以显著提升用户体验,特别是在需要频繁读取数据的应用场景中。

2.3 缓存的成本

  1. 内存消耗:缓存需要占用系统的内存资源,过多的缓存可能会影响其他应用程序的性能。
  2. 数据一致性:缓存中的数据可能会与数据库中的数据不一致,尤其是在数据频繁更新的场景中。需要设计有效的缓存失效策略来保证数据的一致性。
  3. 复杂性增加:引入缓存机制会增加系统的复杂性,需要处理缓存的管理、更新和失效等问题。
  4. 维护成本:缓存系统需要定期监控和维护,以确保其高效运行。

2.4 使用Spring Cache和Redis的优点

为了实现高效的数据缓存,Spring Boot提供了Spring Cache模块,而Redis则是一个强大的缓存数据库。结合使用Spring Cache和Redis,能够充分发挥二者的优点,实现高效的数据缓存。

  • Spring Cache的优点
    • 简化缓存操作:Spring Cache提供了一系列注解(如@Cacheable@CachePut@CacheEvict),简化了缓存的使用,使开发者能够专注于业务逻辑。
    • 灵活的缓存管理:Spring Cache支持多种缓存提供者(如EhCache、Hazelcast、Redis等),可以根据具体需求选择合适的缓存实现。
    • 透明的缓存机制:Spring Cache使得缓存操作对业务代码透明,开发者无需关心缓存的具体实现细节。
  • Redis的优点
    • 高性能:由于数据存储在内存中,Redis的读写速度非常快,能够处理每秒数百万级别的请求。
    • 丰富的数据结构:Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,能够满足不同场景下的数据存储需求。
    • 持久化支持:虽然Redis主要用于内存存储,但它也提供了数据持久化的功能,可以将数据定期保存到磁盘,防止数据丢失。
    • 分布式支持:Redis支持主从复制、哨兵模式和集群模式,能够实现高可用性和数据的水平扩展。
    • 灵活的过期策略:Redis支持为每个键设置过期时间,自动删除过期数据,方便实现缓存失效策略。

3. Spring Cache基础知识

在Spring Boot中,Spring Cache提供了一套简洁且强大的缓存抽象机制,帮助开发者轻松地将缓存集成到应用程序中。以下是Spring Cache的一些核心概念和常用注解。

3.1 Spring Cache的核心概念

  1. CacheManager

    • 定义CacheManager是Spring Cache的核心接口,负责管理多个缓存实例。它是缓存操作的入口点,提供了获取和操作缓存实例的方法。
    • 实现:Spring提供了多种CacheManager实现,如ConcurrentMapCacheManagerEhCacheCacheManagerRedisCacheManager等。不同的实现适用于不同的缓存存储机制。
  2. Cache

    • 定义Cache是缓存的具体实现,负责存储和检索缓存数据。它提供了基本的缓存操作,如putgetevict等。
    • 实现:具体的Cache实现依赖于底层的缓存存储机制,如内存缓存、Redis缓存等。

3.2 Spring Cache的注解

3.2.1 SpEL表达式

因为Spring Cache使用SpEL表达式来动态生成缓存键,所以在学习Spring Cache的注解之前我们还要先简单了解一下SpEL表达式的语法,这部分可以先不看懂,在后面看注解的时候回来看即可。

SpEL表达式的语法类似于Java的表达式语法,支持以下几种操作:

  1. 字面量
    • 数字:1, 2.5
    • 字符串:'hello', "world"
    • 布尔值:true, false
    • 空值:null
  2. 属性和方法
    • 访问对象的属性:#user.name
    • 调用对象的方法:#user.getName()
  3. 运算符
    • 算术运算:+, -, *, /, %
    • 比较运算:==, !=, <, >, <=, >=
    • 逻辑运算:&&, ||, !
  4. 集合和数组
    • 访问集合元素:#users[0]
    • 集合操作:#users.size(), #users.isEmpty()
  5. 条件运算符
    • 三元运算符:condition ? trueValue : falseValue
    • Elvis运算符:expression ?: defaultValue
  6. 变量
    • 定义和使用变量:#variableName

接下来进入Spring Cache注解的学习:

3.2.2 @Cacheable
  • 作用@Cacheable注解用于标注需要缓存的方法。当该方法被调用时,Spring Cache会先检查缓存中是否存在对应的数据。如果存在,则直接返回缓存数据;如果不存在,则执行方法并将结果存入缓存。
  • 示例
    @RestController("/users")
    @RequiredArgsConstructor
    public class UserController {private final UserService userService;@Cacheable(value = "user", key = "#id")public User getUser(Long id) {// 获取用户的逻辑return userService.findById(id);}
    }
    
  • 参数
    • value:指定缓存的名称。
    • key:指定缓存的键,可以使用SpEL表达式。
3.2.3 @CachePut
  • 作用@CachePut注解用于标注需要更新缓存的方法。即使缓存中已经存在数据,该方法仍然会执行,并将结果更新到缓存中。
  • 示例
    @RestController("/users")
    @RequiredArgsConstructor
    public class UserController {private final UserService userService;@CachePut(value = "user", key = "#user.id")public User updateUser(User user) {// 更新用户的逻辑return userService.save(user);}
    }
    
  • 参数
    • value:指定缓存的名称。
    • key:指定缓存的键,可以使用SpEL表达式。
3.2.4 @CacheEvict
  • 作用@CacheEvict注解用于标注需要清除缓存的方法。当该方法被调用时,Spring Cache会清除对应的缓存数据。
  • 示例
    @RestController("/users")
    @RequiredArgsConstructor
    public class UserController {private final UserService userService;@CacheEvict(value = "user", key = "#id")public void deleteUser(Long id) {// 删除用户的逻辑userService.deleteById(id);}
    }
    
  • 参数
    • value:指定缓存的名称。
    • key:指定缓存的键,可以使用SpEL表达式。
    • allEntries:如果设置为true,则清除缓存中的所有数据。

4. 实现查询数据缓存

4.1 准备工作

  1. Redis安装与配置:

这里可以自行查找文章进行安装和配置,网上优质文章很多👻。

  1. 创建Product实体类:
@Data
@AllArgsConstructor
public class Product implements Serializable {private Long id;private String name;private Integer category;private String description;private Integer stock;}
  1. 创建枚举类ResultEnum
@Getter
public enum ResultEnum {/* 成功状态码 */SUCCESS(1, "操作成功!"),/* 错误状态码 */FAIL(0, "操作失败!"),/* 参数错误:10001-19999 */PARAM_IS_INVALID(10001, "参数无效"),PARAM_IS_BLANK(10002, "参数为空"),PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"),PARAM_NOT_COMPLETE(10004, "参数缺失"),/* 用户错误:20001-29999*/USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),USER_LOGIN_ERROR(20002, "账号不存在或密码错误"),USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),USER_NOT_EXIST(20004, "用户不存在"),USER_HAS_EXISTED(20005, "用户已存在"),/* 系统错误:40001-49999 */FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"),FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"),/* 数据错误:50001-599999 */RESULT_DATA_NONE(50001, "数据未找到"),DATA_IS_WRONG(50002, "数据有误"),DATA_ALREADY_EXISTED(50003, "数据已存在"),AUTH_CODE_ERROR(50004, "验证码错误"),/* 权限错误:70001-79999 */PERMISSION_UNAUTHENTICATED(70001, "此操作需要登陆系统!"),PERMISSION_UNAUTHORIZED(70002, "权限不足,无权操作!"),PERMISSION_EXPIRE(70003, "登录状态过期!"),PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),PERMISSION_LIMIT(70005, "访问次数受限制"),PERMISSION_TOKEN_INVALID(70006, "无效token"),PERMISSION_SIGNATURE_ERROR(70007, "签名失败");// 状态码int code;// 提示信息String message;ResultEnum(int code, String message) {this.code = code;this.message = message;}public int code() {return code;}public String message() {return message;}public void setCode(int code) {this.code = code;}public void setMessage(String message) {this.message = message;}
}
  1. 创建统一返回结果封装类Result

相关文章可以看这里:Spring Boot3统一结果封装

@Data
@NoArgsConstructor
public class Result<T> implements Serializable {// 操作代码Integer code;// 提示信息String message;// 结果数据T data;public Result(ResultEnum resultCode) {this.code = resultCode.code();this.message = resultCode.message();}public Result(ResultEnum resultCode, T data) {this.code = resultCode.code();this.message = resultCode.message();this.data = data;}public Result(String message) {this.message = message;}//成功返回封装-无数据public static Result<String> success() {return new Result<String>(ResultEnum.SUCCESS);}//成功返回封装-带数据public static <T> Result<T> success(T data) {return new Result<T>(ResultEnum.SUCCESS, data);}//失败返回封装-使用默认提示信息public static Result<String> error() {return new Result<String>(ResultEnum.FAIL);}//失败返回封装-使用返回结果枚举提示信息public static Result<String> error(ResultEnum resultCode) {return new Result<String>(resultCode);}//失败返回封装-使用自定义提示信息public static Result<String> error(String message) {return new Result<String>(message);}
}

4.2 添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.3 修改配置文件

spring:data:redis:# Redis服务器地址host: ${shijun.redis.host}# Redis服务器端口port: ${shijun.redis.port}# Redis服务器认证密码password: ${shijun.redis.password}# Redis数据库索引database: ${shijun.redis.database}

4.4 配置缓存管理器

/*** 配置类,用于设置缓存管理器及相关配置,以启用缓存功能** @author shijun* @date 2024/06/13*/
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {/*** 配置Redis键的序列化方式** @return StringRedisSerializer,用于序列化和反序列化Redis中的键*/private RedisSerializer<String> keySerializer() {return new StringRedisSerializer();}/*** 配置Redis值的序列化方式** @return GenericJackson2JsonRedisSerializer,使用Jackson库以JSON格式序列化和反序列化Redis中的值*/private RedisSerializer<Object> valueSerializer() {return new GenericJackson2JsonRedisSerializer();}/*** 缓存前缀,用于区分不同的缓存命名空间,一般以模块名或者服务名命名,这里暂时写cache*/public static final String CACHE_PREFIX = "cache:";/*** 配置缓存管理器,使用Redis作为缓存后端** @param redisConnectionFactory Redis连接工厂,用于创建Redis连接* @return RedisCacheManager,Redis缓存管理器实例*/@Beanpublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {// 配置序列化,解决乱码的问题,设置缓存名称的前缀和缓存条目的默认过期时间RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()// 设置键的序列化器.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))// 设置值的序列化器.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))// 设置缓存名称的前缀.computePrefixWith(name -> CACHE_PREFIX + name + ":")// 设置缓存条目的默认过期时间为300秒.entryTtl(Duration.ofSeconds(300));// 创建非锁定的Redis缓存写入器RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisConnectionFactory));// 返回Redis缓存管理器实例,使用上述配置return new RedisCacheManager(redisCacheWriter, config);}}

分析:
StringRedisSerializer :使用 StringRedisSerializer 将缓存的键序列化为字符串。因为Redis中的键通常是字符串类型,使用字符串序列化器可以确保键在Redis中以可读的形式存储,便于调试和管理。

GenericJackson2JsonRedisSerializer :使用 GenericJackson2JsonRedisSerializer 将缓存的值序列化为JSON格式,可读性高并且便于人工排查数据。

4.5 使用Spring Cache注解

由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有数据一致性问题存在,在一些并发场景会出现问题。

这里采用Cache Aside Pattern 即旁路缓存模式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案。

  • 读流程:
    image-20240613224855076

分析:

  1. 应用程序首先从缓存中查找数据。
  2. 如果缓存命中,则直接返回缓存中的数据。
  3. 如果缓存未命中,则从数据库中读取数据,并将读取到的数据写入缓存,以便
  4. 后续请求可以直接从缓存中获取。
  • 写流程
    image-20240613221100300

分析:

  1. 应用程序首先更新数据库中的数据。
  2. 然后使缓存中的对应数据失效
@Slf4j
@RestController("/products")
public class ProductController {/*** 根据ID获取产品信息* 通过@Cacheable注解,当请求的产品ID在缓存中存在时,直接从缓存中获取产品信息,减少数据库查询** @param id 产品ID* @return 返回查询结果,包含指定ID的产品信息*/@GetMapping("/getProductById")@Cacheable(value = "productsCache", key = "#id")public Result<Product> getProductById(Long id) {// 当从数据库获取数据时会打印,如果是从缓存中查询并不会执行到这里。log.info("从数据库获取产品: id = {}", id);Product product = new Product(id, "product", 100, "课本", 10);return Result.success(product);}/*** 更新产品信息* 通过@CacheEvict注解,当更新产品时,清除缓存中对应产品的数据,确保获取到最新的数据* 设置allEntries为true,表示清除整个缓存中的所有产品数据** @param product 产品对象,包含更新后的详细信息* @return 返回更新结果,成功更新时返回成功标志*/@PutMapping("/updateProduct")@CacheEvict(value = "productsCache", key = "#product.id")public Result updateProduct(@RequestBody Product product) {// 更新操作return Result.success();}/*** 删除指定ID的产品* 通过@CacheEvict注解,当删除产品时,清除缓存中对应产品的数据** @param id 待删除产品的ID* @return 返回删除结果,成功删除时返回成功标志*/@DeleteMapping("/deleteProductById")@CacheEvict(value = "productsCache", key = "#id")public Result deleteProductById(Long id) {// 删除操作return Result.success();}}

4.6 测试

4.6.1 查询测试
  1. 发送查询请求:
image-20240613203320506
  1. 查看Redis中的缓存数据:

通过观察可以发现CacheConfig类中的序列化配置起作用了,Redis中的数据不再是一堆乱码,并且在右上角还有我们之前配置的缓存的过期时间(我们之前配置的300s)。

image-20240613203847598
  1. 查看控制台发现本次查询为从数据库查询:
image-20240613212356495
  1. 再次发送会发现数据成功的查询了:
image-20240613203438434
  1. 再次查询控制台发现并没有输出从数据库获取产品: id = 1,说明本次查询为从Redis缓存中获取数据。
4.6.2 更新、删除测试

分别发送更新请求和删除请求,然后再次查看Redis中的缓存数据:

image-20240613215742944 image-20240613221930757 可以发现Redis当中对应的缓存数据被删除了,符合我们的设计: image-20240613204329734

5. 总结

在本文中,我们详细介绍了如何在Spring Boot项目中使用Spring Cache和Redis实现数据缓存,并简单讲解了使用Cache Aside Pattern来解决数据一致性问题,希望对大家学习有所帮助。如有问题,大家可以私信或者在评论区询问😊。

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

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

相关文章

递归函数知识点

基本概念 递归函数就是让函数自己调用自己。 static void Fun() {if (false){return;}Fun(); } 一个正确的递归函数 1.必须有结束调用的条件 2.用于体检判断的&#xff0c;这个条件&#xff0c;必须改变能够达到停止的目的。 实例 用递归函数打印出0~10。 递归函数就是…

Julia 文件读写

Julia 文件读写 Julia 是一种高性能的动态编程语言,特别适合于数值计算和科学计算。在数据处理和科学研究中,文件读写是一项基本且重要的技能。Julia 提供了一套丰富的函数和库来处理文件读写操作,使得文件操作变得简单而高效。 基本文件操作 打开和关闭文件 在 Julia 中…

eclipse创建maven项目

第一步&#xff1a;打开eclipse 我们选择java项目即可 点击finish即可 它会自动下载插件 然后在控制台上输入Y即可

聚合分析是Elasticsearch中非常强大的工具

Elasticsearch的聚合分析&#xff08;Aggregations&#xff09;是一种强大的功能&#xff0c;它允许用户对数据进行汇总和分析。聚合分析可以揭示数据中的模式、趋势和异常&#xff0c;非常适合用于生成报告、仪表板或进行复杂的数据分析。 ### 聚合分析的基本概念&#xff1a…

HCIE-QOS基本原理

QOS基本原理 QOS概述什么是QOSQoS服务模型区分服务模型QoS常用技术 (DiffServ模型)QoS数据处理流程 (DiffServ模型) QoS流分类和流标记QoS数据处理流程为什么需要流分类和流标记 简单流分类外部优先级 - VLAN报文外部优先级 - MPLS报文外部优先级 - IP报文各外部优先级间的对应…

C++ 字符串处理4-根据指定的分隔符将字符串分割为多个子串根据指定的分隔符将多个子串连接成一个字符串

1. 关键词 C 字符串处理 分割字符串 连接字符串 跨平台 2. strutil.h #pragma once#include <string> #include <vector>namespace cutl {/*** brief The type of vector strings used in this library.**/using strvec std::vector<std::string>;/*** b…

机器学习作业7——PCA

目录 一、原理 1.数据中心化 2.白数据与目标 3.协方差与协方差矩阵 4.特征值与特征向量 5.最终结果构造 二、代码 代码解释&#xff1a; 三、结果 结果解释&#xff1a; pca优缺点分析&#xff1a; 参考视频&#xff1a; 一、原理 目的&#xff1a; pca是为了将原…

辽宁普通测径仪升级智能测径仪后都有哪些改进?

关键字: 普通测径仪, 智能测径仪, 测径仪升级, 测径仪特点, 智能测径仪优势, 目前多数厂家测径仪的数据处理方式是单片机计算出最终结果&#xff0c;然后传输到工控机后期处理。这样的电路系统对轧钢现场的高温、高粉尘和强电磁干扰的环境适应性很差&#xff0c;使得同一厂家、…

基于对抗神经网络的图像生成

基于对抗神经网络的图像生成 生成对抗网络&#xff08;Generative Adversarial Network, GAN&#xff09;是一种深度学习模型&#xff0c;用于生成高质量、逼真的图像。由Ian Goodfellow等人在2014年提出&#xff0c;GAN已经成为图像生成领域的一个重要工具。GAN的核心思想是通…

JUC并发编程-第一天

JUC并发编程-第一天 JUC开发基础知识进程、线程、协程 JUC开发基础知识 先有进程&#xff0c;然后进程可以创建线程&#xff0c;线程是依附在进程里面的&#xff0c;线程里面包含多个协程 进程之间不共享全局变量&#xff0c;线程之间共享全局变量(线程通信就是用的这个&#x…

Spring boot集成log4j及日志配置详解,实战,ELK使用教程。

目录 引言一、SpringBoot 集成 Log4j1. 添加 Log4j 依赖2. 移除默认的Logback组件3. 创建 Log4j 配置文件4. 配置 Log4j2 日志文件 二、Log4j2 XML 文件配置详解基本结构Appenders 配置详解Loggers 配置详解 三、日志的作用四、日志数据采集与分析1. 日志数据采集2. 日志数据分…

如何选择靠谱的LabVIEW外包公司

概述 选择一家靠谱的LabVIEW外包公司是项目成功的关键。本文从公司成立时间、人员变动、团队稳定性、经验丰富度、主业聚焦度等多个角度进行分析比较&#xff0c;提供合理建议和注意事项&#xff0c;帮助你找到最合适的外包合作伙伴&#xff0c;确保项目顺利进行和高质量交付。…

经典的网站系统架构(入门级)

从开发到部署&#xff0c;从用户访问到底层数据库&#xff0c;介绍搭建网站系统的经典架构的10个核心部分。 &#xff08;图转自bytebytego&#xff0c;翻译整理by dogstar&#xff09; 1、使用Git管理和协同源代码&#xff0c;通过CI/CD或Git的Webhook方式自动同步更新部署到服…

6.2 文件的缓存位置

1. 文件的缓冲 1.1 缓冲说明 将文件内容写入到硬件设备时, 则需要进行系统调用, 这类I/O操作的耗时很长, 为了减少I/O操作的次数, 文件通常使用缓冲区. 当需要写入的字节数不足一个块时, 将数据放入缓冲区, 当数据凑够一个块的大小后才进行系统调用(即I/O操作).系统调用: 向…

java原子变量

在Java中&#xff0c;原子变量是一种特殊的变量&#xff0c;它们提供了一种不需要显式加锁的情况下进行线程安全的操作。Java.util.concurrent.atomic包提供了原子变量类&#xff0c;如AtomicInteger&#xff0c;AtomicLong等&#xff0c;它们利用底层硬件的原子操作来保证线程…

MyBatis 动态 SQL 的详细内容讲解

1. MyBatis 动态 SQL 的详细内容讲解 文章目录 1. MyBatis 动态 SQL 的详细内容讲解2. 准备工作3. if 标签4. where 标签5. trim 标签6. set 标签7. choose when otherwise 标签8. foreach 标签8.1 批量删除8.2 批量添加 9. SQL 标签与 include 标签10. 总结&#xff1a;11. 最…

使用pnpm创建vue3项目

https://pnpm.io/zh/ 全局安装&#xff1a; npm install -g pnpm 检查版本&#xff1a; pnpm -v 创建vue3项目&#xff1a; pnpm create vuelatest 项目装包&#xff1a; pnpm install 运行项目&#xff1a; pnpm dev 命令行&#xff1a; https://pnpm.io/zh/pnpm-cli pnpm …

C语言 | Leetcode C语言题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; int evalRPN(char** tokens, int tokensSize) {int n tokensSize;int stk[(n 1) / 2];memset(stk, 0, sizeof(stk));int index -1;for (int i 0; i < n; i) {char* token tokens[i];if (strlen(token) > 1 || isdigit(token[0])…

测评要求+基本措施+对应产品

基本要求项测评项基本措施对应产品 网络架构 网络架构 网络架构应保证网络各个部分的带宽满足业务高峰期需要&#xff1b;带宽管理流量控制系统 网络架构 网络架构 网络架构应避免将重要网络区域部署在边界处&#xff0c;重要网络区域与其他网络区域之间应采取可靠的技术隔离手…

LogicFlow 学习笔记——6. LogicFlow 基础 网格 Grid

网格 Grid 网格是渲染或移动节点时的基本单元。其主要功能是在节点移动过程中&#xff0c;确保每个节点的中心点都精准落在网格点上&#xff0c;这大大有利于节点之间的精确对齐。通常&#xff0c;网格的间距越大&#xff0c;流程图编辑时的对齐越为便捷&#xff1b;反之&…