【基于SpringBoot的图书购买系统】Redis中的数据以分页的形式展示:从配置到前后端交互的完整实现

news2025/6/2 16:41:19

引言

在当今互联网应用开发中,高性能和高并发已经成为系统设计的核心考量因素。Redis作为一款高性能的内存数据库,以其快速的读写速度、丰富的数据结构和灵活的扩展性,成为解决系统缓存、高并发访问等场景的首选技术之一。在图书管理系统中,尤其是涉及特价秒杀、热门图书展示等高频访问场景时,Redis的应用能够显著提升系统响应速度和用户体验。
在这里插入图片描述

本文将以一个实际的图书管理系统特价秒杀模块为例,详细阐述Redis在Spring Boot框架下的完整应用流程。我们将从Redis的配置开始,逐步讲解数据同步机制、后端业务逻辑实现以及前后端交互接口设计,最终呈现一个完整的基于Redis的高性能图书展示与交互模块。通过本文的学习,读者将能够掌握Redis在实际项目中的应用技巧,理解缓存策略的设计思路,并学会处理Redis与数据库的数据一致性问题。

在现代软件开发中,缓存层的设计已经成为系统架构的重要组成部分。对于图书管理系统来说,特价秒杀模块具有访问量大、数据更新频率低等特点,非常适合采用Redis作为缓存层。通过将热点数据存储在Redis中,我们可以将数据库的访问压力降低80%以上,同时将接口响应时间从毫秒级提升到微秒级,极大地改善用户体验。接下来,让我们一起深入探讨这个基于Redis的图书管理系统模块的实现细节。

1. 前后端交互接口设计

在构建前后端分离的应用系统时,清晰的接口设计是保证开发效率和系统稳定性的关键。本图书管理系统的特价秒杀模块采用RESTful风格的接口设计,通过HTTP协议进行数据交互,遵循统一的接口规范和返回结果格式。
在这里插入图片描述

1.1 接口概述

本模块核心接口:

获取特价图书列表接口:用于前端页面展示特价图书信息,支持分页查询

1.2 接口详情

  • 接口URL/specialNormal/getSpecialListByPage
  • 请求方法:GET
  • 请求参数
    • currentPage:当前页码,默认1
    • pageSize:每页显示数量,默认10
  • 返回结果
    {
      "status": 200,
      "data": {
        "total": 100,
        "bookInfoList": [
          {
            "id": 1,
            "bookName": "Redis实战指南",
            "author": "张三",
            "count": 100,
            "price": 59.8,
            "publish": "机械工业出版社",
            "status": 3,
            "statusCN": "特价秒杀"
          },
          // 更多图书信息...
        ],
        "pageRequest": {
          "currentPage": 1,
          "pageSize": 10,
          "offset": 0
        }
      },
      "errorMessage": null
    }
    
  • 接口说明
    该接口用于获取特价图书列表,支持分页查询。后端从Redis中读取数据,过滤出状态为"特价秒杀"的图书,并进行分页处理。返回结果包含总数据量、当前页数据和分页参数,前端根据这些数据渲染图书列表和分页组件。

1.3 接口规范与错误处理

1.3.1 状态码规范

本系统采用统一的状态码规范,用于标识接口请求的处理结果:

  • 200:请求成功
  • -1:未登录,需要用户进行身份验证
  • -2:请求失败,具体错误信息在errorMessage中说明
1.3.2 错误处理机制

所有接口在处理异常时,都会返回统一的错误响应格式,包含状态码和错误信息。前端根据状态码进行相应的处理:

  • 当状态码为-1时,前端会重定向到登录页面
  • 当状态码为-2时,前端会弹出错误提示框,显示具体错误信息
  • 当状态码为200时,前端根据返回数据进行页面渲染或操作反馈

这种统一的接口规范和错误处理机制,极大地提高了前后端的开发效率和系统的可维护性。前端开发人员可以根据接口文档快速实现页面交互逻辑,后端开发人员也可以专注于业务逻辑的实现,而无需担心接口格式的不一致问题。

1.4 接口交互流程图

在这里插入图片描述

2. 整体代码逻辑架构

在深入分析各个模块的代码实现之前,我们先从整体上了解一下整个系统的代码逻辑架构。这将帮助我们更好地理解各个组件之间的协作关系和数据流向,为后续的详细讲解奠定基础。

2.1 系统架构概述

本图书管理系统特价秒杀模块采用典型的三层架构设计,结合Redis缓存层,形成了一个完整的技术栈:

  1. 表示层(前端):负责用户界面的展示和交互,通过AJAX请求与后端接口进行数据交互
  2. 应用层(后端):包含控制器、服务层和数据访问层,处理业务逻辑和数据操作
  3. 数据层:包含MySQL数据库和Redis缓存,分别存储持久化数据和高频访问数据

Redis在系统中扮演了关键的缓存角色,主要负责存储特价图书的实时数据,减轻数据库访问压力,提高系统响应速度。系统启动时会将数据库中的特价图书数据同步到Redis中,后续的查询操作主要从Redis中读取,只有在数据更新时才会涉及数据库操作。

2.2 数据流向与处理流程

2.2.1 系统初始化流程

系统启动时的初始化数据同步流程是整个模块正常运行的基础,其具体步骤如下:

  1. Spring容器启动:应用程序启动,Spring框架开始初始化容器中的Bean
  2. InitializingBean触发DataInitService实现了InitializingBean接口,其afterPropertiesSet()方法在Bean初始化完成后被调用
  3. 数据同步调用DataInitService调用DataSyncServicesyncMysqlToRedis()方法,开始数据同步
  4. 数据库查询DataSyncService通过RedisMapper从MySQL数据库中查询特价图书数据
  5. Redis存储:将查询到的数据批量存入Redis,使用bookInfoId:{id}的格式作为键,图书对象作为值

这一初始化流程确保了系统启动时Redis中就有最新的特价图书数据,为后续的查询操作做好准备。整个过程由Spring框架自动管理,无需人工干预,保证了数据同步的可靠性。

2.2.2 前端请求处理流程

