【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践

引言

在当今互联网应用开发中,高并发场景下的系统性能优化始终是开发者面临的重要挑战。特别是在电商、零售等涉及大量商品库存管理的场景中,如何高效处理用户的购买请求、保证库存数据的一致性和系统的响应速度,成为了系统设计的关键。本项目基于Spring Boot框架,结合Redis缓存技术,实现了一个图书购买与库存管理系统,重点解决了高并发环境下的库存扣减、购买操作等核心业务场景。

Redis作为高性能的内存数据库,具备读写速度快、支持丰富数据结构、支持事务和持久化等特点,非常适合用于缓存热点数据、处理高并发的库存扣减等场景。本系统将图书库存数据存储在Redis中,利用其高速读写能力来处理用户的购买和取消购买操作,同时通过数据库持久化存储用户的购买记录,实现了缓存与数据库的双重数据保障。

本文将详细介绍该系统的设计与实现,包括前后端交互接口、整体代码逻辑、Redis配置、后端各层代码讲解以及前端页面实现,最后对整个系统进行总结与优化思考。

1. 前后端交互接口

1.1 接口设计概述

本系统实现了图书购买、批量购买、取消购买、批量取消购买以及分页查询等核心功能,前后端通过RESTful API进行交互。接口设计遵循简洁、清晰的原则,确保前端页面能够高效地与后端服务进行数据交换。

1.2 核心接口详情

1.2.1 购买图书接口
  • 接口URL/specialNormal/buy
  • 请求方法:POST
  • 请求参数
    • bookId:整数类型,要购买的图书ID
  • 请求头:需要包含用户会话信息(通过HttpSession获取用户信息)
  • 响应结果
    • 成功:{"status":"SUCCESS"}
    • 失败:{"status":"FAIL"}
  • 接口功能:处理单个图书的购买请求,包括库存检查、库存扣减、更新用户购买记录等逻辑
1.2.2 批量购买图书接口
  • 接口URL/specialNormal/batchPurchaseSpecialBook
  • 请求方法:POST
  • 请求参数
    • idList:整数列表,要购买的图书ID列表
  • 请求头:需要包含用户会话信息
  • 响应结果
    • 成功:{"status":"SUCCESS"}
    • 失败:{"status":"FAIL"}
  • 接口功能:处理多个图书的批量购买请求,通过循环调用单个购买接口实现
1.2.3 取消购买图书接口
  • 接口URL/specialNormal/noBuy
  • 请求方法:POST
  • 请求参数
    • bookId:整数类型,要取消购买的图书ID
  • 请求头:需要包含用户会话信息
  • 响应结果
    • 成功:{"status":"SUCCESS"}
    • 失败:{"status":"FAIL"}
  • 接口功能:处理单个图书的取消购买请求,包括恢复库存、更新用户购买记录等逻辑
1.2.4 批量取消购买图书接口
  • 接口URL/specialNormal/batchNoBuyBook
  • 请求方法:POST
  • 请求参数
    • idList:整数列表,要取消购买的图书ID列表
  • 请求头:需要包含用户会话信息
  • 响应结果
    • 成功:{"status":"SUCCESS"}
    • 失败:{"status":"FAIL"}
  • 接口功能:处理多个图书的批量取消购买请求,通过循环调用单个取消购买接口实现
1.2.5 分页查询特价购物车接口
  • 接口URL/specialNormal/getSpecialBookListOfNormalByPage
  • 请求方法:GET
  • 请求参数
    • currentPage:整数类型,当前页码
    • pageSize:整数类型,每页显示数量
  • 请求头:需要包含用户会话信息
  • 响应结果
    • 成功:{"status":"SUCCESS","data":{"total":总记录数,"bookInfoList":图书列表,"pageRequest":分页参数}}
    • 失败:{"status":"FAIL","errorMessage":"错误信息"}
  • 接口功能:根据分页参数查询用户特价购物车中的图书列表,支持分页展示

1.3 接口设计特点

  1. 无状态设计:接口设计遵循RESTful原则,尽量保持无状态,通过会话(HttpSession)获取用户信息
  2. 参数校验:每个接口都进行了基本的参数校验,确保输入数据的合法性
  3. 统一响应格式:使用统一的Result类作为响应结构,包含状态码、错误信息和数据内容
  4. 批量操作支持:提供批量购买和批量取消购买接口,提升用户操作效率
  5. 分页查询:实现分页查询接口,优化大数据量下的前端展示性能

这些接口设计不仅满足了基本的业务需求,还考虑了系统的可扩展性和性能优化,为后续功能迭代奠定了良好的基础。

2. 整体的代码逻辑

2.1 系统初始化流程

系统启动时,会自动触发数据初始化流程,将数据库中的图书数据同步到Redis缓存中,具体流程如下:

  1. InitializingBean触发DataInitService实现了InitializingBean接口,在容器初始化完成后,afterPropertiesSet()方法会被自动调用
  2. 数据同步服务调用:在afterPropertiesSet()方法中,调用DataSyncServicesyncMysqlToRedis()方法
  3. 数据库查询DataSyncService通过RedisMapper从数据库查询图书信息
  4. Redis批量存储:将查询到的图书信息批量存入Redis,使用bookInfoId:{id}的格式作为Key,Value使用JSON序列化的图书对象

