Redis从入门到精通(六)Redis实战(三)优惠券秒杀

news2024/11/20 2:37:44

↑↑↑下载测试项目原代码↑↑↑

文章目录

    • 前言
    • 4.3 优惠券秒杀
      • 4.3.1 数据表与实体类
      • 4.3.2 添加优惠券
        • 4.3.2.1 添加普通券代码
        • 4.3.2.2 添加秒杀券代码
      • 4.3.3 实现秒杀下单
        • 4.3.3.1 秒杀下单逻辑分析
        • 4.3.3.2 获取秒杀订单ID
        • 4.3.3.3 获取用户ID
        • 4.3.3.4 实现秒杀下单

前言

Redis实战系列文章:

Redis从入门到精通(四)Redis实战(一)短信登录
Redis从入门到精通(四)Redis实战(二)商户查询缓存

4.3 优惠券秒杀

每个商户都可以发布优惠券,分为普通券和秒杀券。普通券优惠力度较低,所以可以任意购买;而秒杀券优惠力度较大,需要限时限量抢购。例如:

4.3.1 数据表与实体类

在数据库中,tb_voucher表用于保存优惠券的信息,包括优惠券的基本信息、优惠金额、使用规则等:

tb_seckill_voucher表用于保存秒杀券的扩展信息,包括秒杀券的库存、开始抢购时间、结束抢购时间等:

tb_voucher_order表用于保存优惠券购买订单信息,包括购买用户的ID、优惠券的ID、购买时间等:

根据以上三个数据表在项目中创建三个对应的实体类,如下:

// tb_voucher、tb_seckill_voucher
// com.star.redis.dzdp.pojo.Voucher

/***
 * 优惠券
 * @author hsgx
 * @since 2024/4/5 10:26
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_voucher")
public class Voucher implements Serializable {

    private static final long serialVersionUID = 1L;

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

    /**
     * 商铺id
     */
    private Long shopId;

    /**
     * 代金券标题
     */
    private String title;

    /**
     * 副标题
     */
    private String subTitle;

    /**
     * 使用规则
     */
    private String rules;

    /**
     * 支付金额
     */
    private Long payValue;

    /**
     * 抵扣金额
     */
    private Long actualValue;

    /**
     * 优惠券类型
     */
    private Integer type;

    /**
     * 优惠券类型
     */
    private Integer status;

    /**
     * 库存
     */
    @TableField(exist = false)
    private Integer stock;

    /**
     * 生效时间
     */
    @TableField(exist = false)
    private Date beginTime;

    /**
     * 失效时间
     */
    @TableField(exist = false)
    private Date endTime;

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


    /**
     * 更新时间
     */
    private Date updateTime;
    
}
// tb_seckill_voucher
// com.star.redis.dzdp.pojo.SeckillVoucher

/***
 * 秒杀优惠券表
 * @author hsgx
 * @since 2024/4/5 10:26
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_seckill_voucher")
public class SeckillVoucher implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 关联的优惠券的id
     */
    @TableId(value = "voucher_id", type = IdType.INPUT)
    private Long voucherId;

    /**
     * 库存
     */
    private Integer stock;

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

    /**
     * 生效时间
     */
    private Date beginTime;

    /**
     * 失效时间
     */
    private Date endTime;

    /**
     * 更新时间
     */
    private Date updateTime;

}
// tb_voucher_order
// com.star.redis.dzdp.pojo.VoucherOrder

/***
 * 优惠券订单
 * @author hsgx
 * @since 2024/4/5 10:30
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_voucher_order")
public class VoucherOrder implements Serializable {

    private static final long serialVersionUID = 1L;

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

    /**
     * 下单的用户id
     */
    private Long userId;

    /**
     * 购买的代金券id
     */
    private Long voucherId;

    /**
     * 支付方式 1:余额支付;2:支付宝;3:微信
     */
    private Integer payType;

    /**
     * 订单状态,1:未支付;2:已支付;3:已核销;4:已取消;5:退款中;6:已退款
     */
    private Integer status;

    /**
     * 下单时间
     */
    private Date createTime;

    /**
     * 支付时间
     */
    private Date payTime;

    /**
     * 核销时间
     */
    private Date useTime;

    /**
     * 退款时间
     */
    private Date refundTime;

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