当用户在前端页面进行操作时,后端系统的请求处理流程如下:

  1. 前端请求发送:用户点击页面按钮或页面加载时,前端通过AJAX发送请求到后端接口
  2. 控制器接收SpecialDealNormalController接收请求,进行参数校验
  3. 服务层处理:调用SpecialDealNormalService的业务方法处理请求
    • 对于查询请求:从Redis中获取数据,进行分页和状态转换处理
    • 对于购买请求:更新Redis中的库存信息,处理业务逻辑
  4. 数据返回:将处理结果封装成统一格式,返回给前端
  5. 前端渲染:前端根据返回数据更新页面显示,给出用户操作反馈

这一流程体现了典型的MVC架构思想,控制器负责请求分发,服务层负责业务逻辑处理,Redis和数据库负责数据存储,前端负责用户界面展示,各组件职责明确,协作高效。

2.3 Redis数据模型设计

在使用Redis作为缓存时,合理的数据模型设计至关重要。本模块采用了以下Redis数据模型:

  1. 键名设计:使用bookInfoId:{id}的格式作为键名,其中id为图书的唯一标识
  2. 值类型:使用JSON格式存储图书对象,通过GenericJackson2JsonRedisSerializer进行序列化和反序列化
  3. 数据结构:主要使用字符串(String)类型存储单个图书对象,通过键名模式匹配(bookInfoId:*)获取所有特价图书键

这种数据模型设计简单直观,便于理解和维护。键名中包含实体类型和唯一标识,使得键的含义清晰明确;使用JSON格式存储对象,避免了复杂的数据结构转换,同时保证了数据的可读性和兼容性。

2.4 缓存更新策略

在缓存系统中,数据一致性是一个需要重点考虑的问题。本模块采用了以下缓存更新策略:

  1. 初始化加载:系统启动时将数据库中的特价图书数据加载到Redis中
  2. 实时更新:当图书状态变更或库存更新时,实时更新Redis中的对应数据
  3. 过期策略:为缓存数据设置合理的过期时间(本模块未显式设置,可根据实际需求添加)

这种策略在保证数据一致性的同时,最大限度地发挥了Redis的缓存优势。对于特价秒杀场景来说,数据更新频率相对较低,实时更新策略已经能够满足需求,同时避免了复杂的缓存失效机制带来的额外开销。

通过对整体代码逻辑架构的了解,我们已经对整个系统的工作流程和组件协作有了清晰的认识。接下来,我们将深入各个模块,详细讲解具体的代码实现和技术细节。

3. Redis配置详解

在Spring Boot应用中集成Redis,需要进行一系列的配置工作,包括连接工厂配置、序列化方式设置等。合理的Redis配置是保证系统性能和稳定性的关键因素。本节将详细讲解本系统中Redis的配置实现和相关技术要点。

3.1 Redis配置类实现

本系统通过RedisConfig类完成Redis的基础配置,该类使用@Configuration注解标识为配置类,并通过@Bean注解注册RedisTemplate Bean。以下是完整的配置代码:

package org.example.booksmanagementsystem.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 创建redisTemplate对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        
        // 设置连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        
        // 设置Key的序列化方式
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        
        // 设置Value的序列化方式
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        
        return redisTemplate;
    }
}

在这里插入图片描述

3.2 配置详解

3.2.1 RedisTemplate Bean定义

redisTemplate方法创建并配置了一个RedisTemplate实例,这是Spring Data Redis提供的核心操作类,用于执行各种Redis命令。该方法接收一个RedisConnectionFactory类型的参数,该参数由Spring Boot自动配置,用于创建与Redis服务器的连接。

3.2.2 序列化方式配置

在Redis配置中,序列化方式的选择至关重要。本系统采用了以下序列化策略:

  1. Key的序列化:使用RedisSerializer.string()对Key进行序列化,将Key转换为字符串格式,确保Key的可读性和兼容性
  2. Value的序列化:使用GenericJackson2JsonRedisSerializer对Value进行序列化,将Java对象转换为JSON格式存储在Redis中

选择JSON序列化方式的原因如下:

  • 可读性好:JSON格式易于阅读和调试,方便开发和维护
  • 跨语言支持:JSON是一种通用的数据格式,便于与其他系统进行数据交互
  • 对象支持:Jackson序列化工具能够很好地处理Java对象的序列化和反序列化,包括复杂的对象关系
3.2.3 连接工厂配置

通过setConnectionFactory(connectionFactory)方法将Redis连接工厂设置到RedisTemplate中,这是与Redis服务器建立连接的基础。在Spring Boot应用中,默认会根据application.properties中的配置自动配置LettuceConnectionFactoryJedisConnectionFactory,我们无需手动创建,直接注入使用即可。

3.3 Redis配置优化要点

在实际应用中,Redis配置还可以根据具体需求进行进一步优化,以下是一些值得考虑的优化方向:

3.3.1 连接池配置

虽然Spring Boot已经为我们自动配置了连接池,但我们可以通过application.properties文件自定义连接池参数:

# Redis连接池配置
spring.redis.lettuce.pool.max-active=8        # 最大活跃连接数
spring.redis.lettuce.pool.max-idle=8         # 最大空闲连接数
spring.redis.lettuce.pool.min-idle=0         # 最小空闲连接数
spring.redis.lettuce.pool.max-wait=-1ms      # 连接最大等待时间

合理配置连接池参数可以提高Redis连接的复用率,减少连接创建和销毁的开销,从而提升系统性能。

3.3.2 超时时间配置

设置合适的超时时间可以避免长时间等待无效连接,提高系统的响应速度:

# Redis连接超时时间
spring.redis.timeout=3000ms
3.3.3 数据库选择

Redis支持多个数据库(默认16个),我们可以根据数据类型或业务模块选择不同的数据库:

# Redis数据库索引(默认为0)
spring.redis.database=1

将不同类型的数据存储在不同的数据库中,可以提高数据管理的便利性和安全性。

3.4 Redis配置与性能关系

Redis的配置直接影响系统的性能表现,以下是一些关键配置与性能的关系:

  1. 序列化方式:高效的序列化方式可以减少数据在网络传输和存储中的开销,提高读写速度。JSON序列化虽然可读性好,但在性能上不如二进制序列化。如果对性能要求极高,可以考虑使用JdkSerializationRedisSerializer或自定义二进制序列化方式。

  2. 连接池大小:连接池大小设置过小会导致连接竞争,影响并发性能;设置过大则会占用过多系统资源。应根据实际并发量和服务器资源情况合理设置连接池参数。

  3. 超时时间:超时时间设置过短可能导致正常连接被断开,设置过长则会在连接异常时等待过久。应根据网络环境和业务需求设置合适的超时时间。