【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第1张图片

2.2 购买操作整体逻辑

当用户执行购买操作时,系统会按以下流程处理:

  1. 参数校验:Controller层对输入的图书ID和用户会话进行校验
  2. Redis库存检查:Service层从Redis中查询对应图书的库存信息
  3. 库存扣减:如果库存充足,减少Redis中的库存数量
  4. 用户购买记录处理:检查用户是否已购买过该图书
    • 首次购买:向用户特价图书表插入记录
    • 重复购买:更新用户特价图书表中的购买数量
  5. 返回结果:根据操作结果返回成功或失败响应

【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第2张图片

2.3 取消购买操作整体逻辑

当用户执行取消购买操作时,系统会按以下流程处理:

  1. 参数校验:Controller层对输入的图书ID和用户会话进行校验
  2. 用户购买记录查询:Service层查询用户特价图书表中该图书的购买记录
  3. 购买数量更新:如果存在购买记录且数量大于0,减少用户特价图书表中的购买数量
  4. Redis库存恢复:增加Redis中对应图书的库存数量
  5. 返回结果:根据操作结果返回成功或失败响应
    【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第3张图片

2.4 批量操作逻辑

批量购买和批量取消购买操作的整体逻辑类似,主要区别在于:

  1. 循环处理:批量操作会遍历ID列表,逐个调用单个操作接口
  2. 异常处理:批量操作中如果某个单操作失败,会记录错误但继续处理后续操作
  3. 结果汇总:最终返回整体操作结果,成功或失败
    【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第4张图片

2.5 分页查询逻辑

分页查询用户特价购物车的流程如下:

  1. 参数校验:校验分页参数的合法性(当前页码、每页数量)
  2. 数据库查询:根据分页参数查询用户特价图书表中的记录
  3. 数据处理:将查询结果中的状态码转换为可读的状态名称(通过枚举类)
  4. 分页结果封装:将查询结果和分页信息封装为PageResult对象
  5. 返回结果:将分页结果返回给前端页面展示
    【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第5张图片

2.6 数据一致性保证

本系统在设计时考虑了数据一致性问题,主要通过以下方式保证:

  1. 缓存与数据库双重存储:图书库存数据存储在Redis中,用户购买记录存储在数据库中
  2. 操作原子性:购买和取消购买操作中,Redis操作和数据库操作保持一致
  3. 异常处理:在操作失败时,通过异常捕获和处理确保不会出现数据不一致的情况
  4. 初始化同步:系统启动时自动将数据库数据同步到Redis,保证初始数据一致

通过以上逻辑设计,系统实现了高效的图书购买与库存管理功能,同时保证了数据的一致性和系统的稳定性。
【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第6张图片

3. Redis配置

3.1 Redis配置类设计

在Spring Boot项目中,我们通过RedisConfig类来配置Redis相关参数,该类使用@Configuration注解标识为配置类,并通过@Bean注解注册RedisTemplate bean。以下是完整的配置代码:

@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;

    }
}

【基于Redis的秒杀图书专区】秒杀专区商品的购买和取消购买--从理论到实践_第7张图片

3.2 序列化方式选择

在Redis配置中,序列化方式的选择至关重要,它直接影响数据在Redis中的存储格式和读取效率。本系统采用了以下序列化策略:

  1. Key的序列化:使用RedisSerializer.string()对Key进行序列化,将Key转换为字符串格式,保证Key的可读性和唯一性
  2. Value的序列化:使用GenericJackson2JsonRedisSerializer对Value进行序列化,将Java对象转换为JSON格式存储在Redis中
    • 优点:JSON格式具有良好的跨平台性和可读性,便于调试和问题排查
    • 缺点:相比二进制序列化,JSON序列化会增加一定的存储空间,但在可读性和兼容性上更有优势

3.3 RedisTemplate配置解析

RedisTemplate是Spring Data Redis提供的核心操作类,通过配置RedisTemplate可以自定义Redis的连接方式、序列化方式等。以下是配置中的关键步骤:

  1. 创建RedisTemplate实例:通过构造函数创建RedisTemplate实例
  2. 设置连接工厂:通过setConnectionFactory()方法注入RedisConnectionFactory,该工厂负责创建Redis连接
  3. 配置序列化器
    • 设置Key和HashKey的序列化器为字符串序列化器
    • 设置Value和HashValue的序列化器为JSON序列化器
  4. 返回配置好的RedisTemplate:将配置完成的RedisTemplate作为bean注册到Spring容器中

3.4 Redis配置的最佳实践

