当前位置: 首页 > news >正文

尚品汇-秒杀下单实现-页面轮询查询订单状态(五十三)

目录:

(1)整合秒杀业务

(2)秒杀下单

(3)秒杀下单监听

(4)页面轮询接口

(1)整合秒杀业务

秒杀的主要目的就是获取一个下单资格,拥有下单资格就可以去下单支付,获取下单资格后的流程就与正常下单流程一样,只是没有购物车这一 步,总结起来就是,秒杀根据库存获取下单资格,拥有下单资格进入下单页面(选择地址,支付方式,提交订单,然后支付订单)

步骤:

  1. 校验下单码,只有正确获得下单码的请求才是合法请求
  2. 校验状态位state

State为null,说明非法请求;

State0说明已经售罄;

State为1,说明可以抢购

状态位是在内存中判断,效率极高,如果售罄,直接就返回了,不会给服务器造成太大压力

  1. 前面条件都成立,将秒杀用户加入队列,然后直接返回
  2. 前端轮询秒杀状态,查询秒杀结果

(2)秒杀下单

添加mq常量MqConst

/*** 秒杀*/
public static final String EXCHANGE_DIRECT_SECKILL_USER = "exchange.direct.seckill.user";
public static final String ROUTING_SECKILL_USER = "seckill.user";
//队列
public static final String QUEUE_SECKILL_USER  = "queue.seckill.user";

定义实体UserRecode

记录哪个用户要购买哪个商品!

@Data
public class UserRecode implements Serializable {private static final long serialVersionUID = 1L;private Long skuId;private String userId;
}

编写控制器SeckillGoodsApiController

@Autowired
private RabbitService rabbitService;
/*** 根据用户和商品ID实现秒杀下单* @param skuId* @return*/
@PostMapping("auth/seckillOrder/{skuId}")
public Result seckillOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) throws Exception {//校验下单码(抢购码规则可以自定义)String userId = AuthContextHolder.getUserId(request);String skuIdStr = request.getParameter("skuIdStr");if (!skuIdStr.equals(MD5.encrypt(userId))) {//请求不合法return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);}//校验状态位//产品标识, 1:可以秒杀 0:秒杀结束String state = (String) CacheHelper.get(skuId.toString());if (StringUtils.isEmpty(state)) {//请求不合法return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);}if ("1".equals(state)) {//用户记录UserRecode userRecode = new UserRecode();userRecode.setUserId(userId);userRecode.setSkuId(skuId);//发送消息rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);} else {//已售罄return Result.build(null, ResultCodeEnum.SECKILL_FINISH);}return Result.ok();
}

全局统一返回结果类: 

package com.atguigu.gmall.common.result;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** 全局统一返回结果类**/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private T data;public Result(){}// 返回数据protected static <T> Result<T> build(T data) {Result<T> result = new Result<T>();if (data != null)result.setData(data);return result;}public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {Result<T> result = build(body);result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());return result;}public static<T> Result<T> ok(){return Result.ok(null);}/*** 操作成功* @param data* @param <T>* @return*/public static<T> Result<T> ok(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.SUCCESS);}public static<T> Result<T> fail(){return Result.fail(null);}/*** 操作失败* @param data* @param <T>* @return*/public static<T> Result<T> fail(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.FAIL);}public Result<T> message(String msg){this.setMessage(msg);return this;}public Result<T> code(Integer code){this.setCode(code);return this;}public boolean isOk() {if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {return true;}return false;}
}

统一返回结果状态信息类:

package com.atguigu.gmall.common.result;import lombok.Getter;/*** 统一返回结果状态信息类**/
@Getter
public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(201, "失败"),SERVICE_ERROR(2012, "服务异常"),ILLEGAL_REQUEST( 204, "非法请求"),PAY_RUN(205, "支付中"),LOGIN_AUTH(208, "未登陆"),PERMISSION(209, "没有权限"),SECKILL_NO_START(210, "秒杀还没开始"),SECKILL_RUN(211, "正在排队中"),SECKILL_NO_PAY_ORDER(212, "您有未支付的订单"),SECKILL_FINISH(213, "已售罄"),SECKILL_END(214, "秒杀已结束"),SECKILL_SUCCESS(215, "抢单成功"),SECKILL_FAIL(216, "抢单失败"),SECKILL_ILLEGAL(217, "请求不合法"),SECKILL_ORDER_SUCCESS(218, "下单成功"),COUPON_GET(220, "优惠券已经领取"),COUPON_LIMIT_GET(221, "优惠券已发放完毕"),;private Integer code;private String message;private ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}
}

