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

秒杀项目的消息推送

目录

一、创建消费者

二、创建订单链路配置

1.定义RabbitMQ配置类

2.创建RabbitmqOrderConfig配置类 

三、如何实现RabbitMQ重复投递机制

1.开启发送者消息确认模式

2.消息发送确认

① 创建ConfirmCallBacker确认模式 

② 创建ReturnCallBack退回模式 

3.创建生产者

4.创建消费者(手动消费)

5.启动测试

6.采坑日记 

① 异常点一:@RabbitListener 

② 异常点二:手动确认消息 

③ 异常点三:消息格式  

④ 异常点四:消息不确认

⑤ 异常点五:消息无限投递 

⑥ 异常点六:重复消费

四、RabbitMQ在项目中的秒杀业务优化

1.修改秒杀订单生产方式

2.消费者监听器完成秒杀订单生成


一、创建消费者

第1步:基于Spring Initialzr方式创建zmall-rabbitmq消费者模块

第2步:在公共模块中添加rabbitmq相关依赖

<!--rabbitmq-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

第3步:配置子模块zmall-rabbitmq的pom.xml,引入公共模块zmall-common

<dependencies><dependency><groupId>com.zking.zmall</groupId><artifactId>zmall-common</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

第4步:配置父模块的pom.xml,添加子模块zmall-rabbitmq 

<modules><module>zmall-common</module>...<module>zmall-rabbitmq</module>
</modules>

第5步:配置application.yml

server:port: 8050
spring:application:name: zmall-rabbitmqdatasource:#type连接池类型 DBCP,C3P0,Hikari,Druid,默认为Hikaritype: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/zmall?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=trueusername: rootpassword: 123456cloud:nacos:config:server-addr: localhost:8848redis:host: localhostport: 6379password: 123456jedis:pool:max-active: 100max-wait: 10max-idle: 10min-idle: 10database: 0rabbitmq:host: 192.168.29.128port: 5672username: adminpassword: adminvirtual-host: my_vhost# 发送者开启 confirm 确认机制#publisher-confirm-type: correlated# 发送者开启 return 确认机制#publisher-returns: true# 设置消费端手动 acklistener:simple:#手动应答acknowledge-mode: manual#消费端最小并发数concurrency: 5#消费端最大并发数max-concurrency: 10#一次请求中预处理的消息数量prefetch: 5# 是否支持重试retry:#启用消费重试enabled: true#重试次数max-attempts: 3#重试间隔时间initial-interval: 3000cache:channel:#缓存的channel数量size: 50
#mybatis-plus配置
mybatis-plus:#所对应的 XML 文件位置mapper-locations: classpath*:/mapper/*Mapper.xml#别名包扫描路径type-aliases-package: com.zking.zmall.modelconfiguration:#驼峰命名规则map-underscore-to-camel-case: true
#日志配置
logging:level:com.zking.zmall.mapper: debug

注意:这里面的配置,记得要改换为你的虚拟机ID

消费者采用的是手动消费模式,请注意设置

spring.rabbitmq.listener.simple.acknowledge-mode=manual

第6步:配置启动类

package com.zking.zmallrabbitmq;import org.checkerframework.checker.units.qual.Mass;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@EnableFeignClients
@EnableDiscoveryClient
@MapperScan({"com.zking.zmall.mapper","com.zking.zmallrabbitmq.mapper"})
@SpringBootApplication
public class ZmallRabbitmqApplication {public static void main(String[] args) {SpringApplication.run(ZmallRabbitmqApplication.class, args);}}

启动项目的时候,记得打开我们的nacos注册中心 ,测试一下看看有没有问题。能运行起来说明就没有问题。

二、创建订单链路配置

1.定义RabbitMQ配置类

定义RabbitMQ配置类,设置生成者发送数据时自动转换成JSON,设置消费者获取消息自动转换成JSON。

package com.zking.zmallrabbitmq.config;import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitmqConfig {@Beanpublic RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate template = new RabbitTemplate(connectionFactory);template.setMessageConverter(new Jackson2JsonMessageConverter());return template;}@Beanpublic SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);factory.setMessageConverter(new Jackson2JsonMessageConverter());return factory;}
}

设置RabbitTemplate消息转换模式为Jackson2JsonMessageConverter;