本配置遵循了以下Redis使用的最佳实践:

  1. 分开配置Key和Value的序列化:Key使用字符串序列化保证可读性,Value使用JSON序列化保证对象完整性
  2. 使用连接工厂:通过连接工厂管理Redis连接,提高连接的复用性和管理效率
  3. 模板化操作:使用RedisTemplate封装Redis操作,避免直接操作Redis连接,提高代码的可维护性
  4. 通用Object类型:将Value的类型设为Object,提高模板的通用性,支持存储不同类型的对象

3.5 Redis配置的扩展点

如果需要对Redis配置进行扩展,可以考虑以下方向:

  1. 连接池配置:在RedisConnectionFactory中可以配置连接池的大小、超时时间等参数
  2. Redis集群配置:如果需要支持Redis集群,可以配置集群节点信息
  3. 序列化方式优化:如果对性能要求极高,可以考虑使用二进制序列化(如JdkSerializationRedisSerializer)
  4. 缓存过期策略:可以在存储数据时设置过期时间,避免无效数据长期占用内存

通过合理的Redis配置,本系统实现了高效的Redis操作,为高并发的库存管理和购买操作提供了性能保障。

4. 后端代码讲解

4.1 Controller层

Controller层作为前端请求的入口,负责接收请求、参数校验、调用Service层处理业务逻辑,并将结果返回给前端。本系统的Controller层由SpecialDealNormalController类实现,主要包含以下几个核心方法:

4.1.1 购买图书方法
@RequestMapping("/buy")
public Result buy(Integer bookId, HttpSession session) {
    Result result = new Result();
    if (bookId == null) {
        result.setStatus(ResultStatus.FAIL);
        return result;
    }

    // 从session中提取用户
    UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    if (userInfo == null) {
        log.info("提取session中的userInfo失败");
        result.setStatus(ResultStatus.FAIL);
        return result;
    }

    boolean b = specialDealNormalService.buy(bookId, userInfo.getId());
    if (b == true) {
        result.setStatus(ResultStatus.SUCCESS);
        return result;
    }

    result.setStatus(ResultStatus.FAIL);
    return result;
}

该方法的处理流程:

  1. 校验图书ID是否为空
  2. 从Session中获取用户信息,校验用户是否登录
  3. 调用Service层的buy方法处理购买逻辑
  4. 根据Service层的返回结果设置响应状态
4.1.2 批量购买图书方法
@RequestMapping("/batchPurchaseSpecialBook")
public Result batchPurchaseSpecialBook(@RequestParam("idList") List<Integer> idList, HttpSession session) {
    log.info("----------------------batchPurchaseSpecialBook");
    Result result = new Result();

    if (idList == null || idList.size() == 0) {
        result.setStatus(ResultStatus.FAIL);
        return result;
    }
    try {
        for (Integer id : idList) {
            buy(id, session);
        }
    } catch (Exception e) {
        log.info("批量购买错误:" + e.getMessage());
        result.setStatus(ResultStatus.FAIL);
        return result;
    }

    result.setStatus(ResultStatus.SUCCESS);
    return result;
}

批量购买方法的特点:

  1. 校验ID列表是否为空或为空列表
  2. 遍历ID列表,逐个调用单个购买方法
  3. 使用try-catch捕获可能的异常,保证部分失败不影响整体流程
  4. 最终返回批量操作的整体结果
4.1.3 取消购买图书方法
@RequestMapping("/noBuy")
public Result noBuy(Integer bookId, HttpSession session) {
    Result result = new Result();

    if (bookId == null) {
        result.setStatus(ResultStatus.FAIL);
        return result;
    }

    // 从session中提取用户
    UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    if (userInfo == null) {
        log.info("提取session中的userInfo失败");
        result.setStatus(ResultStatus.FAIL);
        return result;
    }

    boolean b = specialDealNormalService.noBuy(bookId, userInfo.getId());

    if (b == true) {
        result.setStatus(ResultStatus.SUCCESS);
        return result;
    } else {
        result.setStatus(ResultStatus.FAIL);
        return result;
    }
}

取消购买方法的逻辑与购买方法类似:

  1. 校验图书ID和用户信息
  2. 调用Service层的noBuy方法处理取消购买逻辑
  3. 根据操作结果返回响应状态
4.1.4 分页查询方法
@RequestMapping("/getSpecialBookListOfNormalByPage")
public Result getSpecialBookListOfNormalByPage(PageRequest pageRequest, HttpSession session) {
    log.info("Controller--特价购物车展示");
    Result result = new Result();

    // 参数校验
    if (pageRequest.getPageSize() < 1 || pageRequest.getCurrentPage() <= 0) {
        result = new Result();
        result.setStatus(ResultStatus.FAIL);
        result.setErrorMessage("参数验证失败");
        return result;
    }

    // 从session中提取用户
    UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    if (userInfo == null) {
        log.info("提取session中的userInfo失败");
        result.setStatus(ResultStatus.FAIL);
        return result;
    }

    PageResult<BookInfo> pageResult = null;

    try {
        pageResult = specialDealNormalService.getSpecialShoppingtrolleyByPage(pageRequest, userInfo.getId());
    } catch (Exception e) {
        log.error("查询翻页信息错误:" + e);
    }

    return Result.success(pageResult);
}

