Redis延时队列在订单超时未报到场景的应用分享
一、引言
在电商、医疗预约等众多业务场景中,经常会遇到需要处理超时任务的情况。比如医疗预约订单,如果患者在支付成功后,到了预约结束时间还未报到,系统需要自动取消订单。为了实现这样的功能,我们可以利用 Redis 延时队列。本文将详细介绍 Redis 延时队列的使用,对比它与其他消息队列的优缺点,并结合实际的订单超时未报到业务代码进行分享。
二、Redis 延时队列详细介绍
2.1 什么是 Redis 延时队列
Redis 延时队列是一种特殊的队列,它允许元素在指定的时间后才被消费。在 Redis 中,通常可以使用有序集合(Sorted Set)或 Redisson 提供的延迟队列来实现。有序集合的分数可以用来表示元素的过期时间,通过不断轮询有序集合,当分数小于当前时间时,就将元素取出消费。而 Redisson 则提供了更方便的 API 来实现延时队列,它内部封装了很多复杂的操作,让开发者可以更简单地使用。
2.2 工作原理
以 Redisson 实现的延时队列为例,它基于 Redis 的 List 和 ZSet 数据结构。当我们向延时队列中添加元素时,Redisson 会将元素存储在一个 ZSet 中,分数为元素的过期时间。同时,会有一个后台线程不断轮询 ZSet,当发现有元素的分数小于当前时间时,就将元素从 ZSet 移动到 List 中,然后消费者就可以从 List 中获取元素进行消费。
三、Redis 延时队列与其他消息队列的对比
3.1 与 MQ(如 RabbitMQ)对比
- 优点
- 简单易用:Redis 延时队列的实现相对简单,不需要像 RabbitMQ 那样复杂的配置和管理。对于一些简单的业务场景,使用 Redis 延时队列可以快速实现功能。
- 性能高:Redis 是基于内存的数据库,读写速度非常快。在处理大量的延时任务时,Redis 延时队列可以提供更高的性能。
- 缺点
- 功能有限:相比 RabbitMQ,Redis 延时队列的功能相对较少。例如,RabbitMQ 支持多种消息模式(如发布 - 订阅、路由等),而 Redis 延时队列主要用于处理延时任务。
- 可靠性低:Redis 没有像 RabbitMQ 那样完善的消息确认机制和持久化策略。如果 Redis 出现故障,可能会导致部分消息丢失。
- 应用场景
- Redis 延时队列:适用于对性能要求较高、业务逻辑相对简单的延时任务场景,如订单超时未支付、缓存过期等。
- RabbitMQ:适用于对消息可靠性要求较高、业务逻辑复杂的场景,如分布式系统中的消息传递、异步任务处理等。
3.2 与 Kafka 对比
- 优点
- 低延迟:Redis 延时队列的响应速度非常快,可以在短时间内处理大量的延时任务。而 Kafka 主要用于高吞吐量的消息处理,在处理延时任务时可能会有一定的延迟。
- 易于集成:Redis 可以很方便地与各种编程语言和框架集成,对于开发者来说更加友好。
- 缺点
- 吞吐量低:Kafka 具有高吞吐量的特点,可以处理海量的消息。而 Redis 延时队列在处理大规模数据时,吞吐量相对较低。
- 数据持久化弱:Kafka 支持数据的持久化存储,即使服务器重启也不会丢失数据。而 Redis 的数据持久化策略相对较弱,可能会导致数据丢失。
- 应用场景
- Redis 延时队列:适用于对延迟要求较高、数据量较小的延时任务场景。
- Kafka:适用于大数据处理、日志收集等需要高吞吐量的场景。
3.3 为什么订单超时未报到使用延时队列
在订单超时未报到的场景中,我们需要在订单支付成功后,在预约结束时间到达时自动取消订单。这个场景对延迟要求较高,需要在指定的时间点准确地执行任务。Redis 延时队列可以很好地满足这个需求,它可以在指定的时间后将订单信息从队列中取出,然后进行相应的处理。而且,这个业务场景相对简单,不需要像 MQ 或 Kafka 那样复杂的功能,因此使用 Redis 延时队列更加合适。
四、订单超时未报到业务代码分享
4.1 Redis 延时队列工具类
import cn.hutool.core.collection.ConcurrentHashSet;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit; /** * @author * description: reids 延迟队列工具类 */
@Slf4j
@Component
public class RedisDelayQueueUtil<T> implements ApplicationContextAware { public static RedissonClient redissonClient; private T obj; public static Set<String> queueCodeSet = new ConcurrentHashSet<>(); //release发布后开启 @Scheduled(cron = "0 */10 * * * ?") private void keepAlive() { queueCodeSet.forEach((code) -> { //云redis会主动断掉长期未使用的链接,主动激活 addDelayQueue("keepAlive", 1, TimeUnit.SECONDS, code); }); } /** * 添加延迟队列 * * @param value 队列值 * @param delay 延迟时间 * @param timeUnit 时间单位 * @param queueCode 队列键 * @param <T> */ public static <T> void addDelayQueue(T value, long delay, TimeUnit timeUnit, String queueCode) { try { RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueCode); RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque); delayedQueue.offer(value, delay, timeUnit); log.debug("Redisson 添加延时队列成功 队列键:{},队列值:{},延迟时间:{}", queueCode, value, timeUnit.toSeconds(delay) + "秒"); } catch (Exception e) { log.error("Redisson 添加延时队列失败 {}", e.getMessage()); throw new RuntimeException("Redisson添加延时队列失败"); } } /** * 获取延迟队列 - 会阻塞 * * @param queueCode 队列名称 * @return <T> 数据 * @throws InterruptedException */ public static <T> Optional<T> getDelayQueue(String queueCode) throws InterruptedException { queueCodeSet.add(queueCode); RBlockingDeque<T> blockingDeque = redissonClient.getBlockingDeque(queueCode); try { T t = blockingDeque.take(); if(Objects.equals("keepAlive",t)){ return Optional.empty(); } return Optional.of(t); } catch (Exception e) { return Optional.empty(); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { RedisDelayQueueUtil.redissonClient = applicationContext.getBean(RedissonClient.class); }
}
4.2 订单支付成功后入延时队列
ps:只是部分代码,主要展示延时队列的添加
@Slf4j
@Service
public class WechatPayCallbackServiceImpl implements WechatPayCallbackService { @Resource private HisFeign hisFeign; @Resource private HisV2Feign hisV2Feign; @Resource private OrderService orderService; @Resource private UserInfoService userInfoService; @Resource private TenantConfigApi tenantConfigApi; @Resource private MedicalRegOrderApi medicalRegOrderApi; @Resource private OrderAddressService orderAddressService; @Resource private OrderPrescriptionService orderPrescriptionService; //订单超时未报到:支付成功后入延时队列 private final String ORDER_TIMEOUT_WITHOUT_REPORTING = "delayQueue:OrderTimeoutWithoutReportingDelayQueue"; @Override public ProcessDto businessProcess(ProcessParam processParam) { ProcessDto processDto = new ProcessDto(); Order order = orderService.getOne(Wrappers.<Order>lambdaQuery().eq(Order::getOrderSeq, processParam.getBusiTransactionNumber())); if (ObjectUtil.isEmpty(order)) { processDto.setBusiLogicSuccessful(YesNo.NO); processDto.setErrorMsg(" 订单不存在"); return processDto; } //如果微信回调我们的状态不是成功,订单记录异常 if (processParam.getPaymentSuccessful().equals(YesNo.NO)) { order.setExceptionFlag(true); order.setExceptionDesc(" 订单微信回调的支付状态未成功"); order.setOrderStatus(5); order.setCancelReason(CancelType.EXCEPTION_REFUND.getDesc()); orderService.updateById(order); //TODO 异常退费逻辑 processDto.setBusiLogicSuccessful(YesNo.NO); processDto.setErrorMsg(" 微信回调的支付状态未成功"); return processDto; } //获取租户token TenantUserContextVO contextVO = userInfoService.getTenantToken(order.getHospitalId(), order.getPatientId(), order.getUserId()); switch (order.getOrderType()) { case 1: //挂号订单 log.info(" 挂号订单支付"); //如果微信支付回调的状态是成功,那么需要将成功的订单,入到超时未报到延时队列中,供超时未报到业务使用 createOrderCheckTask(order); return registeredOrder(processParam, order, contextVO); case 2: //处方订单 log.info(" 处方订单支付"); return prescriptionOrder(processParam, order, contextVO); default: log.error(" 订单类型错误"); processDto.setBusiLogicSuccessful(YesNo.NO); processDto.setErrorMsg(" 订单类型错误"); return processDto; } } // 超时未报到订单检查延时任务 private void createOrderCheckTask(Order order) { try { log.info(" 开始创建超时未报到订单检查延时任务,订单信息为:{}", JSONUtil.toJsonPrettyStr(order)); Duration duration; //todo 方案一:这里可以根据预约结束时间或者最晚报到时间,作为结束时间。如果超过结束时间就取消订单。【暂时使用方案一】 // 方案二:这里可以根据时间段,比如上午(12:00:00)、下午(17:00:00)、晚上(20:00:00)。全天的话,可以根据全天的结束时间为标准。 LocalDateTime prebookStartTime = order.getPrebookStartTime(); //预约结束时间 LocalDateTime prebookEndTime = order.getPrebookEndTime(); //最晚报到时间 LocalDateTime registerEndDate = order.getRegisterEndDate(); if (Objects.nonNull(prebookEndTime)) { duration = Duration.between(LocalDateTime.now(), prebookEndTime); } else if (Objects.nonNull(registerEndDate)) { duration = Duration.between(LocalDateTime.now(), registerEndDate); } else { duration = Duration.between(prebookStartTime, LocalDateTime.now().plusMinutes(30)); } if (duration.getSeconds() < 10) { //避免时间过短出现问题 duration = Duration.ofSeconds(10L); } log.info(" 创建超时未报到订单检查延时任务,orderId为{},检查时间为{}", order.getId(), LocalDateTime.now().plusSeconds(duration.getSeconds())); RedisDelayQueueUtil.addDelayQueue(order.getId(), duration.getSeconds(), TimeUnit.SECONDS, ORDER_TIMEOUT_WITHOUT_REPORTING); } catch (Exception e) { log.error(" 超时未报到订单检查延时任务创建异常:" + e); GlobalException.throwEx(" 超时未报到订单检查延时任务创建异常"); } }
}
4.3 订单超时业务处理
ps:这里用了线程池,但是搞复杂了,其实可以直接用定时去消费延时队列
/*** 订单重构超时场景校验处理*/
@Component
@Slf4j
public class OrderRefactorPayCheckSchedule implements CommandLineRunner {//订单超时未支付:创建订单的时候入延时队列,和之前共用一个:超过支付限制时间自动取消private final String ORDER_PAY_CHECK_DELAY_QUEUE = "delayQueue:OrderPayCheckDelayQueue";//订单超时未报到:支付成功后入延时队列:超过预约结束时间后,自动取消private final String ORDER_TIMEOUT_WITHOUT_REPORTING = "delayQueue:OrderTimeoutWithoutReportingDelayQueue";//订单超时未接诊:报道后入延时队列:24小时后未接诊自动取消private final String ORDER_TIMEOUT_NO_APPOINTMENT_RECEIVED = "delayQueue:OrderTimeoutNoAppointmentReceivedDelayQueue";//订单自动结束问诊:医生接诊时入延时队列:24小时未结束问诊自动取消private final String ORDER_CONSULTATION_AUTOMATIC_END = "delayQueue:OrderConsultationAutomaticEndDelayQueue";//处方超时未支付 1小时后自动取消private final String PRESCRIPTION_PAY_CHECK_DELAY_QUEUE = "delayQueue:PrescriptionPayCheckDelayQueue";//医生停诊private final String DOCTOR_SUSPEND_DELAY_QUEUE = "delayQueue:DoctorSuspendDelayQueue";// 订单自动结束问诊:24小时未结束@Value("${order.timeout.automatic_end_hours:24}")private Double orderConsultationAutomaticEndHours;@Resourceprivate OrderService orderService;@Resourceprivate SessionApi sessionApi;@Resourceprivate SessionRuntimeApi sessionRuntimeApi;@Resourceprivate ConsultationRecordService consultationRecordService;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate ConsultationRecordApi consultationRecordApi;@Resourceprivate UserApi userApi;@Resourceprivate ShortMessageApi shortMessageApi;@Resourceprivate SuspendService suspendService;@Resourceprivate DoctorConfigApi doctorConfigApi;// 在类变量区新增线程池配置@Value("${schedule.pool.coreSize:5}")private int corePoolSize;@Value("${schedule.pool.maxSize:10}")private int maxPoolSize;@Value("${schedule.pool.queueCapacity:100}")private int queueCapacity;// 新增线程池bean(放在类变量声明之后)private ThreadPoolTaskExecutor taskExecutor;@PostConstructprivate void initThreadPool() {taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(corePoolSize);taskExecutor.setMaxPoolSize(maxPoolSize);taskExecutor.setQueueCapacity(queueCapacity);taskExecutor.setThreadNamePrefix("OrderSchedule-");taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());taskExecutor.initialize();}/*** 开启线程接受阻塞队列内的待同步订单进行同步*/@Overridepublic void run(String... args) {// 为每个队列启动一个独立的线程(超时未支付业务,OrderPayCheckSchedule中已经有了,这里可以注掉)//taskExecutor.execute(() -> wrapTask(this::handleOrderPayCheck));// 使用线程池提交任务taskExecutor.execute(() -> wrapTask(this::handleOrderTimeoutWithoutReporting));taskExecutor.execute(() -> wrapTask(this::handleOrderTimeoutNoAppointmentReceived));taskExecutor.execute(() -> wrapTask(this::handleOrderConsultationAutomaticEnd));taskExecutor.execute(() -> wrapTask(this::handlePrescriptionPayCheckDelayQueue));taskExecutor.execute(() -> wrapTask(this::handleDoctorSuspendDelayQueue));}// 新增任务包装方法private void wrapTask(Runnable task) {while (!Thread.currentThread().isInterrupted()) {try {task.run();} catch (Throwable e) {log.error("定时任务执行异常", e);try {TimeUnit.SECONDS.sleep(5); // 异常后暂停5秒} catch (InterruptedException ex) {Thread.currentThread().interrupt();}}}}/*** 订单超时未支付业务处理*/private void handleOrderPayCheck() {Optional<Long> optional = null;try {optional = RedisDelayQueueUtil.getDelayQueue(ORDER_PAY_CHECK_DELAY_QUEUE);} catch (InterruptedException e) {GlobalException.throwEx("获取延时队列异常:" + e);}log.info("获取延时队列成功,延时队列为:" + optional);if (optional.isPresent()) {Long orderId = optional.get();log.info("开始检查支付状态,订单id:{}", orderId);//调用订单超时未支付逻辑处理//todo 延时任务没有traceId,不方便排查,这里手动设置traceIdMap<String, String> contextMap = new HashMap<>();contextMap.put(Constants.TRACE_ID, "orderId_" + orderId);MDC.setContextMap(contextMap);checkOrderStatus(orderId, CancelTypeEnum.TIMEOUT.getCode());}}/*** 医生停诊业务处理*/private void handleDoctorSuspendDelayQueue() {Optional<DoctorSuspendEvent> optional = null;try {optional = RedisDelayQueueUtil.getDelayQueue(DOCTOR_SUSPEND_DELAY_QUEUE);} catch (InterruptedException e) {GlobalException.throwEx("获取延时队列异常:" + e);}log.info("获取延时队列成功,延时队列为:" + optional);if (optional.isPresent()) {DoctorSuspendEvent doctorSuspendEvent = optional.get();log.info("开始处理停诊业务,医生停诊信息为:{}", doctorSuspendEvent);doctorSuspend(doctorSuspendEvent);}}/*** 订单超时未报到业务处理*/private void handleOrderTimeoutWithoutReporting() {Optional<Long> optional = null;try {optional = RedisDelayQueueUtil.getDelayQueue(ORDER_TIMEOUT_WITHOUT_REPORTING);} catch (InterruptedException e) {GlobalException.throwEx("获取延时队列异常:" + e);}log.info("获取延时队列成功,延时队列为:" + optional);if (optional.isPresent()) {Long orderId = optional.get();log.info("处理超时未报到逻辑,订单id:{}", orderId);//todo 延时任务没有traceId,不方便排查,这里手动设置traceIdMap<String, String> contextMap = new HashMap<>();contextMap.put(Constants.TRACE_ID, "orderId_" + orderId);MDC.setContextMap(contextMap);// 调用超时未报到处理逻辑checkOrderStatus(orderId, CancelTypeEnum.UN_REPORT.getCode());}}/*** 订单超时未接诊业务处理*/private void handleOrderTimeoutNoAppointmentReceived() {Optional<Long> optional = null;try {optional = RedisDelayQueueUtil.getDelayQueue(ORDER_TIMEOUT_NO_APPOINTMENT_RECEIVED);} catch (InterruptedException e) {GlobalException.throwEx("获取延时队列异常:" + e);}log.info("获取延时队列成功,延时队列为:" + optional);if (optional.isPresent()) {Long orderId = optional.get();log.info("处理超时未接诊逻辑,订单id:{}", orderId);//todo 延时任务没有traceId,不方便排查,这里手动设置traceIdMap<String, String> contextMap = new HashMap<>();contextMap.put(Constants.TRACE_ID, "orderId_" + orderId);MDC.setContextMap(contextMap);// 调用超时未接诊处理逻辑checkOrderStatus(orderId, CancelTypeEnum.OVERTIME.getCode());}}/*** 订单自动结束问诊业务处理*/private void handleOrderConsultationAutomaticEnd() {Optional<Long> optional = null;try {optional = RedisDelayQueueUtil.getDelayQueue(ORDER_CONSULTATION_AUTOMATIC_END);} catch (InterruptedException e) {GlobalException.throwEx("获取延时队列异常:" + e);}log.info("获取延时队列成功,延时队列为:" + optional);if (optional.isPresent()) {Long orderId = optional.get();log.info("处理自动结束问诊逻辑,订单id:{}", orderId);//todo 延时任务没有traceId,不方便排查,这里手动设置traceIdMap<String, String> contextMap = new HashMap<>();contextMap.put(Constants.TRACE_ID, "orderId_" + orderId);MDC.setContextMap(contextMap);// 调用自动结束问诊处理逻辑checkOrderStatus(orderId, CancelTypeEnum.AUTO_END.getCode());}}private void handlePrescriptionPayCheckDelayQueue() {Optional<Long> optional = null;try {optional = RedisDelayQueueUtil.getDelayQueue(PRESCRIPTION_PAY_CHECK_DELAY_QUEUE);} catch (InterruptedException e) {GlobalException.throwEx("获取延时队列异常:" + e);}log.info("获取延时队列成功,延时队列为:" + optional);if (optional.isPresent()) {Long orderId = optional.get();log.info("处理处方结束问诊逻辑,订单id:{}", orderId);//todo 延时任务没有traceId,不方便排查,这里手动设置traceIdMap<String, String> contextMap = new HashMap<>();contextMap.put(Constants.TRACE_ID, "orderId_" + orderId);MDC.setContextMap(contextMap);// 调用自动结束问诊处理逻辑checkOrderStatus(orderId, null);}}/*** 订单业务处理** @param orderId*/public void checkOrderStatus(Long orderId, Integer code) {log.info("开始检查订单状态,订单id:{}", orderId);Order order = orderService.lambdaQuery().eq(Order::getId, orderId).one();if (ObjectUtil.isEmpty(order)) {GlobalException.throwEx("获取订单信息为空 订单id:" + orderId);}CancelOrderDTO cancelOrderDTO = new CancelOrderDTO();cancelOrderDTO.setOrderId(order.getId());cancelOrderDTO.setOrderSeq(order.getOrderSeq());cancelOrderDTO.setSendShortMessage(true);switch (Objects.requireNonNull(OrderType.of(order.getOrderType()))) {case VISIT -> {if (Objects.equals(code, CancelTypeEnum.TIMEOUT.getCode()) && Objects.equals(PayStatus.UN_PAY.getCode(), order.getPayStatus()) && !Objects.equals(ConsultationRecordStatus.CANCEL.getCode(), order.getOrderStatus())) {log.info("超时未支付,订单取消,订单为{}", order);} else if (Objects.equals(code, CancelTypeEnum.UN_REPORT.getCode()) && Objects.equals(PayStatus.PAID.getCode(), order.getPayStatus()) && Objects.equals(ConsultationRecordStatus.WAITFOR_CHECKIN.getCode(), order.getOrderStatus())) {log.info("超时未报到,订单取消,订单为{}", order);} else if (Objects.equals(code, CancelTypeEnum.OVERTIME.getCode()) && Objects.equals(PayStatus.PAID.getCode(), order.getPayStatus()) && Objects.equals(ConsultationRecordStatus.WAITFOR_INQUIRING.getCode(), order.getOrderStatus())) {log.info("超时未接诊,订单取消,订单为{}", order);} else if (Objects.equals(code, CancelTypeEnum.AUTO_END.getCode()) && Objects.equals(PayStatus.PAID.getCode(), order.getPayStatus()) && Objects.equals(ConsultationRecordStatus.INQUIRING.getCode(), order.getOrderStatus())) {log.info("自动结束问诊,订单为{}", order);//判断当前时间是否小于问诊结束时间,如果小于就说明延长问诊了,不执行后续业务//获取im会话信息//im诊室关闭时间// 获取当前时间//自动结诊:(1)更改订单与问诊订单状态,为已结束//关闭im诊室//短信通知//获取当前医生结诊时常配置(默认24小时)} else {//其他场景待补充log.info("问诊订单其他场景:订单信息:" + order);}}case PRESCRIPTION -> {// 校验订单状态log.info("处方订单开始校验是否支付", JSONUtil.toJsonPrettyStr(order));if (Objects.equals(RpStatus.UN_PAY.getCode(), order.getOrderStatus()) || Objects.equals(RpStatus.EXTERNAL_RP.getCode(), order.getOrderStatus())) {// 作废处方订单orderService.invalidPrescriptionByOrderId(orderId);}}}}
}
ps:后续会有超时场景的应用补充说明:Redis延时队列在订单超时未报到场景的应用补充说明-CSDN博客
相关文章:
Redis延时队列在订单超时未报到场景的应用分享
一、引言 在电商、医疗预约等众多业务场景中,经常会遇到需要处理超时任务的情况。比如医疗预约订单,如果患者在支付成功后,到了预约结束时间还未报到,系统需要自动取消订单。为了实现这样的功能,我们可以利用 Redis 延…...
精心整理-2024最新网络安全-信息安全全套资料(学习路线、教程笔记、工具软件、面试文档).zip
2024最新网络安全-信息安全全套资料(学习路线、教程笔记、工具软件、面试文档),视频教程文档资料共55GB。 一、网络安全-信息安全学习路线 0、网络安全-信息安全思维导图.jpg 1、网络安全大师课 V2024.pdf 2、网络安全行业白皮书.pdf 3、网络…...
多人协同进行qt应用程序开发应该注意什么?
多人协同进行Qt应用程序开发的注意事项 多人协同开发Qt应用程序时,需要注意以下几个关键方面以确保开发效率和代码质量: 1. 代码版本控制 使用Git:建立合理的分支策略(如Git Flow).gitignore配置:确保忽…...
8.4考研408简单选择排序与堆排序知识点深度解析
考研408「简单选择排序与堆排序」知识点全解析 一、简单选择排序 1.1 定义与核心思想 简单选择排序(Selection Sort)是一种选择排序算法,其核心思想是: 每趟选择:从待排序序列中选择最小(或最大&#x…...
C++中ShellExecute函数使用方法说明,如果一开始参数为隐藏,后面还能再显示出来吗
文章目录 一、ShellExecute基础用法函数原型关键参数 nShowCmd示例代码:启动程序并隐藏窗口 二、隐藏后能否重新显示窗口直接答案 三、实现隐藏后显示窗口的步骤1. 获取目标窗口句柄2. 显示窗口 四、完整流程示例五、注意事项六、总结 在C中使用ShellExecute函数时&…...
MySQL数据库精研之旅第五期:CRUD的趣味探索(上)
专栏:MySQL数据库成长记 个人主页:手握风云 目录 一、CRUD简介 二、Create新增 2.1. 语法 2.2. 示例 三、Retrieve检索 3.1. 语法 3.2. 示例 一、CRUD简介 CURD是对数据库中的记录进行基本的增删改查操作:Create(创建)、Retrieve(检索…...
【文件上传】✈️大文件的上传服务器的简单实现
💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥 🏆本篇文章阅读大约耗时五分钟。 ⛳️motto:不积跬步、无以千里 📋📋📋本文目录如下:🎁🎁&a…...
Windows DOS窗口12个命令
DOS 命令是指在 Windows 命令提示符(CMD)中使用的命令行工具,源于早期的 Disk Operating System。虽然现代 Windows 系统更多使用图形界面,但命令提示符仍然是测试人员的重要工具。测试人员通常需要执行文件操作、测试网络连接、监…...
AI加Python的文本数据情感分析流程效果展示与代码实现
本文所使用数据来自于梯田景区评价数据。 一、数据预处理 数据清洗 去除重复值、空值及无关字符(如表情符号、特殊符号等)。 提取中文文本,过滤非中文字符。 统一文本格式(如全角转半角、繁体转简体)。 中文分词与去停用词 使用 jieba 分词工具进行分词。 加载自定义词…...
Go语言手动内存对齐的四大场景与实践指南
Go语言手动内存对齐的四大场景与实践指南 引言:Go的内存对齐机制 Go语言通过编译器自动处理内存对齐问题,开发者通常无需关心底层细节。然而,在特定场景下,手动干预内存对齐是避免程序崩溃或数据错乱的必要操作。本文将深入探讨G…...
PDF多表格结构识别与跨表语义对齐:基于对抗迁移的鲁棒相似度度量模型
文章目录 一. 项目结构二.流程分析2.1 批处理器核心代码解析 三. 跨页表格相似度匹配原理3.1 表头内容相似度-特征向量归一化3.2 表头内容相似度-余弦相似度3.3 定时缓存清理 ocr扫描有其局限性。对于pdf文本类型这种pdfbox,aspose-pdf,spire直接提取文本…...
docker启动nacos+redis+seata
docker启动nacos 最新版本的nacos需要再启动的时候设置mysql的一些属性,【也可以先启动nacos,再到配置文件中找到application.yml设置mysql的一些属性】。 1.如果直接启动nacos设置的mysql我们需要确定两个容器的ip都是一样的。 查看mysql容器中的ip命令…...
从 select 到 epoll:拆解 I/O 多路复用的演进与实战
目录 一、引言:为什么需要 I/O 多路复用? 二、select 1.函数介绍 2.原理 3.样例代码 4.优缺点总结 三、poll 1.函数介绍 2.样例代码 3.优缺点总结 四、epoll 1.函数介绍 2.原理 3.LT和ET两种工作模式 4.优缺点总结 五、核心机制对比&…...
Go后端架构探索:从 MVC 到 DDD 的演进之路
Go语言 MVC 与 DDD 分层架构详细对比 MVC和DDD是后台开发两种流行的分层架构思想,MVC(Model-View-Controller)是一种设计模式,主要用于分离用户界面、业务逻辑和数据模型,便于分层解耦,而DDD(领…...
【力扣hot100题】(017)矩阵置零
还是挺简单的,使用哈希表记录需要置换的行列即可,这样就可以避免重复节省时间。 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {unordered_set<int> row;unordered_set<int> line;for(int i0;i&l…...
One Commander 3,文件管理新体验
One Commander 3 是一款集多功能于一体 Windows 10/11的文件管理工具,其设计目的在于为用户带来多元化的操作体验。这款工具通过支持多栏界面布局,让用户能够迅速且高效地组织和管理文件。此外,它还提供了多主题选项和多种图标集,…...
Ubuntu 下 nginx-1.24.0 源码分析
main 函数在 src\core\nginx.c int ngx_cdecl main(int argc, char *const *argv) {ngx_buf_t *b;ngx_log_t *log;ngx_uint_t i;ngx_cycle_t *cycle, init_cycle;ngx_conf_dump_t *cd;ngx_core_conf_t *ccf;ngx_debug_init();if (ngx_strerror_in…...
c# ftp上传下载 帮助类
工作中FTP的上传和下载还是很常用的。如下载打标数据,上传打标结果等。 这个类常用方法都有了:上传,下载,判断文件夹是否存在,创建文件夹,获取当前目录下文件列表(不包括文件夹) ,获取当前目录下文件列表(不包括文件夹) ,获取FTP文件列表(包括文件夹), 获取当前目…...
Java进阶——静态代理与动态代理
代理模式是一种常用的设计模式,为其他对象提供一种代理以控制对这个对象的访问。代理模式就像是一个中间人,客户端通过代理来间接访问目标对象,可以在不修改目标对象的基础上,对目标对象的功能进行增强或扩展。代理模式主要分为静…...
VS Code 中 .history`文件的来源与 .gitignore`的正确使用
引言 在使用 VS Code 进行 Git 版本控制时,有时会发现项目中多出一个 .history 目录,并被 Git 识别为未跟踪文件。本文将解释 .history 的来源,并提供 .gitignore 的正确配置方法,确保开发环境的整洁性。 1. .history 文件的来源…...
非手性分子发光有妙招:借液晶之力,实现高不对称圆偏振发光
*本文只做阅读笔记分享* 一、圆偏振发光研究背景与挑战 圆偏振发光(CPL)材料在3D显示、光电器件等领域大有用处,衡量它的一个重要指标是不对称发光因子(glum)。早期CPL材料的glum值低,限制了实际应用。为…...
解释器模式_行为型_GOF23
解释器模式 解释器模式(Interpreter Pattern)是一种行为型设计模式,核心思想是定义语言的文法规则,并构建一个解释器来解析和执行该语言中的表达式。它类似于“翻译器”——将符合特定语法规则的文本(如数学公式、脚本…...
OTN(Optical Transport Network)详解
OTN(光传送网)是一种基于**波分复用(WDM)**的大容量光传输技术,结合了SDH的运维管理优势和WDM的高带宽特性,广泛应用于骨干网、城域核心层及数据中心互联(DCI)。 1. OTN 的基本概念 …...
YOLOv8+ Deepsort+Pyqt5车速检测系统
该系统通过YOLOv8进行高效的目标检测与分割,结合DeepSORT算法完成目标的实时跟踪,并利用GPU加速技术提升处理速度。系统支持模块化设计,可导入其他权重文件以适应不同场景需求,同时提供自定义配置选项,如显示标签和保存…...
【干货】前端实现文件保存总结
⚠️⚠️文前推荐一下👉 前端必备工具推荐网站(图床、API和ChatAI、智能AI简历、AI思维导图神器等实用工具): 站点入口:http://luckycola.com.cn/ 前端实现文件保存实现总结 在Web开发中,文件下载是常见的交互需求。本文将系统总结前端实现文…...
并发编程之FutureTask.get()阻塞陷阱:深度解析线程池CPU飚高问题排查与解决方案
FutureTask.get方法阻塞陷阱:深度解析线程池CPU飚高问题排查与解决方法 FutureTask.get()方法阻塞陷阱:深度解析线程池CPU飚高问题排查与解决方法1、情景复现1.1 线程池工作原理1.2 业务场景模拟1.3 运行结果1.4 发现问题:线程池没有被关闭1.…...
DGNN-YOLO:面向遮挡小目标的动态图神经网络检测与追踪方法解析
一、算法结构与核心贡献 1.1 文章结构 采用经典五段式结构: 引言:分析智能交通系统(ITS)中小目标检测与追踪的挑战,提出研究动机。相关工作:综述小目标检测(YOLO系列、Faster R-CNN)、目标追踪(SORT、Transformer)和图神经网络(GNN)的进展。方法论:提出DG…...
在Ubuntu中固定USB设备的串口号
获取设备信息 lsusb # 记录设备的Vendor ID和Product ID(例如:ID 0403:6001)# 获取详细属性(替换X和Y为实际设备号) udevadm info -a /dev/ttyUSBX 结果一般如下 创建udev规则文件 sudo gedit /etc/udev/rules.d/us…...
javaSE————文件IO(2)、
文件内容的读写——数据流 我们对于文件操作使用流对象Stream来操作,什么是流对象呢,水流是什么样的,想象一下,水流的流量是多种的,可以流100ml,也可以流1ml,流对象就和水流很像,我…...
前端常问的宏观“大”问题详解(二)
JS与TS选型 一、为什么选择 TypeScript 而不是 JavaScript? 1. 静态类型系统:核心优势 TypeScript 的静态类型检查能在 编译阶段 捕获类型错误(如变量类型不匹配、未定义属性等),显著减少运行时错误风险。例如&…...
