feat: 云打印重试
This commit is contained in:
1 parent
2b15925482
commit
82d68f4a92
6 files changed
+258
-15
No files matched your search
+33
-3
@@ -15,20 +15,50 @@ import static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_OR
|
|||||||
public class PrintOrderRedisDAO {
|
public class PrintOrderRedisDAO {
|
||||||
|
|
||||||
private static final long TTL_HOURS = 24;
|
private static final long TTL_HOURS = 24;
|
||||||
|
private static final String LEGACY_PRINTED = "1";
|
||||||
|
private static final String PRINTING = "PRINTING";
|
||||||
|
private static final String PRINTED = "PRINTED";
|
||||||
|
private static final String FAILED = "FAILED";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true 表示首次标记成功,可以打印;false 表示已打印过
|
* @return true 表示首次标记成功,可以打印;false 表示已有打印任务
|
||||||
*/
|
*/
|
||||||
public boolean tryMarkPrinted(String orderId) {
|
public boolean tryStartPrinting(String orderId) {
|
||||||
String redisKey = formatKey(orderId);
|
String redisKey = formatKey(orderId);
|
||||||
Boolean ok = stringRedisTemplate.opsForValue()
|
Boolean ok = stringRedisTemplate.opsForValue()
|
||||||
.setIfAbsent(redisKey, "1", TTL_HOURS, TimeUnit.HOURS);
|
.setIfAbsent(redisKey, PRINTING, TTL_HOURS, TimeUnit.HOURS);
|
||||||
return Boolean.TRUE.equals(ok);
|
return Boolean.TRUE.equals(ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记云端已经成功接收打印订单。
|
||||||
|
*/
|
||||||
|
public void markPrinted(String orderId) {
|
||||||
|
setState(orderId, PRINTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记指定次数的自动重试已经全部失败。
|
||||||
|
*/
|
||||||
|
public void markFailed(String orderId) {
|
||||||
|
setState(orderId, FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兼容升级前值为 1 的打印幂等记录。
|
||||||
|
*/
|
||||||
|
public boolean isPrinted(String orderId) {
|
||||||
|
String state = stringRedisTemplate.opsForValue().get(formatKey(orderId));
|
||||||
|
return PRINTED.equals(state) || LEGACY_PRINTED.equals(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setState(String orderId, String state) {
|
||||||
|
stringRedisTemplate.opsForValue().set(formatKey(orderId), state, TTL_HOURS, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
|
||||||
private static String formatKey(String orderId) {
|
private static String formatKey(String orderId) {
|
||||||
return String.format(YSHOP_ORDER_PRINT_KEY.getKeyTemplate(), orderId);
|
return String.format(YSHOP_ORDER_PRINT_KEY.getKeyTemplate(), orderId);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -1,9 +1,23 @@
|
|||||||
package co.yixiang.yshop.module.order.print.xpyun;
|
package co.yixiang.yshop.module.order.print.xpyun;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@EnableConfigurationProperties(XpYunProperties.class)
|
@EnableConfigurationProperties(XpYunProperties.class)
|
||||||
public class XpYunConfiguration {
|
public class XpYunConfiguration {
|
||||||
|
|
||||||
|
public static final String PRINT_RETRY_TASK_SCHEDULER = "xpYunPrintRetryTaskScheduler";
|
||||||
|
|
||||||
|
@Bean(name = PRINT_RETRY_TASK_SCHEDULER, destroyMethod = "shutdown")
|
||||||
|
public ThreadPoolTaskScheduler xpYunPrintRetryTaskScheduler() {
|
||||||
|
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
|
||||||
|
taskScheduler.setPoolSize(4);
|
||||||
|
taskScheduler.setThreadNamePrefix("xpyun-print-retry-");
|
||||||
|
taskScheduler.setRemoveOnCancelPolicy(true);
|
||||||
|
taskScheduler.setWaitForTasksToCompleteOnShutdown(false);
|
||||||
|
return taskScheduler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package co.yixiang.yshop.module.order.print.xpyun;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static co.yixiang.yshop.module.order.print.xpyun.XpYunConfiguration.PRINT_RETRY_TASK_SCHEDULER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 芯烨云打印失败重试调度器。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class XpYunPrintRetryScheduler {
|
||||||
|
|
||||||
|
static final List<Duration> RETRY_DELAYS = List.of(
|
||||||
|
Duration.ofSeconds(30),
|
||||||
|
Duration.ofSeconds(60),
|
||||||
|
Duration.ofSeconds(120),
|
||||||
|
Duration.ofSeconds(180),
|
||||||
|
Duration.ofSeconds(300));
|
||||||
|
|
||||||
|
@Resource(name = PRINT_RETRY_TASK_SCHEDULER)
|
||||||
|
private TaskScheduler taskScheduler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param retryIndex 从 0 开始的重试序号
|
||||||
|
* @return true 表示已安排重试;false 表示已没有剩余重试次数
|
||||||
|
*/
|
||||||
|
public boolean schedule(String orderId, int retryIndex, Runnable retryTask) {
|
||||||
|
if (retryIndex < 0 || retryIndex >= RETRY_DELAYS.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Duration delay = RETRY_DELAYS.get(retryIndex);
|
||||||
|
Instant executeAt = Instant.now().plus(delay);
|
||||||
|
taskScheduler.schedule(retryTask, executeAt);
|
||||||
|
log.warn("[schedule][orderId({}) 第 {}/{} 次重试将在 {} 秒后执行]",
|
||||||
|
orderId, retryIndex + 1, RETRY_DELAYS.size(), delay.toSeconds());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int retryCount() {
|
||||||
|
return RETRY_DELAYS.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+89
-11
@@ -18,6 +18,7 @@ import co.yixiang.yshop.module.order.dal.redis.order.PrintOrderRedisDAO;
|
|||||||
import co.yixiang.yshop.module.order.enums.OrderLogEnum;
|
import co.yixiang.yshop.module.order.enums.OrderLogEnum;
|
||||||
import co.yixiang.yshop.module.order.print.xpyun.OrderReceiptBuilder;
|
import co.yixiang.yshop.module.order.print.xpyun.OrderReceiptBuilder;
|
||||||
import co.yixiang.yshop.module.order.print.xpyun.XpYunPrintClient;
|
import co.yixiang.yshop.module.order.print.xpyun.XpYunPrintClient;
|
||||||
|
import co.yixiang.yshop.module.order.print.xpyun.XpYunPrintRetryScheduler;
|
||||||
import co.yixiang.yshop.module.order.print.xpyun.XpYunPrintResponse;
|
import co.yixiang.yshop.module.order.print.xpyun.XpYunPrintResponse;
|
||||||
import co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto;
|
import co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto;
|
||||||
import co.yixiang.yshop.module.product.service.storeproduct.StoreProductService;
|
import co.yixiang.yshop.module.product.service.storeproduct.StoreProductService;
|
||||||
@@ -68,6 +69,8 @@ public class AsynStoreOrderServiceImpl implements AsyncStoreOrderService {
|
|||||||
private XpYunPrintClient xpYunPrintClient;
|
private XpYunPrintClient xpYunPrintClient;
|
||||||
@Resource
|
@Resource
|
||||||
private OrderReceiptBuilder orderReceiptBuilder;
|
private OrderReceiptBuilder orderReceiptBuilder;
|
||||||
|
@Resource
|
||||||
|
private XpYunPrintRetryScheduler xpYunPrintRetryScheduler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取某个用户的订单统计数据
|
* 获取某个用户的订单统计数据
|
||||||
@@ -248,7 +251,10 @@ public class AsynStoreOrderServiceImpl implements AsyncStoreOrderService {
|
|||||||
@Async
|
@Async
|
||||||
public void printOrderAsync(String orderId) {
|
public void printOrderAsync(String orderId) {
|
||||||
try {
|
try {
|
||||||
printOrder(orderId, false);
|
PrintAttemptResult result = printOrderInternal(orderId, false, true);
|
||||||
|
if (result == PrintAttemptResult.RETRYABLE_FAILURE) {
|
||||||
|
schedulePrintRetry(orderId, 0);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[printOrderAsync][orderId({}) 自动打印失败]", orderId, e);
|
log.error("[printOrderAsync][orderId({}) 自动打印失败]", orderId, e);
|
||||||
}
|
}
|
||||||
@@ -256,47 +262,56 @@ public class AsynStoreOrderServiceImpl implements AsyncStoreOrderService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void printOrder(String orderId, boolean force) {
|
public void printOrder(String orderId, boolean force) {
|
||||||
|
printOrderInternal(orderId, force, !force);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrintAttemptResult printOrderInternal(String orderId, boolean force, boolean firstAutomaticAttempt) {
|
||||||
if (StrUtil.isBlank(orderId)) {
|
if (StrUtil.isBlank(orderId)) {
|
||||||
if (force) {
|
if (force) {
|
||||||
throw exception(PARAM_ERROR);
|
throw exception(PARAM_ERROR);
|
||||||
}
|
}
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
if (!xpYunPrintClient.isReady()) {
|
if (!xpYunPrintClient.isReady()) {
|
||||||
failOrSkip(force, STORE_ORDER_PRINT_DISABLED, "芯烨云未启用或凭证未配置");
|
failOrSkip(force, STORE_ORDER_PRINT_DISABLED, "芯烨云未启用或凭证未配置");
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
StoreOrderDO order = storeOrderMapper.selectOne(new LambdaQueryWrapper<StoreOrderDO>()
|
StoreOrderDO order = storeOrderMapper.selectOne(new LambdaQueryWrapper<StoreOrderDO>()
|
||||||
.eq(StoreOrderDO::getOrderId, orderId));
|
.eq(StoreOrderDO::getOrderId, orderId));
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
failOrSkip(force, STORE_ORDER_NOT_EXISTS, "订单不存在");
|
failOrSkip(force, STORE_ORDER_NOT_EXISTS, "订单不存在");
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
if (!OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getPaid())) {
|
if (!OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getPaid())) {
|
||||||
failOrSkip(force, STORE_ORDER_PRINT_NOT_PAID, "订单未支付");
|
failOrSkip(force, STORE_ORDER_PRINT_NOT_PAID, "订单未支付");
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
String orderType = order.getOrderType();
|
String orderType = order.getOrderType();
|
||||||
if (!OrderLogEnum.ORDER_TAKE_IN.getValue().equals(orderType)
|
if (!OrderLogEnum.ORDER_TAKE_IN.getValue().equals(orderType)
|
||||||
&& !OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(orderType)) {
|
&& !OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(orderType)) {
|
||||||
failOrSkip(force, STORE_ORDER_PRINT_TYPE_NOT_SUPPORT, "订单类型不支持打印:" + orderType);
|
failOrSkip(force, STORE_ORDER_PRINT_TYPE_NOT_SUPPORT, "订单类型不支持打印:" + orderType);
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
if (order.getShopId() == null) {
|
if (order.getShopId() == null) {
|
||||||
failOrSkip(force, STORE_ORDER_PRINT_SN_EMPTY, "订单无门店");
|
failOrSkip(force, STORE_ORDER_PRINT_SN_EMPTY, "订单无门店");
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
StoreShopDO shop = storeShopMapper.selectById(order.getShopId());
|
StoreShopDO shop = storeShopMapper.selectById(order.getShopId());
|
||||||
if (shop == null || StrUtil.isBlank(shop.getUniprintId())) {
|
if (shop == null || StrUtil.isBlank(shop.getUniprintId())) {
|
||||||
failOrSkip(force, STORE_ORDER_PRINT_SN_EMPTY, "门店未配置打印机SN");
|
failOrSkip(force, STORE_ORDER_PRINT_SN_EMPTY, "门店未配置打印机SN");
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验通过后再做幂等,避免空 SN / 未启用误占 key
|
// 校验通过后再做幂等,避免空 SN / 未启用误占 key
|
||||||
if (!force && !printOrderRedisDAO.tryMarkPrinted(orderId)) {
|
if (!force && firstAutomaticAttempt && !printOrderRedisDAO.tryStartPrinting(orderId)) {
|
||||||
log.info("[printOrder][orderId({}) 已打印过,跳过]", orderId);
|
log.info("[printOrder][orderId({}) 已存在打印任务,跳过]", orderId);
|
||||||
return;
|
return PrintAttemptResult.STOPPED;
|
||||||
|
}
|
||||||
|
// 人工补打成功后会写入 PRINTED,尚未执行的自动重试应立即停止。
|
||||||
|
if (!force && !firstAutomaticAttempt && printOrderRedisDAO.isPrinted(orderId)) {
|
||||||
|
log.info("[printOrder][orderId({}) 已打印成功,停止重试]", orderId);
|
||||||
|
return PrintAttemptResult.STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<StoreOrderCartInfoDO> items = storeOrderCartInfoMapper.selectList(
|
List<StoreOrderCartInfoDO> items = storeOrderCartInfoMapper.selectList(
|
||||||
@@ -312,8 +327,11 @@ public class AsynStoreOrderServiceImpl implements AsyncStoreOrderService {
|
|||||||
throw exception(STORE_ORDER_PRINT_FAILED, msg);
|
throw exception(STORE_ORDER_PRINT_FAILED, msg);
|
||||||
}
|
}
|
||||||
log.error("[printOrder][orderId({}) 打印失败] {}", orderId, msg);
|
log.error("[printOrder][orderId({}) 打印失败] {}", orderId, msg);
|
||||||
|
return PrintAttemptResult.RETRYABLE_FAILURE;
|
||||||
} else {
|
} else {
|
||||||
log.info("[printOrder][orderId({}) 打印成功] data={}", orderId, resp.getData());
|
log.info("[printOrder][orderId({}) 打印成功] data={}", orderId, resp.getData());
|
||||||
|
markPrintedQuietly(orderId);
|
||||||
|
return PrintAttemptResult.SUCCESS;
|
||||||
}
|
}
|
||||||
} catch (co.yixiang.yshop.framework.common.exception.ServiceException e) {
|
} catch (co.yixiang.yshop.framework.common.exception.ServiceException e) {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -322,9 +340,69 @@ public class AsynStoreOrderServiceImpl implements AsyncStoreOrderService {
|
|||||||
throw exception(STORE_ORDER_PRINT_FAILED, e.getMessage());
|
throw exception(STORE_ORDER_PRINT_FAILED, e.getMessage());
|
||||||
}
|
}
|
||||||
log.error("[printOrder][orderId({}) 打印异常]", orderId, e);
|
log.error("[printOrder][orderId({}) 打印异常]", orderId, e);
|
||||||
|
return PrintAttemptResult.RETRYABLE_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void schedulePrintRetry(String orderId, int retryIndex) {
|
||||||
|
try {
|
||||||
|
boolean scheduled = xpYunPrintRetryScheduler.schedule(orderId, retryIndex,
|
||||||
|
() -> executePrintRetry(orderId, retryIndex));
|
||||||
|
if (!scheduled) {
|
||||||
|
finishPrintRetries(orderId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[schedulePrintRetry][orderId({}) 安排打印重试失败]", orderId, e);
|
||||||
|
markFailedQuietly(orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executePrintRetry(String orderId, int retryIndex) {
|
||||||
|
try {
|
||||||
|
PrintAttemptResult result = printOrderInternal(orderId, false, false);
|
||||||
|
if (result == PrintAttemptResult.RETRYABLE_FAILURE) {
|
||||||
|
if (retryIndex + 1 < xpYunPrintRetryScheduler.retryCount()) {
|
||||||
|
schedulePrintRetry(orderId, retryIndex + 1);
|
||||||
|
} else {
|
||||||
|
finishPrintRetries(orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[executePrintRetry][orderId({}) 第 {} 次打印重试异常,停止后续重试]",
|
||||||
|
orderId, retryIndex + 1, e);
|
||||||
|
markFailedQuietly(orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishPrintRetries(String orderId) {
|
||||||
|
markFailedQuietly(orderId);
|
||||||
|
log.error("[finishPrintRetries][orderId({}) 打印初次请求及 {} 次重试均失败,不再重试]",
|
||||||
|
orderId, xpYunPrintRetryScheduler.retryCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markPrintedQuietly(String orderId) {
|
||||||
|
try {
|
||||||
|
printOrderRedisDAO.markPrinted(orderId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 云端已成功接单,Redis 状态写入失败不能触发再次打印,否则可能重复出票。
|
||||||
|
log.error("[markPrintedQuietly][orderId({}) 写入打印成功状态失败]", orderId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markFailedQuietly(String orderId) {
|
||||||
|
try {
|
||||||
|
printOrderRedisDAO.markFailed(orderId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[markFailedQuietly][orderId({}) 写入打印失败状态失败]", orderId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum PrintAttemptResult {
|
||||||
|
SUCCESS,
|
||||||
|
RETRYABLE_FAILURE,
|
||||||
|
STOPPED
|
||||||
|
}
|
||||||
|
|
||||||
private void failOrSkip(boolean force,
|
private void failOrSkip(boolean force,
|
||||||
co.yixiang.yshop.framework.common.exception.ErrorCode errorCode,
|
co.yixiang.yshop.framework.common.exception.ErrorCode errorCode,
|
||||||
String logMsg) {
|
String logMsg) {
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ public interface AsyncStoreOrderService {
|
|||||||
void getOrderTimeData();
|
void getOrderTimeData();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付成功后异步打印小票(失败只记日志,不抛异常)
|
* 支付成功后异步打印小票(瞬时故障按 30/60/120/180/300 秒重试)
|
||||||
*
|
*
|
||||||
* @param orderId 业务订单号
|
* @param orderId 业务订单号
|
||||||
*/
|
*/
|
||||||
|
|||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package co.yixiang.yshop.module.order.print.xpyun;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class XpYunPrintRetrySchedulerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TaskScheduler taskScheduler;
|
||||||
|
|
||||||
|
private XpYunPrintRetryScheduler retryScheduler;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws ReflectiveOperationException {
|
||||||
|
retryScheduler = new XpYunPrintRetryScheduler();
|
||||||
|
Field field = XpYunPrintRetryScheduler.class.getDeclaredField("taskScheduler");
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(retryScheduler, taskScheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseConfiguredRetryDelays() {
|
||||||
|
assertEquals(List.of(
|
||||||
|
Duration.ofSeconds(30),
|
||||||
|
Duration.ofSeconds(60),
|
||||||
|
Duration.ofSeconds(120),
|
||||||
|
Duration.ofSeconds(180),
|
||||||
|
Duration.ofSeconds(300)), XpYunPrintRetryScheduler.RETRY_DELAYS);
|
||||||
|
assertEquals(5, retryScheduler.retryCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldScheduleRetryAtConfiguredTime() {
|
||||||
|
int retryIndex = 2;
|
||||||
|
Instant before = Instant.now();
|
||||||
|
|
||||||
|
assertTrue(retryScheduler.schedule("order-1", retryIndex, () -> { }));
|
||||||
|
|
||||||
|
ArgumentCaptor<Instant> executeAtCaptor = ArgumentCaptor.forClass(Instant.class);
|
||||||
|
verify(taskScheduler).schedule(any(Runnable.class), executeAtCaptor.capture());
|
||||||
|
Duration actualDelay = Duration.between(before, executeAtCaptor.getValue());
|
||||||
|
assertTrue(actualDelay.compareTo(Duration.ofSeconds(120)) >= 0);
|
||||||
|
assertTrue(actualDelay.compareTo(Duration.ofSeconds(121)) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldStopAfterConfiguredRetries() {
|
||||||
|
assertFalse(retryScheduler.schedule("order-1", retryScheduler.retryCount(), () -> { }));
|
||||||
|
verify(taskScheduler, never()).schedule(any(Runnable.class), any(Instant.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user