分页查询方法的特点:

  1. 校验分页参数的合法性(页码和每页数量)
  2. 获取用户信息,确保查询的是当前用户的购物车
  3. 调用Service层的分页查询方法
  4. 使用Result.success方法封装成功响应,包含分页结果数据
4.1.5 Controller层设计特点
  1. 统一响应格式:所有接口返回统一的Result对象,包含状态码和数据
  2. 参数校验:对所有输入参数进行基本校验,保证数据合法性
  3. 会话处理:通过HttpSession获取用户信息,确保操作的用户上下文
  4. 日志记录:关键操作添加日志记录,便于问题排查和系统监控
  5. 异常处理:对可能的异常进行捕获和处理,避免接口抛出未处理异常

Controller层作为MVC架构中的"控制器",起到了请求分发和结果封装的作用,将复杂的业务逻辑委托给Service层处理,保持了自身的简洁和清晰。

4.2 Service和Mapper

Service层是业务逻辑的核心实现层,负责处理具体的业务规则和数据操作;Mapper层则负责与数据库进行交互,执行SQL查询和更新操作。本系统的Service和Mapper层实现了图书购买、取消购买和分页查询等核心功能。

4.2.1 购买图书的Service实现
public boolean buy(Integer bookId, Integer userId) {
    /*
     * 1.检查redis有没有库存
     * 2.减redis中的库存
     * 3.看特价购物车有没买过
     * 4.添加到特价购物车
     */

    BookInfo bookInfo = null;

    // 找购买的book
    // 1:获取所有以"bookInfoId:"开头的键
    String keyPattern = "bookInfoId:*";
    Set<String> keys = redisTemplate.keys(keyPattern);

    // 2:遍历键,逐个获取对应的值
    for (String key : keys) {
        BookInfo value = (BookInfo) redisTemplate.opsForValue().get(key);

        // 找到购买的book
        if (bookId.equals(value.getId())) {

            // 检查有没有库存
            if (value.getCount().equals(0) || value.getStatus().equals(1)
                    || value.getStatus().equals(2) || value.getStatus().equals(0)) {
                log.info("库存不足或不可特价购买,特价商品购买失败");
                return false;
            }

            bookInfo = value;

            // 库存 -1
            bookInfo.setCount(bookInfo.getCount() - 1);

            // 更新到redis
            redisTemplate.opsForValue().set(key, bookInfo);

            break;
        }
    }

    // 用户是否购买过
    BookInfo bookInfoFromMysql = specialDealNormalMapper.queryBookInfoByNormalId(bookId, userId);
    if (bookInfoFromMysql == null) {
        // 第一次购买,直接插入
        specialDealNormalMapper.insertUserBookInfoTable(userId, bookInfo);
    }

    // 重复购买, count+1
    specialDealNormalMapper.addCountUserBookInfoTable(userId, bookInfo.getId());

    return true;
}

购买方法的核心逻辑:

  1. Redis库存查询:通过redisTemplate.keys("bookInfoId:*")获取所有图书Key,遍历查找目标图书
  2. 库存校验:检查图书库存是否大于0且状态为可购买(状态3)
  3. 库存扣减:减少Redis中图书的库存数量,并更新Redis
  4. 用户购买记录处理
    • 首次购买:向用户特价图书表插入记录
    • 重复购买:更新用户特价图书表中的购买数量
  5. 事务保证:虽然没有显式使用事务,但Redis操作和数据库操作在同一个方法中,保证了逻辑上的一致性
4.2.2 取消购买的Service实现
public boolean noBuy(Integer bookId, Integer userId) {
    /*
     * 1.查看用户的的特价表中是否有该book
     * 2.查看count是否大于0
     * 3.用户特价表count-1
     * 4.redis表count+1
     */

    // 查询用户特价表
    BookInfo bookInfo = specialDealNormalMapper.queryBookInfoByNormalId(bookId, userId);
    if (bookInfo == null || bookInfo.getCount().equals(0)) {
        return false;
    }

    // 用户表count-1
    specialDealNormalMapper.deleteSpecialBookCount(bookId, userId);

    // redis count+1
    // 1:获取所有以"bookInfoId:"开头的键
    String keyPattern = "bookInfoId:*";
    Set<String> keys = redisTemplate.keys(keyPattern);

    // 2:遍历键,逐个获取对应的值
    for (String key : keys) {
        BookInfo value = (BookInfo) redisTemplate.opsForValue().get(key);

        // 找到购买的book
        if (bookId.equals(value.getId())) {

            // 库存 +1
            value.setCount(value.getCount() + 1);

            // 更新到redis
            redisTemplate.opsForValue().set(key, value);

            break;
        }
    }

    return true;
}

取消购买方法的核心逻辑:

  1. 用户购买记录查询:查询用户特价图书表中是否存在该图书的购买记录
  2. 购买数量校验:检查购买数量是否大于0
  3. 购买记录更新:减少用户特价图书表中的购买数量
  4. Redis库存恢复:增加Redis中图书的库存数量
  5. 数据一致性:确保数据库和Redis中的数据同步更新