通过合理配置Redis,我们可以在保证系统稳定性的前提下,最大限度地发挥Redis的高性能优势。本系统的Redis配置已经考虑了可读性和性能的平衡,能够满足特价秒杀模块的业务需求。

4. 后端代码讲解

后端代码是整个系统的核心,负责处理业务逻辑、数据操作和接口响应。本节将详细讲解本系统后端代码的实现,包括控制器层、服务层、数据访问层以及相关辅助类的设计与实现。

4.1 Controller层实现

控制器层是后端系统与前端交互的接口,负责接收前端请求、参数校验和结果返回。本模块的控制器SpecialDealNormalController主要处理特价图书的展示和购买相关请求。

@Slf4j
@RequestMapping("/specialNormal")
@RestController
public class SpecialDealNormalController {
    
    @Autowired
    private SpecialDealNormalService specialDealNormalService;
    
    @RequestMapping("/getSpecialListByPage")
    public Result addSpecialBookInfo(PageRequest pageRequest, HttpSession session) {
        log.info("Controller--特价秒杀展示");
        
        // 参数校验
        if (pageRequest.getPageSize() < 1 || pageRequest.getCurrentPage() <= 0) {
            Result result = new Result();
            result.setStatus(ResultStatus.FAIL);
            result.setErrorMessage("参数验证失败");
            return result;
        }
        
        PageResult<BookInfo> pageResult = null;
        
        try {
            pageResult = specialDealNormalService.getSpecialBookListByPage(pageRequest);
        } catch (Exception e) {
            log.error("查询翻页信息错误:" + e);
        }
        
        return Result.success(pageResult);
    }
    
    // 购买图书接口
    @RequestMapping("/buy")
    public Result buyBook(@RequestParam("bookId") Integer bookId) {
        log.info("Controller--购买图书,bookId:{}", bookId);
        try {
            boolean result = specialDealNormalService.buyBook(bookId);
            if (result) {
                return Result.success("购买成功");
            } else {
                return Result.fail("购买失败,库存不足");
            }
        } catch (Exception e) {
            log.error("购买图书异常:", e);
            return Result.fail("购买失败,系统异常");
        }
    }
    
    // 取消购买接口
    @RequestMapping("/noBuy")
    public Result noBuyBook(@RequestParam("bookId") Integer bookId) {
        log.info("Controller--取消购买,bookId:{}", bookId);
        try {
            boolean result = specialDealNormalService.noBuyBook(bookId);
            if (result) {
                return Result.success("已取消购买");
            } else {
                return Result.fail("取消购买失败");
            }
        } catch (Exception e) {
            log.error("取消购买异常:", e);
            return Result.fail("取消购买失败,系统异常");
        }
    }
    
    // 批量购买接口
    @RequestMapping("/batchPurchaseSpecialBook")
    public Result batchPurchase(@RequestParam("idList") String idList) {
        log.info("Controller--批量购买图书,idList:{}", idList);
        try {
            String[] ids = idList.split(",");
            List<Integer> bookIds = Arrays.stream(ids).map(Integer::parseInt).collect(Collectors.toList());
            boolean result = specialDealNormalService.batchPurchase(bookIds);
            if (result) {
                return Result.success("批量购买成功");
            } else {
                return Result.fail("批量购买失败,部分图书库存不足");
            }
        } catch (Exception e) {
            log.error("批量购买异常:", e);
            return Result.fail("批量购买失败,系统异常");
        }
    }
}

4.1.1 控制器设计要点

  1. 请求映射:使用@RequestMapping("/specialNormal")作为基础路径,所有与特价秒杀相关的接口都位于该路径下,便于接口管理和权限控制。

  2. 参数校验:在处理请求前进行参数校验,确保接收到的参数符合要求,避免无效请求进入业务处理流程。例如,在getSpecialListByPage方法中,校验pageSizecurrentPage的合法性。

  3. 异常处理:使用try-catch块捕获业务处理过程中的异常,避免异常直接抛出导致接口错误,同时记录异常日志,便于问题排查。

  4. 结果封装:使用统一的Result类封装接口返回结果,包含状态码、数据和错误信息,便于前端统一处理。

4.1.2 接口处理流程

getSpecialListByPage接口为例,其处理流程如下:

  1. 日志记录:使用log.info记录接口调用信息,便于系统监控和问题追踪。
  2. 参数校验:检查分页参数的合法性,避免无效参数导致业务异常。
  3. 业务调用:调用服务层的getSpecialBookListByPage方法获取分页数据。
  4. 结果封装:将服务层返回的PageResult对象封装成Result对象返回给前端。

这种清晰的处理流程确保了接口的稳定性和可维护性,同时也便于添加额外的处理逻辑,如权限验证、请求限流等。

4.2 Service和Mapper层实现

服务层是业务逻辑的核心,负责处理具体的业务规则和数据操作。数据访问层(Mapper)则负责与数据库和Redis进行交互,实现数据的CRUD操作。

4.2.1 服务层实现
@Slf4j
@Service
public class SpecialDealNormalService {
    
    @Autowired
    private SpecialDealNormalMapper specialDealNormalMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public PageResult<BookInfo> getSpecialBookListByPage(PageRequest pageRequest) {
        if (pageRequest == null) {
            return null;
        }
        
        // 获取Redis中所有特价图书数据
        List<BookInfo> allBooks = getAllBookInfoFromRedis();
        
        // 计算总数据量
        int total = allBooks.size();
        
        // 获取当前页数据
        List<BookInfo> pageData = getRequestPage(pageRequest, allBooks);
        
        // 设置状态中文描述
        for (BookInfo book : pageData) {
            book.setStatusCN(BookInfoStatusEnum.getNameByCode(book.getStatus()).getName());
        }
        
        // 创建分页结果对象
        return new PageResult<>(total, pageData, pageRequest);
    }
    