4.3.2 添加优惠券

4.3.2.1 添加普通券代码
  • 1)接口文档
项目说明
请求方法POST
请求路径/voucher/add
请求参数Voucher
返回值
  • 2)代码实现

首先为普通优惠券业务创建对应的VoucherController类-IVoucherService接口-VoucherServiceImpl实现类-VoucherMapper类;为秒杀优惠券业务创建对应的ISeckillVoucherService接口-SeckillVoucherServiceImpl实现类-SeckillVoucherMapper类。详见测试项目代码。

然后在VoucherController类中编写一个add()方法,用于新增普通优惠券:

// com.star.redis.dzdp.controller.VoucherController

@Resource
private IVoucherService voucherService;

/**
 * 添加普通优惠券
 * @author hsgx
 * @since 2024/4/5 10:43
 * @param voucher
 * @return com.star.redis.dzdp.pojo.BaseResult
 */
@PostMapping("/add")
public BaseResult add(@RequestBody Voucher voucher) {
    log.info("add {}", voucher.toString());
    voucherService.save(voucher);
    log.info("add success. id = {}", voucher.getId());
    return BaseResult.setOk("添加普通优惠券成功");
}
  • 3)功能测试

编写完成后,调用/voucher/add接口,新增一个普通优惠券:

控制台打印信息如下:

add Voucher(id=null, shopId=1, title=500元代金券, subTitle=周一至周日均可使用, rules=全场通用 无需预约 可无限叠加 不兑现、不找零 仅限堂食, payValue=45000, actualValue=50000, type=null, status=null, stock=null, beginTime=null, endTime=null, createTime=null, updateTime=null)
==>  Preparing: INSERT INTO tb_voucher ( shop_id, title, sub_title, rules, pay_value, actual_value ) VALUES ( ?, ?, ?, ?, ?, ? )
==> Parameters: 1(Long), 500元代金券(String), 周一至周日均可使用(String), 全场通用 无需预约 可无限叠加 不兑现、不找零 仅限堂食(String), 45000(Long), 50000(Long)
<==    Updates: 1
add success. id = 10
4.3.2.2 添加秒杀券代码
  • 1)接口文档
项目说明
请求方法POST
请求路径/voucher/seckill/add
请求参数Voucher
返回值
  • 2)代码实现

在VoucherController类中编写一个addSeckill()方法,调用IVoucherService接口的addSeckillVoucher()方法,用于新增秒杀优惠券:

// com.star.redis.dzdp.controller.VoucherController

/**
 * 添加秒杀优惠券
 * @author hsgx
 * @since 2024/4/5 11:00
 * @param voucher
 * @return com.star.redis.dzdp.pojo.BaseResult
 */
@PostMapping("/seckill/add")
public BaseResult addSeckill(@RequestBody Voucher voucher) {
    return voucherService.addSeckillVoucher(voucher);
}
// com.star.redis.dzdp.service.impl.VoucherServiceImpl

@Resource
private ISeckillVoucherService seckillVoucherService;

@Resource
private StringRedisTemplate stringRedisTemplate;

@Override
public BaseResult addSeckillVoucher(Voucher voucher) {
    log.info("add a seckill voucher, {}", voucher.toString());
    // 1.保存优惠券信息
    save(voucher);
    log.info("add voucher success. id = {}", voucher.getId());
    // 2.保存秒杀信息
    SeckillVoucher seckillVoucher = new SeckillVoucher();
    seckillVoucher.setVoucherId(voucher.getId());
    seckillVoucher.setStock(voucher.getStock());
    seckillVoucher.setBeginTime(voucher.getBeginTime());
    seckillVoucher.setEndTime(voucher.getEndTime());
    seckillVoucherService.save(seckillVoucher);
    // 3.将秒杀优惠券的库存保存到Redis
    String key = "seckill:stock:" + voucher.getId();
    stringRedisTemplate.opsForValue().set(key, voucher.getStock().toString());
    log.info("set to Redis : Key = {}, Value = {}", key, voucher.getStock().toString());
    return BaseResult.setOk("新增秒杀券成功!");
}
  • 3)功能测试

编写完成后,调用/voucher/seckill/add接口,新增一个秒杀优惠券:

控制台打印信息如下:

add a seckill voucher, Voucher(id=null, shopId=1, title=1000元代金券, subTitle=限时秒杀, rules=周一至周日均可使用, payValue=50000, actualValue=100000, type=null, status=null, stock=1000, beginTime=Fri Apr 05 14:00:00 CST 2024, endTime=Fri Apr 05 18:00:00 CST 2024, createTime=null, updateTime=null)
==>  Preparing: INSERT INTO tb_voucher ( shop_id, title, sub_title, rules, pay_value, actual_value ) VALUES ( ?, ?, ?, ?, ?, ? )
==> Parameters: 1(Long), 1000元代金券(String), 限时秒杀(String), 周一至周日均可使用(String), 50000(Long), 100000(Long)
<==    Updates: 1
add voucher success. id = 11
==>  Preparing: INSERT INTO tb_seckill_voucher ( voucher_id, stock, begin_time, end_time ) VALUES ( ?, ?, ?, ? )
==> Parameters: 11(Long), 1000(Integer), 2024-04-05 
<==    Updates: 1
set to Redis : Key = seckill:stock:11, Value = 1000

此时,在数据库的tb_voucher表有2条记录,tb_seckill_voucher有1条记录:

4.3.3 实现秒杀下单

4.3.3.1 秒杀下单逻辑分析

如上图所示,当用户下单时,会提交优惠券的ID,后台根据该ID查询对应的优惠券信息,并判断秒杀是否开始,如果未开始,则直接返回错误信息;如果已经开始,则再次判断库存是否充足,如果不充足,则直接返回错误信息;如果充足,则扣减库存,并创建订单,并返回订单ID。

4.3.3.2 获取秒杀订单ID

每个商户都可以发布订单ID,并且保存到tb_voucher_order表中,如果这个表的ID使用自增ID,则是存在一些问题的:ID的规律太明显,且受到单表数据量的限制。

如果ID具有太明显的规律,用户或者商业对手就很容易猜测出商户的一些敏感信息,比如商户在一天时间内卖出了多少单,这明显不合适。

同时,随着商户规模的扩大,需要存入数据库的数据量也在变大,而MySQL的单表容量不宜超过500W。数据量过大之后,**就要进行拆库拆表,拆分之后,从逻辑上讲它们仍然是同一张表,所以ID是也不能是一样的。

综上,需要保证订单ID的唯一性。本项目使用全局ID生成器来解决这个问题。 全局ID生成器是一种在分布式系统下用来生成全局唯一ID的工具,满足唯一性、高可用、高性能、递增型和安全性等。

为了增加ID的安全性,使用以下编码方式生成全局唯一ID:

  • 符号位:永远是0
  • 时间戳:31bit,以秒为单位,可以使用69年
  • 序列号:32bit,支持每秒产生2^32个不同的ID

下面就创建一个工具类RedisIdWorker,用于生成全局唯一ID:

// com.star.redis.dzdp.utils.RedisIdWorker

public class RedisIdWorker {

    /**
     * 获取全局唯一ID
     * @author hsgx
     * @since 2024/4/5 12:37
     * @param stringRedisTemplate
     * @param keyPrefix
     * @return long
     */
    public static long nextId(StringRedisTemplate stringRedisTemplate, String keyPrefix) {
        // 1.生成时间戳
        long nowSec = System.currentTimeMillis() / 1000;
        // 2.生成序列号
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + new Date());
        // 3.拼接并返回
        return nowSec << 32 | count;
    }
}
4.3.3.3 获取用户ID

在tb_voucher_order表中,有一个user_id字段,该字段保存了下单用户的ID。下单用户即当前登录的用户,因此要想办法在业务层拿到当前登录用户的ID。

我们在登录拦截器中已经拿到了当前登录用户的信息,因此可以将这里拿到的信息继续向下游传递:

// com.star.redis.dzdp.interceptor.LoginInterceptor#preHandle()

// 5.存在,放行
// 将用户ID向下游传递
request.setAttribute("userId", user.getId());
return true;

如此,后续在Controller方法中,就可以使用request.getAttribute("userId")方法获取用户ID。

4.3.3.4 实现秒杀下单

为优惠券订单业务创建对应的IVoucherOrderService接口-VoucherOrderServiceImpl实现类-VoucherOrderMapper类。详见测试项目代码。