4.2.3 分页查询的Service实现
public PageResult<BookInfo> getSpecialShoppingtrolleyByPage(PageRequest pageRequest, Integer userId) {
    if (pageRequest == null) {
        return null;
    }

    // 获取表中数据的总数
    Integer count = specialDealNormalMapper.getAllSpecialShoppingtroll(userId);

    // 获取该页的数据
    List<BookInfo> bookInfoList = specialDealNormalMapper.getSpecialShoppingtrollbyPage(pageRequest, userId);

    // 根据status 修改 statusCN
    for (BookInfo it : bookInfoList) {
        // 使用枚举类设置图书的状态
        it.setStatusCN(BookInfoStatusEnum.getNameByCode(it.getStatus()).getName());
    }

    // 创建PageResult对象返回给Controller
    return new PageResult<BookInfo>(count, bookInfoList, pageRequest);
}

分页查询方法的核心逻辑:

  1. 参数校验:检查分页参数是否为空
  2. 总记录数查询:查询用户特价图书表中的总记录数
  3. 分页数据查询:根据分页参数查询当前页的图书数据
  4. 状态转换:将状态码转换为可读的状态名称(通过枚举类)
  5. 分页结果封装:将查询结果和分页信息封装为PageResult对象
4.2.4 Mapper层实现

Mapper层通过MyBatis实现数据库操作,以下是核心的Mapper方法:

@Mapper
public interface SpecialDealNormalMapper {

    // 从用户特价图书中查询图书信息
    @Select("select id,book_name,author,count,price,publish,status," +
            "create_time,update_time from #{userId}_special_book_info "+
            "where id = #{bookId} ")
    BookInfo queryBookInfoByNormalId(@Param("bookId") Integer bookId, @Param("userId") Integer userId);

    // 购买过图书,用户图书count+1
    @Update("update #{userId}_special_book_info set count=count+1 where id=#{bookId}")
    Integer addCountUserBookInfoTable(@Param("userId") Integer userId, @Param("bookId") Integer bookId);

    // 第一次购买图书,向用户图书表插入数据
    @Insert("Insert into #{userId}_special_book_info (id,book_name,author,count,price,publish,status) " +
            "values(#{bookInfo.id},#{bookInfo.bookName},#{bookInfo.author},1,#{bookInfo.price},#{bookInfo.publish},#{bookInfo.status})")
    Integer insertUserBookInfoTable(@Param("userId") Integer userId, @Param("bookInfo") BookInfo bookInfo);

    // 取消购买,用户特价表count-1
    @Update("update #{userId}_special_book_info set count=count-1 where id=#{bookId}")
    Integer deleteSpecialBookCount(@Param("bookId") Integer bookId, @Param("userId") Integer userId);

    // 查询用户特价表的书总数
    @Select("select count(*) from #{userId}_special_book_info ")
    Integer getAllSpecialShoppingtroll(Integer userId);

    // 分页查询 用户特价表的书
    @Select("select id,book_name,author,count,price,publish,status," +
            "create_time,update_time from #{userId}_special_book_info "+
            "where count !=0 and status !=0 order by id asc limit #{pageRequest.offset},#{pageRequest.pageSize} ")
    List<BookInfo> getSpecialShoppingtrollbyPage(@Param("pageRequest") PageRequest pageRequest, @Param("userId") Integer userId);
}

Mapper层的设计特点:

  1. 动态表名:使用#{userId}_special_book_info动态生成表名,实现用户专属的特价图书表
  2. 参数映射:使用@Param注解明确参数映射关系,提高SQL的可读性
  3. 分页查询:使用limit #{pageRequest.offset},#{pageRequest.pageSize}实现数据库层面的分页
  4. 操作原子性:每个Mapper方法对应一个原子性的数据库操作
  5. 结果映射:自动将SQL查询结果映射为BookInfo对象

4.3 相关类

4.3.1 枚举类设计
public enum BookInfoStatusEnum {
    DELETED(0, "已经删除"),
    NORMALL(1, "允许借阅"),
    FORBIDDEN(2, "禁止借阅"),
    SPECIALDEAL(3, "特价秒杀");

    public static BookInfoStatusEnum getNameByCode(int code) {
        switch (code) {
            case 0:
                return BookInfoStatusEnum.DELETED;
            case 1:
                return BookInfoStatusEnum.NORMALL;
            case 2:
                return BookInfoStatusEnum.FORBIDDEN;
            case 3:
                return BookInfoStatusEnum.SPECIALDEAL;
            default:
                return BookInfoStatusEnum.FORBIDDEN;
        }
    }

    private int code;
    private String name;

    BookInfoStatusEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

枚举类的作用:

  1. 状态标准化:定义图书的状态码和对应的状态名称,保证系统中状态表示的一致性
  2. 代码可读性:使用枚举常量代替魔法数字,提高代码的可读性和可维护性
  3. 状态转换:提供getNameByCode方法,方便状态码与状态名称的转换
  4. 类型安全:枚举类型保证了状态值的合法性,避免了非法状态值的出现
4.3.2 结果类设计
public class Result {
    private ResultStatus status;

    private String errorMessage;

    private Object data;

    public static Result success(Object data) {
        Result result = new Result();
        result.setStatus(ResultStatus.SUCCESS);

        // 赋值返回的数据
        result.data = data;

        return result;
    }

    // getter和setter方法
}
public enum ResultStatus {
    SUCCESS(200),
    UNLOGIN(-1),
    FAIL(-2);

    private Integer status;

    ResultStatus(Integer status) {
        this.status = status;
    }

    // getter和setter方法
}

结果类的设计特点:

  1. 统一响应格式:所有接口返回统一的Result对象,包含状态、错误信息和数据
  2. 状态枚举:使用ResultStatus枚举定义响应状态码,保证状态表示的一致性
  3. 便捷构造:提供success静态方法,方便构造成功响应
  4. 数据泛型:使用Object类型存储数据,提高结果类的通用性
  5. 错误处理:包含errorMessage字段,方便前端展示错误信息
4.3.3 分页相关类
public class PageRequest {
    private int currentPage;  // 当前页码
    private int pageSize;     // 每页显示记录数

    // 计算偏移量
    public int getOffset() {
        return (currentPage - 1) * pageSize;
    }

    // getter和setter方法
}
public class PageResult<T> {
    private int total;          // 总记录数
    private List<T> dataList;   // 数据列表
    private PageRequest pageRequest;  // 分页参数

    public PageResult(int total, List<T> dataList, PageRequest pageRequest) {
        this.total = total;
        this.dataList = dataList;
        this.pageRequest = pageRequest;
    }

    // getter和setter方法
}

分页相关类的作用:

  1. 分页参数封装:PageRequest类封装分页参数(当前页码、每页数量),并提供计算偏移量的方法
  2. 分页结果封装:PageResult类封装分页查询结果,包括总记录数、数据列表和分页参数
  3. 泛型支持:使用泛型提高分页结果的通用性,支持不同类型的数据分页
  4. 前端展示:封装的分页结果便于前端页面进行分页展示和导航
4.3.4 数据初始化类
@Slf4j
@Service
public class DataInitService implements InitializingBean {

    @Autowired
    private DataSyncService dataSyncService;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 应用初始化完成后执行
        log.info("InitializingBean触发数据同步...");
        dataSyncService.syncMysqlToRedis();
    }
}
@Slf4j
@Service
public class DataSyncService {

    @Autowired
    private RedisMapper redisMapper;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 同步MySQL数据到Redis的核心方法
    public void syncMysqlToRedis() {
        log.info("开始同步MySQL数据到Redis...");
        long startTime = System.currentTimeMillis();

        try {
            // 1.从MySQL查询数据 book_info表
            List<BookInfo> bookInfoList = redisMapper.getBookInfoList();

            if (bookInfoList == null) {
                log.info("MySQL中无数据可同步");
            }

            // 2. 批量存入Redis
            for (BookInfo bookInfo : bookInfoList) {
                String id = "bookInfoId:" + bookInfo.getId();
                redisTemplate.opsForValue().set(id, bookInfo);
            }

            long endTime = System.currentTimeMillis();
            log.info("数据同步完成,共同步{}条数据,耗时{}ms", bookInfoList.size(), endTime - startTime);

        } catch (Exception e) {
            log.error("数据同步失败", e);
            throw new RuntimeException("Redis数据预加载失败", e);
        }
    }
}

数据初始化类的作用:

  1. 系统启动同步:通过实现InitializingBean接口,在系统启动时自动触发数据同步
  2. 数据预加载:将数据库中的图书数据预加载到Redis,提高系统响应速度
  3. 批量操作:使用循环批量存入Redis,提高数据同步效率
  4. 日志记录:记录数据同步的开始、结束和耗时,便于监控和问题排查
  5. 异常处理:捕获数据同步过程中的异常,保证系统启动不受影响

5. 前端代码讲解

前端页面采用HTML结合JavaScript、jQuery和Bootstrap实现,主要包含两个页面:特价秒杀专区页面和特价购物车页面。这两个页面实现了类似的功能结构,包括图书列表展示、购买操作、取消购买操作和分页功能。

5.1 页面结构设计

5.1.1 特价秒杀专区页面
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">图书IDtd>
            <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.1.2 特价购物车页面
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="batchNoBuyBook()">批量取消购买button>
    div>

    <table>
        <thead>
        <tr>
            <td>选择td>
            <td class="width100">图书IDtd>
            <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 页面结构特点

  1. 统一布局:两个页面采用相同的布局结构,包括标题、操作按钮、表格和分页组件
  2. 响应式设计:使用Bootstrap框架实现响应式布局,适应不同屏幕尺寸
  3. 表格展示:使用HTML表格展示图书信息,包含选择框、图书ID、书名、作者等列
  4. 操作按钮:提供购买、取消购买、批量操作等功能按钮
  5. 分页组件:使用jq-paginator插件实现分页导航功能