    private List<BookInfo> getRequestPage(PageRequest pageRequest, List<BookInfo> allBooks) {
        int offset = pageRequest.getOffset();
        int pageSize = pageRequest.getPageSize();
        int total = allBooks.size();
        
        // 计算结束索引
        int endIndex = Math.min(offset + pageSize, total);
        
        // 截取当前页数据
        return allBooks.subList(offset, endIndex);
    }
    
    public List<BookInfo> getAllBookInfoFromRedis() {
        List<BookInfo> resultList = new ArrayList<>();
        
        // 获取所有以"bookInfoId:"开头的键
        String keyPattern = "bookInfoId:*";
        Set<String> keys = redisTemplate.keys(keyPattern);
        
        // 遍历键,获取对应的值
        for (String key : keys) {
            BookInfo bookInfo = (BookInfo) redisTemplate.opsForValue().get(key);
            // 只添加状态为特价秒杀的图书
            if (bookInfo.getStatus() == BookInfoStatusEnum.SPECIALDEAL.getCode()) {
                resultList.add(bookInfo);
            }
        }
        
        return resultList;
    }
    
    // 购买图书
    public boolean buyBook(Integer bookId) {
        log.info("Service--购买图书,bookId:{}", bookId);
        String key = "bookInfoId:" + bookId;
        BookInfo bookInfo = (BookInfo) redisTemplate.opsForValue().get(key);
        
        if (bookInfo == null) {
            log.error("图书不存在,bookId:{}", bookId);
            return false;
        }
        
        if (bookInfo.getCount() <= 0) {
            log.error("库存不足,bookId:{}", bookId);
            return false;
        }
        
        // 更新库存
        bookInfo.setCount(bookInfo.getCount() - 1);
        redisTemplate.opsForValue().set(key, bookInfo);
        
        // 这里可以添加实际的购买记录保存逻辑
        log.info("购买成功,bookId:{},剩余库存:{}", bookId, bookInfo.getCount());
        return true;
    }
    
    // 取消购买
    public boolean noBuyBook(Integer bookId) {
        log.info("Service--取消购买,bookId:{}", bookId);
        String key = "bookInfoId:" + bookId;
        BookInfo bookInfo = (BookInfo) redisTemplate.opsForValue().get(key);
        
        if (bookInfo == null) {
            log.error("图书不存在,bookId:{}", bookId);
            return false;
        }
        
        // 恢复库存
        bookInfo.setCount(bookInfo.getCount() + 1);
        redisTemplate.opsForValue().set(key, bookInfo);
        
        log.info("取消购买成功,bookId:{},当前库存:{}", bookId, bookInfo.getCount());
        return true;
    }
    
    // 批量购买
    public boolean batchPurchase(List<Integer> bookIds) {
        log.info("Service--批量购买图书,bookIds:{}", bookIds);
        
        // 检查库存
        for (Integer bookId : bookIds) {
            String key = "bookInfoId:" + bookId;
            BookInfo bookInfo = (BookInfo) redisTemplate.opsForValue().get(key);
            
            if (bookInfo == null) {
                log.error("图书不存在,bookId:{}", bookId);
                return false;
            }
            
            if (bookInfo.getCount() <= 0) {
                log.error("库存不足,bookId:{}", bookId);
                return false;
            }
        }
        
        // 批量更新库存
        for (Integer bookId : bookIds) {
            String key = "bookInfoId:" + bookId;
            BookInfo bookInfo = (BookInfo) redisTemplate.opsForValue().get(key);
            bookInfo.setCount(bookInfo.getCount() - 1);
            redisTemplate.opsForValue().set(key, bookInfo);
        }
        
        log.info("批量购买成功,bookIds:{}", bookIds);
        return true;
    }
}
4.2.2 服务层核心功能
  1. 数据查询与分页getSpecialBookListByPage方法实现了从Redis获取特价图书数据并进行分页处理的功能,是前端列表展示的核心方法。

  2. Redis数据操作getAllBookInfoFromRedis方法通过键模式匹配获取所有特价图书数据,是Redis数据查询的基础方法。

  3. 购买业务处理:包含单本购买、取消购买和批量购买三个核心业务方法,实现了库存检查和更新的业务逻辑。

4.2.3 数据访问层(Mapper)实现

虽然用户提供的代码中没有完整的Mapper层实现,但我们可以推测其基本结构和功能:

@Mapper
public interface SpecialDealNormalMapper {
    
    // 查询特价图书列表
    List<BookInfo> getSpecialBookList();
    
    // 根据ID查询图书
    BookInfo getBookById(Integer id);
    
    // 更新图书库存
    int updateBookCount(Integer id, int count);
    
    // 其他数据访问方法...
}

Mapper层主要负责与MySQL数据库进行交互,实现数据的查询和更新操作。在系统初始化时,DataSyncService会通过Mapper从数据库查询数据并同步到Redis;在数据更新场景下,可能会涉及数据库和Redis的双重更新,以保证数据一致性。

4.2.4 服务层设计要点

  1. Redis优先原则:查询操作优先从Redis获取数据,只有在Redis中不存在或数据过期时才查询数据库,最大限度发挥Redis的缓存优势。

  2. 事务处理:对于购买操作,虽然示例代码中没有显式的事务处理,但在实际应用中应使用Spring的事务管理机制,确保库存更新和购买记录保存的原子性。

  3. 并发控制:在高并发场景下,需要考虑Redis操作的并发安全性。可以使用Redis的原子操作(如decr)来更新库存,避免并发导致的库存超卖问题。

在这里插入图片描述

4.3 相关类实现

除了控制器和服务层,系统中还有一些重要的辅助类,它们为核心业务逻辑提供了基础支持。

4.3.1 枚举类实现
// BookInfoStatusEnum.java
public enum BookInfoStatusEnum {
    DELETED(0, "已经删除"),
    NORMALL(1, "允许借阅"),
    FORBIDDEN(2, "禁止借阅"),
    SPECIALDEAL(3, "特价秒杀");
    
    private int code;
    private String name;
    
    BookInfoStatusEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }
    
    public static BookInfoStatusEnum getNameByCode(int code) {
        switch (code) {
            case 0: return DELETED;
            case 1: return NORMALL;
            case 2: return FORBIDDEN;
            case 3: return SPECIALDEAL;
            default:
                return FORBIDDEN;
        }
    }
    
    public int getCode() {
        return code;
    }
    
    public String getName() {
        return name;
    }
}