在IVoucherOrderService接口中定义一个seckillVoucher()方法,并在VoucherOrderServiceImpl实现类中具体实现,作用是秒杀下单。

// com.star.redis.dzdp.service.impl.VoucherOrderServiceImpl

@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private IVoucherOrderService voucherOrderService;
@Resource
private StringRedisTemplate stringRedisTemplate;

@Override
public BaseResult<Long> seckillVoucher(Long voucherId, Long userId) {
    log.info("开始秒杀下单...voucherId = {}, userId = {}", voucherId, userId);
    // 1.查询秒杀优惠券信息
    SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
    // 2.判断秒杀活动是否开启或结束
    if(seckillVoucher == null) {
        // 秒杀活动不存在
        return BaseResult.setFail("秒杀活动不存在!");
    } else if(seckillVoucher.getBeginTime().after(new Date())) {
        // 秒杀活动未开始
        log.info("beginTime = {}", seckillVoucher.getBeginTime());
        return BaseResult.setFail("秒杀尚未开始!");
    } else if(seckillVoucher.getEndTime().before(new Date())) {
        // 秒杀活动已结束
        log.info("endTime = {}", seckillVoucher.getEndTime());
        return BaseResult.setFail("秒杀尚已结束!");
    }
    log.info("{}", seckillVoucher.toString());
    // 3.判断库存是否充足
    if(seckillVoucher.getStock() < 1) {
        // 库存不足
        return BaseResult.setFail("库存不足,抢券失败!");
    }
    // 4.扣减库存
    boolean update = seckillVoucherService.update().setSql("stock = stock - 1")
            .eq("voucher_id", voucherId).update();
    log.info("update result = {}", update);
    if(!update) {
        // 扣减库存失败,返回抢券失败
        return BaseResult.setFail("库存不足,抢券失败!");
    }
    // 5.创建订单
    VoucherOrder voucherOrder = new VoucherOrder();
    // 5.1 设置订单ID
    Long orderId = RedisIdWorker.nextId(stringRedisTemplate, "voucher_order");
    log.info("get orderId = {}", orderId);
    voucherOrder.setId(orderId);
    // 5.2 设置用户ID
    voucherOrder.setUserId(userId);
    // 5.3 设置订单其他信息
    voucherOrder.setVoucherId(voucherId);
    voucherOrder.setPayTime(new Date());
    voucherOrderService.save(voucherOrder);
    // 6 返回订单ID
    return BaseResult.setOkWithData(orderId);
}

接着在VoucherOrderMapper类中编写一个seckillOrder()方法,作为秒杀下单的接口。

  • 1)接口文档
项目说明
请求方法POST
请求路径/voucher/seckill/order
请求参数Voucher
返回值订单ID
  • 2)代码实现
/**
 * 秒杀下单
 * @author hsgx
 * @since 2024/4/5 12:41
 * @param voucherOrder
 * @return com.star.redis.dzdp.pojo.BaseResult
 */
@PostMapping("/seckill/order")
public BaseResult seckillOrder(@RequestBody VoucherOrder voucherOrder, HttpServletRequest request) {
    return voucherOrderService.seckillVoucher(voucherOrder.getVoucherId(), (Long)request.getAttribute("userId"));
}
  • 3)功能测试

编写完成后,调用/voucher/seckill/order接口,新增一个秒杀订单:

控制台打印信息如下:

开始秒杀下单...voucherId = 11, userId = 1012
==>  Preparing: SELECT voucher_id,stock,create_time,begin_time,end_time,update_time FROM tb_seckill_voucher WHERE voucher_id=?
==> Parameters: 11(Long)
<==      Total: 1
SeckillVoucher(voucherId=11, stock=999, createTime=Fri Apr 05 11:25:00 CST 2024, beginTime=Fri Apr 05 06:00:00 CST 2024, endTime=Fri Apr 05 18:00:00 CST 2024, updateTime=Fri Apr 05 12:56:15 CST 2024)
==>  Preparing: UPDATE tb_seckill_voucher SET stock = stock - 1 WHERE (voucher_id = ?)
==> Parameters: 11(Long)
<==    Updates: 1
update result = true
get orderId = 7354246043942256641
==>  Preparing: INSERT INTO tb_voucher_order ( id, user_id, voucher_id, pay_time ) VALUES ( ?, ?, ?, ? )
==> Parameters: 7354246043942256641(Long), 1012(Long), 11(Long), 2024-04-05 13:10:40.332(Timestamp)
<==    Updates: 1