5.3 JavaScript功能实现

5.3.1 图书列表获取与展示
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 += ''
                finalHtml += '+ book.id + '" class="book-select">'
                finalHtml += '' + book.id + ''
                finalHtml += '' + book.bookName + ''
                finalHtml += '' + book.author + ''
                finalHtml += '' + book.count + ''
                finalHtml += '' + book.price + ''
                finalHtml += '' + book.publish + ''
                finalHtml += '' + book.statusCN + ''

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

    列表获取与展示的核心逻辑:

    1. AJAX请求:使用jQuery的ajax方法请求后端分页查询接口
    2. 结果处理:根据响应结果判断是否需要重定向到登录页面
    3. HTML拼接:遍历图书列表,拼接HTML字符串生成表格行
    4. DOM操作:将生成的HTML插入到表格的tbody中
    5. 分页初始化:使用jq-paginator初始化分页组件,设置总记录数、每页数量等参数
    6. 分页事件:设置分页切换事件,实现页面跳转
    5.3.2 购买功能实现
    function purchaseBook(id) {
        if (confirm("确认购买?")) {
            $.ajax({
                type: "post",
                url: "/specialNormal/buy",
                data: {
                    bookId: id
                },
                success: function(result) {
                    if (result.status == "SUCCESS") {
                        alert("购买成功!");
                        location.reload();
                    } else {
                        alert("购买失败!");
                        location.reload();
                    }
                },
                error: function() {
                    alert("购买失败,请稍后重试");
                }
            });
        }
    }
    

    购买功能的实现逻辑:

    1. 用户确认:使用confirm对话框确认用户购买操作
    2. AJAX请求:发送POST请求到购买接口,传递图书ID
    3. 结果处理:根据响应状态显示购买成功或失败提示
    4. 页面刷新:操作完成后刷新页面,更新图书列表和库存显示
    5.3.3 批量购买功能实现
    function batchPurchaseBook() {
        var isDelete = confirm("确认批量购买?");
        if (isDelete) {
            // 获取复选框的id
            var ids = [];
            $("input:checkbox[name='selectBook']:checked").each(function () {
                ids.push($(this).val());
            });
    
            // 发送请求,批量删除
            $.ajax({
                type: "post",
                url: "/specialNormal/batchPurchaseSpecialBook?idList=" + ids,
                success: function(result) {
                    if (result.status == "SUCCESS") {
                        alert("批量购买成功!")
                        location.reload();
                    } else {
                        alert("购买失败");
                    }
                },
                error: function() {
                    console.log("请求失败");
                }
            });
        }
    }
    

    批量购买功能的实现逻辑:

    1. 用户确认:使用confirm对话框确认批量购买操作
    2. ID收集:获取所有选中的复选框的值,组成ID列表
    3. AJAX请求:发送POST请求到批量购买接口,传递ID列表
    4. 结果处理:根据响应状态显示批量购买成功或失败提示
    5. 页面刷新:操作完成后刷新页面,更新图书列表
    5.3.4 取消购买功能实现
    function cancelPurchase(id) {
        if (confirm("确认取消购买?")) {
            $.ajax({
                type: "post",
                url: "/specialNormal/noBuy",
                data: { bookId: id },
                success: function(result) {
                    if (result.status == "SUCCESS") {
                        alert("已取消购买");
                        location.reload();
                    } else {
                        alert("取消购买失败");
                        location.reload();
                    }
                },
                error: function() {
                    alert("取消失败,请稍后重试");
                }
            });
        }
    }
    

    取消购买功能的实现逻辑与购买功能类似:

    1. 用户确认:使用confirm对话框确认取消购买操作
    2. AJAX请求:发送POST请求到取消购买接口,传递图书ID
    3. 结果处理:根据响应状态显示取消成功或失败提示
    4. 页面刷新:操作完成后刷新页面,更新图书列表和库存显示
    5.3.5 批量取消购买功能实现
    function batchNoBuyBook() {
        var isDelete = confirm("确认批量取消购买?");
        if (isDelete) {
            // 获取复选框的id
            var ids = [];
            $("input:checkbox[name='selectBook']:checked").each(function () {
                ids.push($(this).val());
            });
    
            // 发送请求,批量删除
            $.ajax({
                type: "post",
                url: "/specialNormal/batchNoBuyBook?idList=" + ids,
                success: function(result) {
                    if (result.status == "SUCCESS") {
                        alert("批量取消购买成功!")
                        location.reload();
                    } else {
                        alert("取消购买失败");
                    }
                },
                error: function() {
                    console.log("请求失败");
                }
            });
        }
    }
    

    批量取消购买功能的实现逻辑与批量购买类似:

    1. 用户确认:使用confirm对话框确认批量取消购买操作
    2. ID收集:获取所有选中的复选框的值,组成ID列表
    3. AJAX请求:发送POST请求到批量取消购买接口,传递ID列表
    4. 结果处理:根据响应状态显示批量取消成功或失败提示
    5. 页面刷新:操作完成后刷新页面,更新图书列表

    5.4 前端设计特点

    1. 异步请求:所有数据获取和操作都通过AJAX异步请求实现,不阻塞页面渲染
    2. 用户交互:使用confirm对话框进行操作确认,避免误操作
    3. 动态渲染:通过JavaScript动态生成表格内容,实现数据的动态展示
    4. 分页组件:使用jq-paginator插件实现美观、易用的分页导航
    5. 响应式设计:使用Bootstrap的响应式类,确保在不同设备上都有良好的显示效果
    6. 错误处理:对AJAX请求的错误情况进行处理,给出友好的错误提示

    前端页面通过与后端接口的紧密配合,实现了图书列表展示、购买、取消购买和批量操作等功能,为用户提供了良好的操作界面和使用体验。

    6. 总结

    6.1 系统功能总结

    本系统基于Spring Boot和Redis技术,实现了一个完整的图书购买与库存管理功能,主要包括以下核心功能:

    1. Redis数据初始化:系统启动时自动将数据库中的图书数据同步到Redis缓存
    2. 图书购买:单个图书的购买操作,包括库存检查和扣减
    3. 批量购买:多个图书的批量购买操作,提高用户操作效率
    4. 取消购买:单个图书的取消购买操作,恢复库存
    5. 批量取消购买:多个图书的批量取消购买操作
    6. 分页查询:用户特价购物车的分页查询和展示

    6.2 技术应用总结

    1. Redis技术

      • 使用Redis作为缓存存储图书库存数据,提高高并发场景下的读写性能
      • 采用JSON序列化方式存储对象,保证数据的可读性和兼容性
      • 通过系统启动时的数据同步,保证Redis和数据库的数据一致性
    2. Spring Boot框架

      • 使用Spring Boot快速构建应用,简化配置
      • 利用Spring的依赖注入特性,实现组件之间的解耦
      • 通过InitializingBean接口实现系统启动时的数据初始化
    3. 前后端交互

      • 采用RESTful API设计接口,规范前后端交互
      • 使用AJAX实现异步数据获取和操作,提升用户体验
      • 统一响应格式,便于前端处理不同的响应状态

    6.3 系统优势分析

    1. 性能优化

      • 使用Redis缓存热点数据,减少数据库访问压力
      • 内存操作比磁盘操作更快,提高了库存扣减等操作的响应速度
      • 批量操作减少了前后端交互次数,提高了操作效率
    2. 用户体验

      • 提供批量购买和批量取消购买功能,提升用户操作效率
      • 分页查询优化了大数据量下的展示性能
      • 友好的操作确认和错误提示,减少用户误操作
    3. 可维护性

      • 清晰的代码分层(Controller、Service、Mapper),便于功能扩展和维护
      • 统一的响应格式和枚举定义,保证了代码的一致性
      • 详细的日志记录,便于问题排查和系统监控

    6.4 改进与优化方向

    1. Redis优化

      • 可以引入Redis集群,提高系统的可用性和扩展性
      • 为不同类型的数据设置合理的过期时间,释放内存空间
      • 考虑使用Lua脚本实现Redis操作的原子性,避免并发问题
    2. 并发处理

      • 在库存扣减操作中加入分布式锁,避免高并发下的超卖问题
      • 优化批量操作逻辑,减少循环次数,提高并发处理能力
    3. 数据一致性

      • 实现Redis与数据库的双向同步,保证数据的最终一致性
      • 加入数据校验和修复机制,定期检查Redis和数据库的数据一致性
    4. 前端优化

      • 实现前端缓存,减少重复请求
      • 优化页面加载性能,如图片懒加载、JS延迟加载等
      • 增加加载状态提示,提升用户体验
    5. 安全增强

      • 加入接口限流,防止恶意攻击和批量操作
      • 优化Session管理,提高用户认证的安全性
      • 对敏感数据进行加密处理

    6.5 总结与展望

    本系统通过结合Redis缓存技术和Spring Boot框架,实现了高效的图书购买与库存管理功能,在高并发场景下能够有效减轻数据库压力,提高系统响应速度。通过合理的代码设计和分层架构,保证了系统的可维护性和可扩展性。

    在未来的开发中,可以进一步引入分布式事务、消息队列等技术,解决高并发下的数据一致性问题;同时可以拓展更多的功能模块,如订单管理、支付功能、用户评价等,完善整个图书购买系统。

    通过本系统的设计与实现,我们深刻体会到Redis在高并发场景下的优势,以及前后端分离架构带来的开发效率提升。这种基于缓存的系统设计思想可以应用于更多的业务场景,如电商平台的商品库存管理、秒杀活动等,为用户提供更流畅、更高效的使用体验。

    你可能感兴趣的:(SpringBoot探秘,redis,数据库,缓存,spring,boot,后端,mybatis)