设置RabbitMQ消费者监听器的的消息转换模式为Jackson2JsonMessageConverter;

2.创建RabbitmqOrderConfig配置类 

创建RabbitmqOrderConfig配置类,增加订单队列、交换机及绑定关系。

package com.zking.zmallrabbitmq.config;import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitmqOrderConfig {public static final String ORDER_QUEUE="order-queue";public static final String ORDER_EXCHANGE="order-exchange";public static final String ORDER_ROUTING_KEY="order-routing-key";@Beanpublic Queue orderQueue(){return new Queue(ORDER_QUEUE,true);}@Beanpublic DirectExchange orderExchange(){return new DirectExchange(ORDER_EXCHANGE,true,false);}@Beanpublic Binding orderBinding(){return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(ORDER_ROUTING_KEY);}
}

三、如何实现RabbitMQ重复投递机制

1.开启发送者消息确认模式

配置application.yml,开启发送者confirm确认机制和return确认机制

spring:rabbitmq:# 发送者开启 confirm 确认机制publisher-confirm-type: correlated# 发送者开启 return 确认机制publisher-returns: true

2.消息发送确认

rabbitmq 的消息确认分为两部分:发送消息确认 和 消息接收确认

发送消息确认:用来确认生产者 producer 将消息发送到 brokerbroker 上的交换机 exchange 再投递给队列 queue的过程中,消息是否成功投递。

消息从 producerrabbitmq broker有一个 confirmCallback 确认模式。

消息从 exchangequeue 投递失败有一个 returnCallback 退回模式。

我们可以利用这两个Callback来确保消息100%送达。

Broker:简单来说,就是一个消息队列服务器实体。

① 创建ConfirmCallBacker确认模式 