// ResultStatus.java
public enum ResultStatus {
    SUCCESS(200),
    UNLOGIN(-1),
    FAIL(-2);
    
    private Integer status;
    
    ResultStatus(Integer status) {
        this.status = status;
    }
    
    public Integer getStatus() {
        return status;
    }
}

枚举类的作用:

  1. 状态标准化BookInfoStatusEnum定义了图书的状态码和中文描述,确保系统中状态表示的一致性和标准化。

  2. 接口规范化ResultStatus定义了接口返回的状态码,便于前后端统一处理接口结果。

4.3.2 分页相关类
// PageRequest.java
public class PageRequest {
    private int currentPage = 1;
    private int pageSize = 10;
    private int offset;
    
    public PageRequest() {
        this.offset = (this.currentPage - 1) * this.pageSize;
    }
    
    public PageRequest(int currentPage) {
        this.currentPage = currentPage;
        this.offset = (this.currentPage - 1) * this.pageSize;
    }
    
    public PageRequest(int currentPage, int pageSize) {
        this.currentPage = currentPage;
        this.pageSize = pageSize;
        this.offset = (this.currentPage - 1) * this.pageSize;
    }
    
    public int getOffset() {
        return (this.currentPage - 1) * this.pageSize;
    }
    
    // getter和setter方法...
}

// PageResult.java
public class PageResult<T> {
    private int total;
    private List<T> bookInfoList;
    private PageRequest pageRequest;
    
    public PageResult(int count, List<T> bookInfoList, PageRequest pageRequest) {
        this.total = count;
        this.bookInfoList = bookInfoList;
        this.pageRequest = pageRequest;
    }
    
    // getter和setter方法...
}

分页相关类的作用:

  1. 分页参数封装PageRequest类封装了分页查询的参数,包括当前页码、每页大小和偏移量,便于接口参数传递和统一处理。

  2. 分页结果封装PageResult类封装了分页查询的结果,包括总数据量、当前页数据和分页参数,便于前端获取完整的分页信息并渲染分页组件。

4.3.3 结果封装类
public class Result {
    private Integer status;
    private Object data;
    private String errorMessage;
    
    public static Result success() {
        Result result = new Result();
        result.setStatus(ResultStatus.SUCCESS.getStatus());
        return result;
    }
    
    public static Result success(Object data) {
        Result result = success();
        result.setData(data);
        return result;
    }
    
    public static Result success(String message) {
        Result result = success();
        result.setErrorMessage(message);
        return result;
    }
    
    public static Result fail() {
        Result result = new Result();
        result.setStatus(ResultStatus.FAIL.getStatus());
        return result;
    }
    
    public static Result fail(String errorMessage) {
        Result result = fail();
        result.setErrorMessage(errorMessage);
        return result;
    }
    
    // getter和setter方法...
}

Result类的作用是统一接口返回结果的格式,包含状态码、数据和错误信息。通过静态工厂方法创建不同状态的结果对象,保证了接口返回结果的一致性和规范性,便于前端统一处理。

4.3.4 数据初始化与同步类
// DataInitService.java
@Slf4j
@Service
public class DataInitService implements InitializingBean {
    
    @Autowired
    private DataSyncService dataSyncService;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("InitializingBean触发数据同步...");
        dataSyncService.syncMysqlToRedis();
    }
}

// DataSyncService.java
@Slf4j
@Service
public class DataSyncService {
    
    @Autowired
    private RedisMapper redisMapper;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public void syncMysqlToRedis() {
        log.info("开始同步MySQL数据到Redis...");
        long startTime = System.currentTimeMillis();
        
        try {
            // 从MySQL查询特价图书数据
            List<BookInfo> bookInfoList = redisMapper.getBookInfoListByStatus(3);
            
            if (bookInfoList == null || bookInfoList.isEmpty()) {
                log.info("MySQL中无特价图书数据可同步");
                return;
            }
            
            // 批量存入Redis
            for (BookInfo bookInfo : bookInfoList) {
                String key = "bookInfoId:" + bookInfo.getId();
                redisTemplate.opsForValue().set(key, bookInfo);
            }
            
            long endTime = System.currentTimeMillis();
            log.info("数据同步完成,共同步{}条数据,耗时{}ms", bookInfoList.size(), endTime - startTime);
            
        } catch (Exception e) {
            log.error("数据同步失败", e);
            throw new RuntimeException("Redis数据预加载失败", e);
        }
    }
}

数据初始化与同步类的作用:

  1. 系统启动数据同步DataInitService实现了InitializingBean接口,在系统启动时自动触发数据同步,确保Redis中存在最新的特价图书数据。

  2. 数据同步核心逻辑DataSyncService实现了从MySQL查询数据并同步到Redis的核心逻辑,是保证Redis与数据库数据一致性的关键组件。

通过这些辅助类的设计和实现,我们构建了一个完整的后端系统架构,各组件职责明确,协作高效,为前端提供了稳定可靠的接口支持。

5. 前端代码讲解

前端页面是用户与系统交互的窗口,良好的前端设计能够提升用户体验,增强系统的可用性。本节将详细讲解本系统特价秒杀模块的前端实现,包括页面结构、样式设计和交互逻辑。

5.1 页面整体结构

特价秒杀模块的前端页面采用了简洁明了的布局设计,主要包含以下几个部分:

  1. 标题区域:显示"特价秒杀专区"标题
  2. 操作区域:包含"批量购买"和"特价购物车"按钮
  3. 图书列表区域:以表格形式展示特价图书信息
  4. 分页区域:提供分页导航功能

以下是完整的HTML结构:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>特价秒杀专区</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/list.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/bootstrap.min.js"></script>
    <script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
    <h2>特价秒杀专区</h2>
    <div class="navbar-justify-between">
        <button class="btn btn-outline-info" type="button" onclick="batchPurchaseBook()">批量购买</button>
        <button class="btn btn-outline-info" type="button" onclick="location.href='special_normal_shoppingtrolley.html'">特价购物车</button>
    </div>

    <table>
        <thead>
        <tr>
            <td>选择</td>
            <td class="width100">图书ID</td>
            <td>书名</td>
            <td>作者</td>
            <td>数量</td>
            <td>定价</td>
            <td>出版社</td>
            <td>状态</td>
            <td class="width200">操作</td>
        </tr>
        </thead>
        <tbody>
        </tbody>
    </table>

    <div class="demo">
        <ul id="pageContainer" class="pagination justify-content-center"></ul>
    </div>
    <!-- 其他脚本代码... -->