至此,秒杀下单完成。

本节完,更多内容请查阅分类专栏:Redis从入门到精通

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析(已完结)
  • MyBatis3源码深度解析(已完结)
  • 再探Java为面试赋能(持续更新中…)

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

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

相关文章

团体程序设计天梯赛-练习集 01

天梯赛题解合集 团体程序设计天梯赛-练习集 (L1-001 - L1-012) 团体程序设计天梯赛-练习集 (L1-013 - L1-024) 团体程序设计天梯赛-练习集 (L1-025 - L1-036) 团体程序设计天梯赛-练习集 (L1-037 - L1-048) L1-001 Hello World 输出题 样例 输入 输出 Hello World!思…

笔记本电脑win7 Wireless-AC 7265连不上wifi6

1.背景介绍 旧路由器连接人数有限&#xff0c;老旧&#xff0c;信号不稳定更换了新路由器&#xff0c;如 TL-XDR5430易展版用户电脑连不上新的WIFI网络了&#xff0c;比较着急 核心问题&#xff1a;有效解决笔记本连接wifi上网问题&#xff0c;方法不限 2.环境信息 Windows…

深入探索MySQL:成本模型解析与查询性能优化,及未来深度学习与AI模型的应用展望

码到三十五 &#xff1a; 个人主页 在数据库管理系统中&#xff0c;查询优化器是一个至关重要的组件&#xff0c;它负责将用户提交的SQL查询转换为高效的执行计划。在MySQL中&#xff0c;查询优化器使用了一个称为“成本模型”的机制来评估不同执行计划的优劣&#xff0c;并选择…

No dashboards are active for the current data set.

再次记录一下这个离谱的问题 之前出现这个问题是因为目录没写对 今天遇到这个问题的原因是目录是对的&#xff0c;跟目录是否带有中文也没关系 是writer写入的时候写的是空的&#xff0c;离谱的是写入是空的情况下也会生成events日志文件&#xff0c;看起来好像成功写入了一样&…

朗之万方程,机器学习与液体中的粒子运动

目录 一、说明二、朗之万方程的诞生2.1 牛顿力学2.2 流体中的随机运动 三、小质量物体布朗运动方程四、布朗运动的Python代码五、稳定性讨论5.1 波尔兹曼分布5.2 梯度下降算法 六、随机梯度下降&#xff08;SGD&#xff09;和小批量梯度下降七、机器学习与物理&#xff0c;作为…

C++ 类(初篇)

类的引入 C语言中&#xff0c;结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。 而为了区分C和C我们将结构体重新命名成class去定义 类的定义 标准格式&#xff1a; class className {// 类体&#xff1a;由成员函…

练习14 Web [极客大挑战 2019]Upload

phtml格式绕过&#xff0c;burp修改content-type绕过&#xff0c;常见的文件上传存放目录名 题目就叫upload&#xff0c;打开靶机 直接上传一个图片格式的一句话木马&#xff0c;返回如下&#xff1a; 提交练习5和9中的两种可以执行图片格式php代码的文件&#xff0c;修改con…

Three.js真实相机模拟

有没有想过如何在 3D Web 应用程序中模拟物理相机&#xff1f; 在这篇博文中&#xff0c;我将向你展示如何使用 Three.js和 OpenCV 来完成此操作。 我们将从模拟针孔相机模型开始&#xff0c;然后添加真实的镜头畸变。 具体来说&#xff0c;我们将仔细研究 OpenCV 的两个失真模…

VMware提示 该虚拟机似乎正在使用中,如何解决?

VMware提示 该虚拟机似乎正在使用中,如何解决&#xff1f; 问题描述解决方法1.找到安装VMware的文件目录2.在VMware目录下.lck后缀的文件夹删除或重命名3.运行VMware 问题描述 该虚拟机似乎正在使用中。 如果该虚拟机未在使用&#xff0c;请按“获取所有权(T)”按钮获取它的所…