package com.zking.zmallrabbitmq.component;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {/**** @param correlationData 对象内部只有一个 id 属性,用来表示当前消息的唯一性* @param ack 消息投递到broker 的状态,true表示成功* @param cause 表示投递失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {if (!ack) {log.error("消息发送异常!");} else {log.info("发送者已经收到确认,ack={}, cause={}",ack, cause);}}
}

② 创建ReturnCallBack退回模式 

@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {/**** @param message 消息体* @param replyCode 响应code* @param replyText 响应内容* @param exchange 交换机* @param routingKey 路由键*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);}
}

3.创建生产者

创建生产者,模拟发送消息

package com.zking.zmallrabbitmq.controller;import com.zking.zmall.model.Order;
import com.zking.zmallrabbitmq.component.ConfirmCallbackService;
import com.zking.zmallrabbitmq.component.ReturnCallbackService;
import com.zking.zmallrabbitmq.config.RabbitmqOrderConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;@RestController
public class ProducerController {@Autowiredprivate RabbitTemplate rabbitTemplate;@Autowiredprivate ReturnCallbackService returnCallbackService;@Autowiredprivate ConfirmCallbackService confirmCallbackService;@RequestMapping("/sendMessage")public void sendMessage(){Order order=new Order();order.setId(1);order.setUserId(2);order.setLoginName("zhangsan");order.setUserAddress("长沙");order.setCreateTime(new Date());order.setCost(120.0F);order.setSerialNumber("123");order.setState(0);//ConfirmCallback确认模式rabbitTemplate.setConfirmCallback(confirmCallbackService);//ReturnCallback退回模式rabbitTemplate.setReturnCallback(returnCallbackService);rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,RabbitmqOrderConfig.ORDER_ROUTING_KEY,order);}
}

4.创建消费者(手动消费)

package com.zking.zmallrabbitmq.component;import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import com.zking.zmall.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class OrderConsumerListener {//最大重试次数private static final Integer MAX_RECONSUME_COUNT=3;//用于记录消息重试次数的集合,可以采用Redis方式实现private static Map<String,Integer> retryMap=new HashMap<>();@RabbitHandler@RabbitListener(queues = {"order-queue"},ackMode = "MANUAL")public void recieveMessage(Message message,Order order,Channel channel) throws IOException {//channel内按顺序自增的消息IDlong deliverTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息:"+message+",消息内容:"+ JSON.toJSONString(order));//模拟异常,开始消息重试int i= 1/0;} catch (Exception e) {e.printStackTrace();String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");Integer retryCount = retryMap.get(msgId)==null?1:retryMap.get(msgId);log.info("即将开始第{}次消息重试....",retryCount);if(retryCount>=MAX_RECONSUME_COUNT){log.info("重试次数达到3次,消息被拒绝,retryCount="+retryCount);//此处要注意:当重试次数到达3次后,将拒绝消息且不在重新入队列channel.basicReject(deliverTag,false);}else{//重新发送消息到队尾//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。channel.basicPublish(message.getMessageProperties().getReceivedExchange(),message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.MINIMAL_PERSISTENT_BASIC,JSON.toJSONBytes(order));}retryMap.put(msgId,retryCount+1);}//成功确认消息,非批量模式channel.basicAck(deliverTag, false);}
}

5.启动测试

访问:http://localhost:8050/sendMessage

6.采坑日记 

① 异常点一:@RabbitListener 

 异常原因:@RabbitListener作用于类上引发异常; 解决方案:@RabbitListener移至消费者监听器的方法上,而@RabbitListener只适用于方法级别。

② 异常点二:手动确认消息 

 虽然在消费者端的application.yml中配置手动消费模式,但是在服务消费时引发了这个异常错误,导致重复消费的问题。原因是使用@RabbitListener注解会自动ACK,如果方法中再手动ACK会造成重复ACK,所以报错;解决方式就是在@RabbitListener中配置手动消费模式:ackMode = "MANUAL"。

③ 异常点三:消息格式  

 在消费者消费消息时引发异常,触发消息重新投递,但是由于重新投递时导致消息格式问题引发了消息转换异常。 具体原因通过查看日志发现,重新投递的消息格式为text/plain,而我们在处理消息时采用的是json方式,导致消息转换异常。解决方案:将重新发送消息的状态由MessageProperties.PERSISTENT_TEXT_PLAIN更改为MessageProperties.MINIMAL_PERSISTENT_BASIC

④ 异常点四:消息不确认

这是一个非常没技术含量的坑,但却是非常容易犯错的地方。开启消息确认机制,消费消息别忘了channel.basicAck,否则消息会一直存在,导致重复消费。

⑤ 异常点五:消息无限投递 

 最开始接触消息确认机制的时候,消费端代码就像下边这样写的,思路很简单:处理完业务逻辑后确认消息, int a = 1 / 0 发生异常后将消息重新投入队列

@RabbitHandler
public void recieveMessage(Message message,Order order,Channel channel) throws IOException {//channel内按顺序自增的消息IDlong deliverTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息:"+message+",消息内容:"+ JSON.toJSONString(order));int i = 1 / 0;channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);}
}

但是有个问题是,业务代码一旦出现 bug 99.9%的情况是不会自动修复,一条消息会被无限投递进队列,消费端无限执行,导致了死循环。

经过测试分析发现,当消息重新投递到消息队列时,这条消息不会回到队列尾部,仍是在队列头部。

消费者会立刻消费这条消息,业务处理再抛出异常,消息再重新入队,如此反复进行。导致消息队列处理出现阻塞,导致正常消息也无法运行。

而解决方案是,先将消息进行应答,此时消息队列会删除该条消息,同时我们再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。

//重新发送消息到队尾
//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。
channel.basicPublish(message.getMessageProperties().getReceivedExchange(),message.getMessageProperties().getReceivedRoutingKey(), 								 MessageProperties.MINIMAL_PERSISTENT_BASIC,JSON.toJSONBytes(order));

⑥ 异常点六:重复消费

如何保证 MQ 的消费是幂等性,这个需要根据具体业务而定,可以借助MySQL、或者redis 将消息持久化。

上述代码结构图

四、RabbitMQ在项目中的秒杀业务优化

1.修改秒杀订单生产方式

第1步:修改zmall-order订单模块的application.yml,加入rabbitmq相关配置

spring:rabbitmq:host: 192.168.195.143port: 5672username: adminpassword: adminvirtual-host: my_vhost# 设置消费端手动 acklistener:simple:acknowledge-mode: manual# 是否支持重试retry:enabled: truemax-attempts: 3

第2步:修改秒杀订单生成方式,针对抢购成功的秒杀订单直接推送到RabbitMQ中

OrderServiceImpl

@Autowiredprivate RabbitTemplate rabbitTemplate;@Transactional@Overridepublic JsonResponseBody<?> createKillOrder(User user, Integer pid, Float price) {//判断用户是否登录if(null==user)throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);//根据秒杀商品ID和用户ID判断是否重复抢购Order order = redisService.getKillOrderByUidAndPid(user.getId(), pid);if(null!=order)return new JsonResponseBody<>(JsonResponseStatus.ORDER_REPART);//Redis库存预减long stock = redisService.decrement(pid);if(stock<0){redisService.increment(pid);return new JsonResponseBody<>(JsonResponseStatus.STOCK_EMPTY);}//创建订单order=new Order();order.setUserId(user.getId());order.setLoginName(user.getLoginName());order.setPid(pid);order.setCost(price);//将生成的秒杀订单保存到Redis中redisService.setKillOrderToRedis(pid,order);//将生成的秒杀订单推送到RabbitMQ中的订单队列中rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,RabbitmqOrderConfig.ORDER_ROUTING_KEY,order);return new JsonResponseBody<>();}

 Order

//    数据库不存在的字段 
@TableField(exist =  false)private  Integer pid;

2.消费者监听器完成秒杀订单生成

第1步:将zmall-order订单模块中的service业务处理接口及实现类移至消息者监听器模块。

第2步:zmall-rabbitmq模块中在IOrderService及OrderServiceImpl中重新定义生成秒杀订单方法

public interface IOrderService extends IService<Order> {void saveOrder(Order order);
}
package com.zking.zmallrabbitmq.service.impl;import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zking.zmall.config.RabbitmqOrderConfig;
import com.zking.zmall.mapper.OrderMapper;
import com.zking.zmall.model.Kill;
import com.zking.zmall.model.Order;
import com.zking.zmall.model.OrderDetail;
import com.zking.zmall.model.User;
import com.zking.zmall.service.impl.RedisServiceImpl;
import com.zking.zmall.util.JsonResponseBody;
import com.zking.zmall.util.JsonResponseStatus;
import com.zking.zmall.util.SnowFlake;
import com.zking.zmallrabbitmq.service.IOrderService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;//import io.seata.spring.annotation.GlobalTransactional;/*** <p>*  服务实现类* </p>** @author zking* @since 2023-02-08*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {@Autowiredprivate KillServiceImpl killService;@Autowiredprivate OrderDetailServiceImpl orderDetailService;@Autowiredprivate RabbitTemplate rabbitTemplate;@Transactional@Overridepublic void saveOrder(Order order) {//2.秒杀商品库存减一boolean flag=killService.update(new UpdateWrapper<Kill>().setSql("total=total-1").eq("item_id",order.getPid()).gt("total",0));if(!flag)return;//3.生成秒杀订单及订单项SnowFlake snowFlake=new SnowFlake(2,3);Long orderId=snowFlake.nextId();int orderIdInt = new Long(orderId).intValue();//订单order.setSerialNumber(orderIdInt+"");this.save(order);//订单项OrderDetail orderDetail=new OrderDetail();orderDetail.setOrderId(orderIdInt);orderDetail.setProductId(order.getPid());orderDetail.setQuantity(1);orderDetail.setCost(order.getCost());orderDetailService.save(orderDetail);}}

第3步:修改秒杀订单消费者监听器

OrderDetailServiceImpl

package com.zking.zmallrabbitmq.component;import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import com.zking.zmall.model.Order;
import com.zking.zmallrabbitmq.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class OrderConsumerListener {@Autowiredprivate IOrderService orderService;//最大重试次数private static final Integer MAX_RECONSUME_COUNT=3;//用于记录消息重试次数的集合,可以采用Redis方式实现private static Map<String,Integer> retryMap=new HashMap<>();@RabbitHandler@RabbitListener(queues = {"order-queue"},ackMode = "MANUAL")public void recieveMessage(Message message,Order order,Channel channel) throws IOException {//channel内按顺序自增的消息IDlong deliverTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息:"+message+",消息内容:"+ JSON.toJSONString(order));//模拟异常,开始消息重试
//            int i= 1/0;
//            修改库存、生成订单表数据,生成订单项表数据
//            保存秒杀订单及订单项orderService.saveOrder(order);} catch (Exception e) {e.printStackTrace();String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");Integer retryCount = retryMap.get(msgId)==null?1:retryMap.get(msgId);log.info("即将开始第{}次消息重试....",retryCount);if(retryCount>=MAX_RECONSUME_COUNT){log.info("重试次数达到3次,消息被拒绝,retryCount="+retryCount);//此处要注意:当重试次数到达3次后,将拒绝消息且不在重新入队列channel.basicReject(deliverTag,false);}else{//重新发送消息到队尾//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。channel.basicPublish(message.getMessageProperties().getReceivedExchange(),message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.MINIMAL_PERSISTENT_BASIC,JSON.toJSONBytes(order));}retryMap.put(msgId,retryCount+1);}//成功确认消息,非批量模式channel.basicAck(deliverTag, false);}
}

 重启Jmeter压测,并查看测试结果。

注意1:目前微服务架构明显拆分,存在重复类的现象,所以在做微服务架构设计的时候,要尽可能避免此问题;

如service层中尽量避免调用service;service中尽可能只调用Mapper;

注意2:实际项目开发中,对于消息未能正常消费,应该设置人工补偿机制;

测试如下: 

 启动七个服务,nacos、nginx,项目

 

相关文章:

秒杀项目的消息推送

目录 一、创建消费者 二、创建订单链路配置 1.定义RabbitMQ配置类 2.创建RabbitmqOrderConfig配置类 三、如何实现RabbitMQ重复投递机制 1.开启发送者消息确认模式 2.消息发送确认 ① 创建ConfirmCallBacker确认模式 ② 创建ReturnCallBack退回模式 3.创建生产者 …...

最近开发及 vue3 几个小总结

只是单纯记录一下最近维护了几个项目之后的感触&#xff0c;也只是在自己现在水平上面的一些感觉。发发牢骚&#xff0c;水水文章 是否过度封装 可能水平不够吧&#xff0c;在 axios 封装的时候&#xff0c;只是把响应和拦截还有一些全局的配置封装了一下&#xff0c;并没有加…...

代谢组学分享-花青素通过调节氨基酸代谢改善糖尿病肾病的肾功能

代谢组学文献分享&#xff0c;文章标题&#xff1a;Anthocyanin improves kidney function in diabetic kidney disease by regulating amino acid metabolism 发表期刊&#xff1a;Journal of Translational Medicine 影响因子&#xff1a;8.44 作者单位&#xff1a;中山大…...

超简单!pytorch入门教程:Tensor

超简单&#xff01;pytorch入门教程&#xff1a;Tensor 一、pytorch安装 安装pytorch之前&#xff0c;需要安装好python&#xff08;废话&#xff09;&#xff0c;还没安装过python的宝宝请先移步到廖雪峰的python教程&#xff0c;待安装熟悉完之后&#xff0c;再过来这边。 …...

如何使用COCO数据集,注意事项

COCO数据集可用来训练目标检测&#xff0c;分类&#xff0c;实例分割等。 下面简单说下如何使用这个数据集&#xff0c; 数据集下载可用如下的代码进行&#xff0c;以2017为例。 # Download the image data. cd ./images echo "Downloading MSCOCO train images ...&quo…...

金三银四跳槽季,JAVA面试撸题就来【笑小枫】微信小程序吧~

JAVA面试撸题就来【笑小枫】微信小程序啦~ 疫情已过&#xff0c;金三银四即将到来&#xff0c;小伙伴们是否有跳槽的打算呢&#xff1f;不管有没有&#xff0c;技术不能丢&#xff0c;让我们一起来撸题吧。 博主最近整理了一批面试题&#xff0c;包括JAVA基础、多线程与锁、Red…...

分享115个HTML电子商务模板,总有一款适合您

分享115个HTML电子商务模板&#xff0c;总有一款适合您 115个HTML电子商务模板下载链接&#xff1a;https://pan.baidu.com/s/158y3jP0tv7ZikxNOBMKsSg?pwdt970 提取码&#xff1a;t970 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 import os import shuti…...

Python 字符串

字符串是 Python 中最常用的数据类型。我们可以使用引号 ( 或 " ) 来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。例如&#xff1a;var1 Hello World!var2 "Python Runoob"Python 访问字符串中的值Python 不支持单字符类型&#xff…...

总线定义,车载总线:车载etherNet or CAN

总线的定义总线是连接多个设备或者接入点的数据传输通路。这里面的关键词是多个设备或者接入点&#xff0c;所以不要过于局限的看待总线。根据互联的设备/接入点不同&#xff0c;传输的数据带宽&#xff0c;速率&#xff0c;距离不同和应用场景的不同都可能有不同的总线。不同的…...

Python(for和while)循环嵌套及用法

Python 不仅支持 if 语句相互嵌套&#xff0c;while 和 for 循环结构也支持嵌套。 所谓嵌套&#xff08;Nest&#xff09;&#xff0c;就是一条语句里面还有另一条语句&#xff0c;例如 for 里面还有 for&#xff0c;while 里面还有 while&#xff0c;甚至 while 中有 for 或者…...

6万字电力行业系统解决方案光伏电站综合安防系统解决方案

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目录 第 一 章背景与需求 1.1行…...

[Android Studio]Android 数据存储--SQLite数据库存储

&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Android Debug&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; Topic 发布安卓学习过程中遇到问题解决过程&#xff0c;希望我的解决方案可以对小伙伴们有帮助。 &#x1f4cb;笔记目…...

学校节能降耗减排方案——能耗监管平台的建设及效果剖析

摘要&#xff1a;作为崭新的校园能耗管理手段&#xff0c;能耗监测平台以传统管理方式无法企及的优势有力地提升了高校能源管理工作的水平&#xff0e;从而受到了相关管理者的青睐。本文梳理总结了高校能耗监测平台的基本组成和优势特点&#xff0c;同时对能耗平台建设和使用中…...

探索IP地址的应用

无论是互联网行业还是传统行业都会用到网络&#xff0c;作为企业如何维护网络安全&#xff0c;保障网站不被攻击&#xff0c;数据不被泄露等。这个时候我们就会通查询IP归属地&#xff0c;辅助企业解决安全问题。下面介绍一下ip归属地在各行业的具体应用。1.网安行业应用一&…...

点赞破万!阿里面试官总结的2022最新1685页Java面试宝典太全了

程序员入职企业的难度也在持续加大&#xff0c;如何顺利通过面试成为了大家所关心的话题。针对这些人群的需求&#xff0c;小编从阿里找来一份让大家在求职过程中旗开得胜&#xff01;是从什么时候开始准备的&#xff1f;大概的我已经记不清了&#xff0c;可能是 4 月份左右开始…...

项目搭建规范

一. 代码规范 1.1. 集成editorconfig配置 EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。 # http://editorconfig.org root true [*] # 表示所有文件适用 charset utf-8 # 设置文件字符集为 utf-8 indent_style space # 缩进…...

8.Docker Machine

Docker Machine Docker Machine是Docker官方编排&#xff08;Orchestration&#xff09;项目之一&#xff0c;负责在多种平台上快速安装 Docker 环境。 Docker Machine项目基于Go语言实现&#xff0c;目前在Github上进行维护。 Docker Machine是 Docker 官方提供的一个工具&…...

如何配合使用ESLINT 和 PRETTIER

各自的作用 eslint&#xff1a;静态分析语法错误prettier&#xff1a;代码格式化工具&#xff08;需要在IDE中安装prettier的插件&#xff09; 使用方法&#xff1a; 安装 npm install eslint --save-dev // 创建 .eslintrc.json 文件 npx eslint --init npm install eslint-…...

学英语的优势已来,抓住这个机会

文 / 冰雪&#xff08;微信公众号&#xff1a;王不留&#xff09; ChatGPT大火&#xff0c;国外的商业价值还没找到&#xff0c;咱们这边已经开始变现了。谷雨小姐姐昨天在”一起学英语”微信群发了一张“收割韭菜”的文案截图。 299入社群&#xff0c;服务内容为&#xff1a;免…...

基于微信小程序云开发实现考研题库小程序项目(完整版)

今天手把手的带大家实现一款答题类的题库小程序&#xff0c;如果着急的话&#xff0c;可以直接去看文末源码.下载与项目部署。考研题库小程序云开发实战&#xff0c;完整版提供给大家学习。题库小程序&#xff0c;基于云开发的微信答题小程序&#xff0c;软件架构是微信原生小程…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...