(3)秒杀下单监听

思路:

  1. 首先判断产品状态位,我们前面不是已经判断过了吗?因为产品可能随时售罄,mq队列里面可能堆积了十万数据,但是已经售罄了,那么后续流程就没有必要再走了;
  2. 判断用户是否已经下过订单,这个地方就是控制用户重复下单,同一个用户只能抢购一个下单资格,怎么控制呢?很简单,我们可以利用setnx控制用户,当用户第一次进来时,返回true,可以抢购,以后进入返回false,直接返回,过期时间可以根据业务自定义,这样用户这一段咋们就控制注了
  3. 获取队列中的商品,如果能够获取,则商品有库存,可以下单。如果获取的商品id为空,则商品售罄,商品售罄我们要第一时间通知兄弟节点,更新状态位,所以在这里发送redis广播
  4. 将订单记录放入redis缓存,说明用户已经获得下单资格,秒杀成功
  5. 秒杀成功要更新库存

SeckillReceiver添加监听方法

@Autowired
private SeckillGoodsService seckillGoodsService;//  监听用户与商品的消息!
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = MqConst.QUEUE_SECKILL_USER,durable = "true",autoDelete = "false"),exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_SECKILL_USER),key = {MqConst.ROUTING_SECKILL_USER}
))
public void seckillUser(UserRecode userRecode,Message message,Channel channel){try {//  判断接收过来的数据if (userRecode!=null){//  预下单处理!seckillGoodsService.seckillOrder(userRecode.getSkuId(),userRecode.getUserId());}} catch (Exception e) {e.printStackTrace();}//  手动确认channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}

预下单接口SeckillGoodsService接口


/*** 根据用户和商品ID实现秒杀下单* @param skuId* @param userId*/
void seckillOrder(Long skuId, String userId);

秒杀订单实体类

package com.atguigu.gmall.model.activity;@Data
public class OrderRecode implements Serializable {private static final long serialVersionUID = 1L;private String userId;private SeckillGoods seckillGoods;private Integer num;private String orderStr;
}

 实现类

/**** 创建订单* @param skuId* @param userId*/
@Override
public void seckillOrder(Long skuId, String userId) {//产品状态位, 1:可以秒杀 0:秒杀结束String state = (String) CacheHelper.get(skuId.toString());if("0".equals(state)) {//已售罄return;}//判断用户是否下单boolean isExist = redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_USER + userId, skuId.toString(), RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);if (!isExist) {return;}//获取队列中的商品,取List中的一个,从右边出来一个,如果能够获取,则商品存在,可以下单String goodsId = (String) redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();if (StringUtils.isEmpty(goodsId)) {//商品售罄,更新状态位  0位售罄状态  发布订阅消息,商品已经销售完了redisTemplate.convertAndSend("seckillpush", skuId+":0");//已售罄return;}//订单记录OrderRecode orderRecode = new OrderRecode();orderRecode.setUserId(userId);orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));orderRecode.setNum(1);//生成订单单码orderRecode.setOrderStr(MD5.encrypt(userId+skuId));//订单数据存入ReidsredisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(orderRecode.getUserId(), orderRecode);//更新库存this.updateStockCount(orderRecode.getSeckillGoods().getSkuId());
}

更新库存

//  表示更新mysql -- redis 的库存数据!
public void updateStockCount(Long skuId) {//  加锁!Lock lock = new ReentrantLock();//  上锁lock.lock();try {//  获取到存储库存剩余数!//  key = seckill:stock:46String stockKey = RedisConst.SECKILL_STOCK_PREFIX + skuId;//  redisTemplate.opsForList().leftPush(key,seckillGoods.getSkuId());//获取库存//Long count = this.redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).size();Long count = redisTemplate.boundListOps(stockKey).size();//  减少库存数!方式一减少压力!//if (count%2==0){//  开始更新数据!SeckillGoods seckillGoods = this.getSeckillGoods(skuId);//  赋值剩余库存数!seckillGoods.setStockCount(count.intValue());//  更新的数据库!seckillGoodsMapper.updateById(seckillGoods);//  更新缓存!redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(seckillGoods.getSkuId().toString(),seckillGoods);//}} finally {//  解锁!lock.unlock();}
}
 /*** 获取商品详情* @param skuId* @return*/@Overridepublic SeckillGoods getSeckillGoods(Long skuId) {return (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());}

 

减少一个:之前9个 

生成一个订单

用户买过之后生成一个用户的信息(防止多次下单)

(4)页面轮询接口

 

思路:

1.  判断用户是否在缓存中存在

 

2.  判断用户是否抢单成功

3.  判断用户是否下过订单

4.  判断状态位

接口

SeckillGoodsService接口
/**** 根据商品id与用户ID查看订单信息* @param skuId* @param userId* @return*/
Result checkOrder(Long skuId, String userId);

实现类

@Override
public Result checkOrder(Long skuId, String userId) {// 用户在缓存中存在,有机会秒杀到商品boolean isExist =redisTemplate.hasKey(RedisConst.SECKILL_USER + userId);if (isExist) {//判断用户是否正在排队//判断用户是否抢单成功boolean isHasKey = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).hasKey(userId);if (isHasKey) {//抢单成功  获取用户临时订单:说明还没有支付OrderRecode orderRecode = (OrderRecode) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);// 秒杀成功! return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);}}//判断是否下单,查询总订单 seckill:orders:users userId OrderIdboolean isExistOrder = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);if(isExistOrder) {String orderId = (String)redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);}String state = (String) CacheHelper.get(skuId.toString());if("0".equals(state)) {//已售罄 抢单失败return Result.build(null, ResultCodeEnum.SECKILL_FAIL);}//正在排队中return Result.build(null, ResultCodeEnum.SECKILL_RUN);
}

控制器

SeckillGoodsApiController
@GetMapping(value = "auth/checkOrder/{skuId}")
public Result checkOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) {//当前登录用户String userId = AuthContextHolder.getUserId(request);return seckillGoodsService.checkOrder(skuId, userId);
}

 

 

 

轮询排队页面

该页面有四种状态:

  1. 排队中
  2. 各种提示(非法、已售罄等)
  3. 抢购成功,去下单
  4. 抢购成功,已下单,显示我的订单

抢购成功,页面显示去下单,跳转下单确认页面

<div class="seckill_dev" v-if="show == 3">抢购成功&nbsp;&nbsp;<a href="/seckill/trade.html" target="_blank">去下单</a>
</div>

总结:

商品秒杀流程:

1.用户抢单的时候先会生成一个下单码,后面会先校验用户的下单码,只有正确获得下单码的请求才是合法请求,然后再校验状态位state,状态位是在内存中判断,效率极高,如果售罄,直接就返回了,不会给服务器造成太大压力,前面条件都成立,将秒杀用户加入队列,然后直接返回

2.监听队列,进行清单,首先判断产品状态位,我们前面不是已经判断过了吗?因为产品可能随时售罄,mq队列里面可能堆积了十万数据,但是已经售罄了,那么后续流程就没有必要再走了

然后判断用户是否已经下过订单,这个地方就是控制用户重复下单,同一个用户只能抢购一个下单资格,我们可以利用setnx控制用户,当用户第一次进来时,返回true,可以抢购,以后进入返回false

要控制库存数量,不能超卖,提供一种解决方案,那就我们在导入商品缓存数据时,同时将商品库存信息导入队列{list},利用redis队列的原子性,保证库存不超卖

然后将订单记录放入redis缓存,说明用户已经获得下单资格,秒杀成功,秒杀成功要更新库存(更新Mysql的库存和Redis中的库存)

3.前端页面轮训查询订单状态,判断用户是否抢单成功,下单了Redis生成临时订单数据(支付了会删除临时订单)抢单成功,去下单页面,判断用户是否下单查询总订单数据是否支付,下单了去我们订单页面,判断状态位,是否售罄

相关文章:

尚品汇-秒杀下单实现-页面轮询查询订单状态(五十三)

目录&#xff1a; &#xff08;1&#xff09;整合秒杀业务 &#xff08;2&#xff09;秒杀下单 &#xff08;3&#xff09;秒杀下单监听 &#xff08;4&#xff09;页面轮询接口 &#xff08;1&#xff09;整合秒杀业务 秒杀的主要目的就是获取一个下单资格&#xff0c;拥…...

2024年微电子与纳米技术国际研讨会(ICMN 2024) Microelectronics and Nanotechnology

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网&#xff1a;https://ais.cn/u/vEbMBz提交检索&#xff1a;EI Compendex、IEEE Xplore、Scopus大会时间&#xff1a;2024年9月20-22日地点&#xff1a;成都…...

2024最新版,人大赵鑫老师《大语言模型》新书pdf分享

本书主要面向希望系统学习大语言模型技术的读者&#xff0c;将重点突出核心概念与 算法&#xff0c;并且配以示例与代码&#xff08;伪代码&#xff09;帮助读者理解特定算法的实现逻辑。由于大语言模型技术的快速更迭&#xff0c;本书无法覆盖所有相关内容&#xff0c;旨在梳理…...

[Leetcode 543][Easy]-二叉树的直径-递归

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 取一个结点的最大直径就是取一个结点的左子树最大深度右子树最大深度之和&#xff0c;因此可以定义一个递归函数&#xff0c;作用是取一个结点的最大直径。这个函数中还实现了求左子树最大深度…...

高级大数据开发学习路线指南

掌握大数据技术是一项系统性工程&#xff0c;涉及到广泛的技能和专业知识。为了帮助初学者构建坚实的基础&#xff0c;并逐步成长为大数据领域的专家&#xff0c;下面详细阐述了一条全面而深入的学习路线&#xff1a; 1. Java 编程基础 - 打造坚实的底层技能 关键知识点&…...

SpringBoot设置mysql的ssl连接

因工作需要&#xff0c;mysql连接需要开启ssl认证&#xff0c;本文主要讲述客户端如何配置ssl连接。 开发环境信息&#xff1a; SpringBoot&#xff1a; 2.0.5.RELEASE mysql-connector-java&#xff1a; 8.0.18 mysql version&#xff1a;8.0.18 一、检查服务端是否开启ssl认…...

2024-1.2.12-Android-Studio配置

本地博客: https://k1t0111.github.io/ K1T0 最近在做一些app方向的移动技术开发学习&#xff0c;但是由于AS的配置问题&#xff0c;市面上找不到最新的2024版本的AS的相关配置。笔者也是踩了很多坑&#xff0c;因此想写一篇文章记录一下最新的AS 2024 1.2.12的对应java环境的一…...

前端vue左侧树的一整套功能实现(一):vue2+vite封装v-resize指令,实现左侧树拖拽宽度和折叠展开

实现v-resize指令&#xff0c;具体以下功能&#xff1a; 指令接收宽度最大最小值&#xff0c;接收一个id用于localStorage存储拖拽宽度&#xff0c;接收padding拖拽时产生虚线拖拽&#xff0c;松开鼠标再进行元素宽度调整折叠展开图标使用本地图片 封装一个vite下使用本地图片…...

本地部署huggingface模型,建立自己的翻译应用

过去&#xff0c;我们使用翻译接口时&#xff0c;往往都是使用百度等的接口&#xff0c;每天有一定量的免费额度。今天为大家介绍一个可以进行翻译的模型&#xff0c;具备英译中、中译英的能力。并且在这个过程中&#xff0c;向大家介绍一个如何在本地部署模型。在之前的”五天…...

基于python+django+vue的在线学习资源推送系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤pythondjangovue…...

.Net Gacutil工具(全局程序集缓存工具)使用教程

GAC介绍&#xff1a; GAC&#xff08;Global Assembly Cache&#xff09;全局程序集缓存&#xff0c;是用于存放.Net应用程序共享的程序集。 像平常我们在Visual Studio中引用系统程序集时&#xff0c;这些程序集便来自于GAC。 GAC默认位置为&#xff1a;%windir%\Microsoft…...

安卓13修改设置设备型号和设备名称分析与更改-android13设置设备型号和设备名称更改

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 用户要定制一些系统显示的设备型号和设备名称,这就需要我们分析设置里面的相关信息来找到对应的位置进行修改了。 2.问题分析 像这种信息要么是config.xml里面写死了,要…...

AI健身体能测试之基于paddlehub实现引体向上计数个数统计

【引体向上计数】 本项目使用PaddleHub中的骨骼检测模型human_pose_estimation_resnet50_mpii&#xff0c;进行人体运动分析&#xff0c;实现对引体向上的自动计数。 1. 项目介绍 人体运动分析是近几年许多领域研究的热点问题。在学科的交叉研究上&#xff0c;人体运动分析涉…...

Redis常见报错及解决方法总结

Redis常见报错及解决方法总结 Redis作为高效的内存数据库&#xff0c;在实际使用过程中不可避免会遇到一些问题和报错。为了帮助大家更好地应对这些问题&#xff0c;我将常见的Redis报错及其解决方法进行总结&#xff0c;并提供具体的操作步骤。 1. Connection Refused 错误…...

【TabBar嵌套Navigation案例-JSON的简单使用 Objective-C语言】

一、JSON的简单使用 1.我们先来看一下示例程序里边,产品推荐页面, 在我们这个产品推荐页面里面, 它是一个CollectionViewController,注册的是一个xib的一个类型,xib显示这个cell,叫做item,然后,这个邮箱大师啊,包括这个图标,以及这些东西,都是从哪儿来的呢,都是从…...

通过鼠标移动来调整两个盒子的宽度(响应式)

DOM结构&#xff1a; <div class"courer"> // 外层盒子<div class"dividing-line" title"拖动"></div> // 拖动的那个线<div class"course-title-box"> // 第一个盒子<div class"course-content-…...

React Zustand状态管理库的使用

Zustand 是一个轻量级的状态管理库&#xff0c;适用于 React 和浏览器环境中的状态管理需求。它由 Vercel 开发并维护&#xff0c;旨在提供一种简单的方式来管理和共享状态。Zustand 的设计理念是尽可能简化状态管理&#xff0c;使其更加直观和易于使用。 Zustand 官网点击跳转…...

pyrosetta MoveMap介绍

在 PyRosetta 中,MoveMap 是一个非常重要的类,用来控制蛋白质分子中哪些部分可以在某些操作(如折叠、旋转、优化等)中被移动。MoveMap 允许你精确地指定哪些残基、键角或原子可以进行特定的运动,从而帮助你在蛋白质结构预测、优化和设计中进行灵活的控制。 MoveMap 的功能…...

在线安全干货|如何更改IP地址?

更改IP地址是一个常见的需求&#xff0c;无论是为了保护个人隐私、绕过地理限制还是进行商业数据分析。不同的IP更改方法适用于不同的需求和环境。但请注意&#xff0c;更改IP地址应在合法场景下进行&#xff0c;无论使用什么方法&#xff0c;都需要在符合当地网络安全法律法规…...

【C++】【网络】【Linux系统编程】单例模式,加锁封装TCP/IP协议套接字

目录 引言 获取套接字 绑定套接字 表明允许监听 单例模式设计 完整代码示例 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 引言 有关套接字编程的细节和更多的系统调用课参考《UNIX环境高级编程》一书&#xff0c;可以在如下网站搜索电子版&#xff0c;该书在第16章详…...

无机布防火卷帘门价格怎么算?按尺寸定制,按需报价

无机布防火卷帘门作为建筑防火分区的核心设备&#xff0c;价格一直是工程采购的关注重点。很多用户在询价时&#xff0c;会发现不同厂家的报价差异较大&#xff0c;这是因为无机布防火卷帘门的价格并非按统一单价计算&#xff0c;而是完全根据项目的实际需求定制化核算。 &…...

Arduino PWM转4-20mA工业电流信号:二阶滤波与V/I转换电路设计

1. 项目概述&#xff1a;从PWM到工业标准电流信号在工业自动化、过程控制和传感器领域&#xff0c;4-20 mA电流环是一个几乎无处不在的标准。它用4 mA代表测量值的下限&#xff08;如0C&#xff09;&#xff0c;20 mA代表上限&#xff08;如100C&#xff09;&#xff0c;这种设…...

毕业设计 yolov11骨折检测医疗辅助系统(源码+论文)

文章目录 0 前言1 项目运行效果2 课题背景2.1 研究背景2.2 国内外研究现状2.3 研究意义 3 设计框架&#xff08;骨折检测系统设计框架说明&#xff09;3.1. 系统架构图3.2. 技术选型3.2.1 核心组件3.2.2 辅助工具 3.3. 核心模块设计3.3.1 YOLO模型训练模块训练流程图关键伪代码…...

告别数据饥荒:用PyTorch手把手实现原型网络(Prototypical Networks)做电影评论情感分类

告别数据饥荒&#xff1a;用PyTorch手把手实现原型网络做电影评论情感分类 在自然语言处理领域&#xff0c;情感分析一直是热门研究方向&#xff0c;但现实中的开发者常面临一个尴尬困境&#xff1a;标注数据太少。传统深度学习方法动辄需要成千上万的标注样本&#xff0c;而实…...

别再死磕USB HID了!用ESP32的Arduino框架手把手教你实现蓝牙鼠标键盘(附完整代码)

ESP32蓝牙HID实战&#xff1a;零基础打造自定义键盘鼠标 手里那块吃灰的ESP32开发板终于能派上用场了&#xff01;上周我用它做了个无线演示控制器&#xff0c;在会议室里走着就能翻PPT&#xff0c;同事们都问是怎么实现的。其实秘诀就在于ESP32的蓝牙HID功能——不需要任何USB…...

Gazebo Sim多旋翼控制:四轴飞行器动力学建模与PID调参

Gazebo Sim多旋翼控制&#xff1a;四轴飞行器动力学建模与PID调参 【免费下载链接】gz-sim Open source robotics simulator. The latest version of Gazebo. 项目地址: https://gitcode.com/gh_mirrors/gz/gz-sim Gazebo Sim是一款功能强大的开源机器人模拟器&#xff…...

如何快速上手DeepPurpose?5分钟完成你的第一个药物-靶点相互作用预测模型

如何快速上手DeepPurpose&#xff1f;5分钟完成你的第一个药物-靶点相互作用预测模型 【免费下载链接】DeepPurpose A Deep Learning Toolkit for DTI, Drug Property, PPI, DDI, Protein Function Prediction (Bioinformatics) 项目地址: https://gitcode.com/gh_mirrors/de…...

HiveWE终极指南:快速掌握魔兽争霸III现代化地图编辑器

HiveWE终极指南&#xff1a;快速掌握魔兽争霸III现代化地图编辑器 【免费下载链接】HiveWE A Warcraft III world editor. 项目地址: https://gitcode.com/gh_mirrors/hi/HiveWE 还在为传统魔兽争霸III地图编辑器缓慢的加载速度和复杂的操作界面而烦恼吗&#xff1f;Hiv…...

如何优化 MySQL 千万级数据分页查询的性能?

它的本质是&#xff1a;**传统 LIMIT offset, size 在大数据量下性能急剧下降&#xff0c;是因为 MySQL 必须 扫描并丢弃 前 offset 行数据。当 offset 很大时&#xff08;如 LIMIT 1000000, 10&#xff09;&#xff0c;MySQL 需要读取 1,000,010 行记录&#xff0c;执行 1,000…...

Taotoken的稳定性与低延迟在实时对话应用中的实际体验

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Taotoken的稳定性与低延迟在实时对话应用中的实际体验 在开发需要快速响应的AI聊天应用时&#xff0c;后端API的稳定性和延迟表现是…...