github生成新的SSH密钥

首先是参考官方文档 生成新的 SSH 密钥并将其添加到 ssh-agent述 当你在创建SSH密钥时遇到提示&#xff1a; Enter file in which to save the key (/c/Users/YOU/.ssh/id_ALGORITHM):这一步是让你选择保存生成的SSH密钥对的文件名和位置。如果你直接按回车键&#xff08;[Pr…

数据结构入门系列-栈的结构及栈的实现

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 栈 栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一段进行插入和删除元素操作&#xff0c;进行数据输入和删除操作的一端称为栈顶&#xff0c;另…

一、Docker部署GitLab(详细步骤)

Docker部署GitLab&#xff08;详细步骤&#xff09; 一、拉取镜像二、启动容器三、修改配置四、修改密码五、浏览器访问 一、拉取镜像 docker安装教程&#xff1a;https://qingsi.blog.csdn.net/article/details/131270071 docker pull gitlab/gitlab-ce:latest二、启动容器 …

CSS 实现航班起飞、飞行和降落动画

CSS 实现航班起飞、飞行和降落动画 效果展示 航班起飞阶段 航班飞行阶段 航班降落 CSS 知识点 animation 属性的综合运用:active 属性的运营 动画分解 航班滑行阶段动画 实现航班的滑行阶段动画&#xff0c;需要使用两个核心物件&#xff0c;一个是跑动动画&#x…

LeetCode-994. 腐烂的橘子【广度优先搜索 数组 矩阵】

LeetCode-994. 腐烂的橘子【广度优先搜索 数组 矩阵】 题目描述&#xff1a;解题思路一&#xff1a;多源广度优先搜索&#xff08;队列实现&#xff09;解题思路二&#xff1a;哈希表实现&#xff0c;先找出所有腐烂和新鲜橘子的集合{}类似于set()。每剔除一次time1解题思路三&…

代码随想录|Day32|动态规划01|509.斐波那契数列、70.爬楼梯、746.使用最小花费爬楼梯

509.斐波那契数列 动规五步曲&#xff1a; 确定 dp[i] 含义&#xff1a;第 i 个斐波那契数值为 dp[i]递推公式&#xff1a;dp[i] dp[i - 1] dp[i - 2]dp数组初始化&#xff1a;dp[0] dp[1] 1遍历顺序&#xff1a;从前向后打印dp数组 class Solution:def fib(self, n: int) …

1.Spring Boot框架整合

Spring Boot项目创建&#xff08;约定大于配置&#xff09; 2.1.3.RELEASE版本示例 idea创建 从官网下载&#xff08;https://start.spring.io/&#xff09;单元测试默认依赖不对时&#xff0c;直接删除即可 Web支持&#xff08;SpringMVC&#xff09; <dependency>&…

在线考试|基于Springboot的在线考试管理系统设计与实现(源码+数据库+文档)

在线考试管理系统目录 目录 基于Springboot的在线考试管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、前台&#xff1a; 2、后台 管理员功能 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主…

FFmpeg与zlmedikit流媒体服务器

流媒体服务器 zlmediakit 运行流媒体服务器后的日志&#xff0c;可以看到rtsp默认端口554&#xff0c;rtmp默认端口&#xff1a;1935&#xff0c;http默认端口&#xff1a;80&#xff0c; FFmpeg rtsp推流 ffmpeg -re -stream_loop -1 -i “1.mp4” -vcodec h264 -acodec …

神经网络汇聚层

文章目录 最大汇聚层平均汇聚层自适应平均池化层 最大汇聚层 汇聚窗口从输入张量的左上角开始&#xff0c;从左往右、从上往下的在输入张量内滑动。在汇聚窗口到达的每个位置&#xff0c;它计算该窗口中输入子张量的最大值或平均值。计算最大值或平均值是取决于使用了最大汇聚…

Vue 样式技巧总结与整理[中级局]

SFC&#xff08;单文件组件&#xff09;由 3 个不同的实体组成&#xff1a;模板、脚本和样式。三者都很重要&#xff0c;但后者往往被忽视&#xff0c;即使它可能变得复杂&#xff0c;且经常导致挫折和 bug。 更好的理解可以改善代码审查并减少调试时间。 这里有 7 个奇技淫巧…