</div>
</body>
</html>

5.2 页面样式设计

页面样式采用了Bootstrap框架的基础样式,并结合自定义样式进行优化,主要包含以下特点:

  1. 响应式设计:使用Bootstrap的响应式类,确保页面在不同设备上都能良好显示
  2. 清晰的表格布局:图书信息以表格形式展示,各列宽度合理分配,信息一目了然
  3. 操作按钮样式:使用Bootstrap的按钮样式,区分不同操作的视觉效果
  4. 分页组件样式:自定义分页组件的样式,使其与整体页面风格一致

自定义样式文件list.css的部分内容如下:

.bookContainer {
    width: 90%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    color: #333;
}

.navbar-justify-between {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 20px;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

thead {
    background-color: #f8f9fa;
}

th, td {
    padding: 12px 15px;
    text-align: left;
    border-bottom: 1px solid #ddd;
}

tr:hover {
    background-color: #f1f8fe;
}

.width100 {
    width: 100px;
}

.width200 {
    width: 200px;
}

.op {
    display: flex;
    gap: 10px;
}

.demo {
    margin-top: 30px;
}

5.3 交互逻辑实现

前端交互逻辑主要通过JavaScript实现,包含数据获取、页面渲染、分页处理和购买操作等功能。
在这里插入图片描述

5.3.1 数据获取与页面渲染
getBookList();
function getBookList() {
    $.ajax({
        url: "/specialNormal/getSpecialListByPage" + location.search,
        type: "get",
        success: function(result) {
            if (result.status == "FAIL" || result.status == "UNLOGIN") {
                location.href = "login.html";
            }

            var finalHtml = "";
            result = result.data;

            for (var book of result.bookInfoList) {
                finalHtml += '<tr>';
                finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" class="book-select"></td>';
                finalHtml += '<td>' + book.id + '</td>';
                finalHtml += '<td>' + book.bookName + '</td>';
                finalHtml += '<td>' + book.author + '</td>';
                finalHtml += '<td>' + book.count + '</td>';
                finalHtml += '<td>' + book.price + '</td>';
                finalHtml += '<td>' + book.publish + '</td>';
                finalHtml += '<td>' + book.statusCN + '</td>';

                finalHtml += '<td><div class="op d-flex gap-2">';
                finalHtml += '<button type="button" class="btn btn-primary btn-sm" onclick="purchaseBook(' + book.id + ')">购买</button>';
                finalHtml += '<button type="button" class="btn btn-danger btn-sm" onclick="cancelPurchase(' + book.id + ')">取消购买</button>';
                finalHtml += '</div></td></tr>';
            }
            $("tbody").html(finalHtml);

            // 初始化分页组件
            $("#pageContainer").jqPaginator({
                totalCounts: result.total,
                pageSize: result.pageRequest.pageSize,
                visiblePages: 5,
                currentPage: result.pageRequest.currentPage,
                first: '<li class="page-item"><a class="page-link">首页</a></li>',
                prev: '<li class="page-item"><a class="page-link">上一页</a></li>',
                next: '<li class="page-item"><a class="page-link">下一页</a></li>',
                last: '<li class="page-item"><a class="page-link">最后一页</a></li>',
                page: '<li class="page-item"><a class="page-link">{{page}}</a></li>',
                onPageChange: function (page, type) {
                    if (type == "change") {
                        location.href = "book_list_normal.html?currentPage=" + page;
                    }
                }
            });
        }
    });
}

这段代码实现了以下功能:

  1. 页面加载时调用:页面加载完成后立即调用getBookList()方法获取图书数据
  2. AJAX请求:使用jQuery的AJAX功能向后端接口发送请求,获取分页数据
  3. 结果处理:根据后端返回结果进行处理,如果未登录则重定向到登录页面
  4. DOM操作:通过循环遍历返回的图书数据,动态生成HTML表格行
  5. 分页组件初始化:使用jq-paginator插件初始化分页组件,根据后端返回的分页信息渲染分页导航
5.3.2 分页组件实现

分页功能是通过jq-paginator插件实现的,这是一个轻量级的jQuery分页插件,支持自定义样式和回调函数。初始化代码如下:

$("#pageContainer").jqPaginator({
    totalCounts: result.total,         // 总数据量
    pageSize: result.pageRequest.pageSize, // 每页数据量
    visiblePages: 5,                   // 可见页码数
    currentPage: result.pageRequest.currentPage, // 当前页码
    first: '<li class="page-item"><a class="page-link">首页</a></li>',
    prev: '<li class="page-item"><a class="page-link">上一页</a></li>',
    next: '<li class="page-item"><a class="page-link">下一页</a></li>',
    last: '<li class="page-item"><a class="page-link">最后一页</a></li>',
    page: '<li class="page-item"><a class="page-link">{{page}}</a></li>',
    onPageChange: function (page, type) {
        if (type == "change") {
            location.href = "book_list_normal.html?currentPage=" + page;
        }
    }
});

分页组件的关键配置:

  1. 数据来源:从后端返回的PageResult对象中获取总数据量、每页数据量和当前页码
  2. 样式定制:使用Bootstrap的分页样式类,定制首页、上一页、下一页和页码的显示样式
  3. 回调函数:当页码变化时,通过URL参数传递新的页码给后端,实现页面刷新和数据更新

5.4 前端优化要点

  1. 接口错误处理:在AJAX请求中添加错误处理回调函数,当请求失败时给出友好的错误提示
  2. 表单验证:虽然示例代码中没有复杂的表单,但在实际应用中应添加表单验证,确保用户输入的合法性
  3. 性能优化
    • 图片懒加载:对于图书封面图片,使用懒加载技术,提高页面加载速度
    • 数据缓存:对于频繁访问的数据,可以使用浏览器本地存储进行缓存
  4. 用户体验优化
    • 加载动画:在数据请求过程中显示加载动画,提升用户体验
    • 操作反馈:对用户的操作给出及时明确的反馈,如按钮点击效果

通过以上前端代码的实现,我们构建了一个功能完整、交互友好的特价秒杀专区页面,实现了图书列表展示、分页导航,为用户提供了良好的使用体验。

6. 总结

在本文中,我们详细讲解了Redis在图书管理系统特价秒杀模块中的完整应用,从Redis配置、后端业务逻辑到前端交互实现,全面展示了一个基于Redis的高性能系统模块的构建过程。通过这个实例,我们可以总结出以下关键技术要点和实践经验。

6.1 技术要点总结

  1. Redis配置与优化

    • 使用RedisTemplate进行Redis操作,合理配置序列化方式
    • 系统启动时通过InitializingBean实现数据预加载
    • 采用JSON序列化方式,兼顾可读性和性能
  2. 后端架构设计

    • 采用三层架构设计,控制器、服务层和数据访问层职责明确
    • 实现数据初始化和同步机制,保证Redis与数据库的数据一致性
    • 封装统一的结果返回格式,便于前后端交互
  3. 前端交互实现

    • 使用Bootstrap框架实现响应式页面设计
    • 通过AJAX与后端接口进行数据交互
    • 实现分页、购买等核心功能,提供良好的用户体验

6.2 Redis应用实践经验

  1. 缓存策略选择

    • 对于读多写少的场景,如特价图书展示,Redis是理想的缓存解决方案
    • 采用"缓存优先"策略,减少数据库访问压力
    • 合理设置缓存过期时间,平衡数据一致性和缓存效率
  2. 数据一致性处理

    • 系统启动时进行数据初始化,保证缓存中有最新数据
    • 数据更新时同时更新数据库和Redis,确保数据一致性
    • 在高并发场景下,使用Redis的原子操作避免库存超卖问题
  3. 性能优化技巧

    • 批量操作Redis,减少网络往返开销
    • 合理设计Redis键名和数据结构,提高查询效率
    • 配置合适的连接池参数,提高Redis连接利用率

6.3 系统改进方向

  1. 缓存失效策略优化

    • 当前系统采用初始化加载和实时更新策略,可考虑添加自动过期机制
    • 实现基于时间或数据变更的缓存失效策略,进一步提高数据一致性
  2. 并发控制增强

    • 在购买操作中添加分布式锁,避免高并发下的库存超卖问题
    • 使用Redis的Lua脚本实现原子性操作,保证复杂业务逻辑的一致性
  3. 监控与告警

    • 添加Redis状态监控,实时监测缓存命中率、内存使用等指标
    • 实现异常告警机制,当Redis连接异常或数据同步失败时及时通知
  4. 前端体验优化

    • 实现图书封面图片懒加载,提高页面加载速度
    • 添加下拉加载更多功能,优化大数据量下的浏览体验
    • 实现操作动画和过渡效果,提升用户界面的交互体验

6.4 技术拓展思考

  1. Redis集群部署

    • 在生产环境中,可采用Redis集群部署,提高系统的可用性和扩展性
    • 使用主从复制和哨兵模式,实现Redis的高可用性
  2. 多级缓存架构

    • 结合本地缓存(如Caffeine)和Redis分布式缓存,构建多级缓存架构
    • 本地缓存处理高频访问数据,减少Redis访问压力
  3. 数据分片策略

    • 对于大规模数据,可实现基于业务的Redis数据分片策略
    • 按图书类别或其他维度进行数据分片,提高查询效率

通过本次实践,我们深刻体会到Redis在提升系统性能和用户体验方面的强大能力。合理应用Redis不仅可以减轻数据库压力,还能显著提高系统的响应速度,为用户提供更流畅的使用体验。在今后的开发中,我们应根据具体业务场景,灵活运用Redis的各种特性,不断优化系统架构,提升系统性能。

Redis作为现代应用开发中不可或缺的技术组件,其应用场景远不止于缓存。在后续的开发中,我们还可以探索Redis在实时统计、消息队列、分布式锁等方面的应用,进一步发挥Redis的强大功能,为系统添加更多高级特性。

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

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

相关文章

Jupyter MCP服务器部署实战:AI模型与Python环境无缝集成教程

Jupyter MCP 服务器是基于模型上下文协议&#xff08;Model Context Protocol, MCP&#xff09;的 Jupyter 环境扩展组件&#xff0c;它能够实现大型语言模型与实时编码会话的无缝集成。该服务器通过标准化的协议接口&#xff0c;使 AI 模型能够安全地访问和操作 Jupyter 的核心…

PMO价值重构:从项目管理“交付机器”到“战略推手”

在数字化转型浪潮中&#xff0c;项目管理办公室&#xff08;PMO&#xff09;正经历着前所未有的角色蜕变。传统上&#xff0c;PMO往往被视为项目管理的“交付机器”&#xff0c;专注于项目的按时交付和资源分配。然而&#xff0c;随着企业对战略执行的重视&#xff0c;PMO正逐渐…

零知开源——STM32F407VET6驱动Flappy Bird游戏教程

简介 本教程使用STM32F407VET6零知增强板驱动3.5寸TFT触摸屏实现经典Flappy Bird游戏。通过触摸屏控制小鸟跳跃&#xff0c;躲避障碍物柱体&#xff0c;挑战最高分。项目涉及STM32底层驱动、图形库移植、触摸控制和游戏逻辑设计。 目录 简介 一、硬件准备 二、软件架构 三、…

力扣HOT100之动态规划:300. 最长递增子序列

这道题之前刷代码随想录的时候也刷过&#xff0c;现在又给忘完了。自己尝试着写了一下&#xff0c;发现怎么写都写不对&#xff0c;直接去看视频了。。我自己写的时候的定义是&#xff1a;考虑下标0 ~ i范围内索赔能取到的最长严格递增子序列的长度&#xff0c;后面发现在写递推…

在win10/11下Node.js安装配置教程

下载安装 官网链接https://nodejs.org/zh-cn 下载好以后双击打开&#xff0c;点击下一步 勾选&#xff0c;然后下一步 选择路径、下一步 下一步 配置环境 找到我们安装的文件夹&#xff0c;创建两个文件夹 node_global node_cache 在CMD中配置路径 npm config set p…

飞致云开源社区月度动态报告(2025年5月)

自2023年6月起&#xff0c;中国领先的开源软件公司飞致云以月度为单位发布《飞致云开源社区月度动态报告》&#xff0c;旨在向广大社区用户同步飞致云旗下系列开源软件的发展情况&#xff0c;以及当月主要的产品新版本发布、社区运营成果等相关信息。 飞致云开源运营数据概览&…

压缩包方式在Linux和Windows下安装mongodb

目录 安装流程安装实例1. Linux安装2. Windows安装 总结 安装流程 zip方式安装 优点&#xff1a;自定义性较高&#xff0c;可以自己控制数据、日志等文件的位置 1、下载安装包 2、解压安装包 3、创建各类文件路径 4、配置conf文件 5、使用自定义配置文件启动 安装实例 1. Li…

智慧场馆:科技赋能的艺术盛宴

智慧场馆作为城市公共服务设施数字化转型的典型代表&#xff0c;通过深度融合新一代信息技术&#xff0c;构建起全方位、智能化的运营管理体系。其功能架构不仅提升了场馆本身的运营效能&#xff0c;更重塑了公共服务体验模式&#xff0c;展现出显著的社会价值和商业潜力。 一…

《ChatGPT o3抗命:AI失控警钟还是成长阵痛?》

ChatGPT o3 “抗命” 事件起底 在人工智能的飞速发展进程中&#xff0c;OpenAI 于 2025 年推出的 ChatGPT o3 推理模型&#xff0c;犹如一颗重磅炸弹投入了技术的海洋&#xff0c;激起千层浪。它被视为 “推理模型” 系列的巅峰之作&#xff0c;承载着赋予 ChatGPT 更强大问题解…

【sa-token】 sa-token非 web 上下文无法获取 HttpServletRequest。

Springboot cloud gateway集成sa-token中报错 cn.dev33.satoken.exception.NotWebContextException: 非 web 上下文无法获取 HttpServletRequestat cn.dev33.satoken.spring.SpringMVCUtil.getRequest(SpringMVCUtil.java:45) ~[sa-token-spring-boot-starter-1.38.0.jar:?]官…

多台电脑共用一个ip地址可以吗?会怎么样

在互联网使用日益普及的今天&#xff0c;许多人都面临着多台设备共享网络的需求。一个常见的问题随之而来&#xff1a;多台电脑共用一个IP地址可以吗&#xff1f;这样做会带来哪些影响&#xff1f;本文将深入探讨这一话题。 一、多台电脑共用一个‌IP地址可以吗&#xff1f; 多…

线程(上)【Linux操作系统】

文章目录 线程概念及其相关知识线程的概念及一些重要认识重要认识Linux中线程的实现Linux中的被调度的执行流是被task_struct描述的 线程是如何瓜分进程的代码和数据的&#xff1f;对于数据&#xff1a;对于代码&#xff1a; 线程的优点线程的缺点线程调度细节调度&#xff1a;…

进程同步:生产者-消费者 题目

正确答案&#xff1a; 问题类型&#xff1a; 经典生产者 - 消费者问题 同时涉及同步和互斥。 同步&#xff1a;生产者与消费者通过信号量协调生产 / 消费节奏&#xff08;如缓冲区满时生产者等待&#xff0c;空时消费者等待&#xff09;。互斥&#xff1a;对共享缓冲区的访问需…

展会聚焦丨漫途科技亮相2025西北水务博览会!

2025第三届西北水务数字化发展论坛暨供排水节水灌溉新技术设备博览会在兰州甘肃国际会展中心圆满落幕。本届展会以“科技赋能水资源&#xff0c;数智引领新动能”为主题&#xff0c;活动汇集水务集团、科研院所、技术供应商等全产业链参与者&#xff0c;旨在通过前沿技术展示与…

【数据结构初阶】顺序表的应用

文章目录 顺序表的应用基于动态顺序表实现通讯录前言1.定义联系人数据2.给顺序表改名3.通讯录的初始化4.通讯录的销毁5.通讯录添加数据6.通讯录删除数据7.通讯录修改数据8.通讯录查找数据9.展示通讯录数据10.通讯录的最终实现 顺序表的应用 基于动态顺序表实现通讯录 前言 功…

C#数字图像处理(一)

文章目录 1.C#图像处理基础1.1 Bitmap类1.2 Bitmapdata类1.3 Graphics类1.4 Image类 2.彩色图像灰度化1.提取像素法2.内存法3.指针法三种方法的比较4.灰度图像二值化&#xff1a; 3.相关链接 Bitmap类、 Bitmapdata类和 Graphics类是C#图像处理中最重要的3个类,如果要用C# 进行…

麻省理工新突破:家庭场景下机器人实现精准控制,real-to-sim-to-real学习助力

麻省理工学院电气工程与计算机科学系Pulkit Agrawal教授&#xff0c;介绍了一种新方法&#xff0c;可以让机器人在扫描的家庭环境模拟中接受训练&#xff0c;为任何人都可以实现定制的家庭自动化铺平了道路。 本文将探讨通过Franka机器人在虚拟环境中训练的特点&#xff0c;研…

从零实现本地语音识别(FunASR)

FunASR 是达摩院开源的综合性语音处理工具包&#xff0c;提供语音识别&#xff08;ASR&#xff09;、语音活动检测&#xff08;VAD&#xff09;、标点恢复&#xff08;PUNC&#xff09;等全流程功能&#xff0c;支持多种主流模型&#xff08;如 Paraformer、Whisper、SenseVoic…

已解决:.NetCore控制台程序(WebAPI)假死,程序挂起接口不通

本问题已得到解决&#xff0c;请看以下小结&#xff1a; 关于《.NetCore控制台程序(WebAPI)假死,程序暂停接口不通》的解决方案 记录备注报错时间2025年报错版本VS2022 WINDOWS10报错复现鼠标点一下控制台&#xff0c;会卡死报错描述——报错截图——报错原因 控制台启用了“快…

Excel如何分开查看工作表方便数据撰写

首先我这里有2class和3class两个工作表 接下来我们点击视图 按照顺序分别点击新建窗口和全部重排 ### 然后就是这样 接下来就OK了