master
YunaiV 2026-05-10 10:11:37 +08:00
commit 32c353c53d
9 changed files with 98 additions and 54 deletions

View File

@ -64,12 +64,10 @@ public class HttpUtils {
return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name()); return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name());
} }
@SuppressWarnings("unchecked")
public static String replaceUrlQuery(String url, String key, String value) { public static String replaceUrlQuery(String url, String key, String value) {
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
// 先移除 // 先移除;再添加
builder.getQuery().remove(key); builder.getQuery().remove(key);
// 后添加
builder.addQuery(key, value); builder.addQuery(key, value);
return builder.build(); return builder.build();
} }

View File

@ -1,50 +1,52 @@
package cn.iocoder.yudao.framework.common.util.http; package cn.iocoder.yudao.framework.common.util.http;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link HttpUtils}
*/
public class HttpUtilsTest { public class HttpUtilsTest {
@Test @Test
public void testReplaceUrlQuery() { public void testReplaceUrlQuery_replace() {
// 定义测试用例:{原始URL, Key, Value, 期望结果} // 准备参数
String[][] testCases = { String url = "https://www.iocoder.cn/path?a=1&b=2";
// 场景1: 替换已存在的参数 (注意参数顺序可能会变,因为 UrlQuery 内部是 List) // 调用
{"https://example.com/path?a=1&b=2", "a", "3", "https://example.com/path?b=2&a=3"}, String result = HttpUtils.replaceUrlQuery(url, "a", "3");
// 场景2: 添加不存在的参数 // 断言:被替换的 key 会移到末尾,原顺序的其它参数保留
{"https://example.com/path?a=1", "b", "2", "https://example.com/path?a=1&b=2"}, assertEquals("https://www.iocoder.cn/path?b=2&a=3", result);
// 场景3: URL 本身没有查询参数
{"https://example.com/path", "a", "1", "https://example.com/path?a=1"},
// 场景4: 值为空 (根据原逻辑,空值通常会被移除或不添加,这里假设是移除)
// 注意:你需要根据 HttpUtils 实际对 null/empty 的处理来调整 expected
{"https://example.com/path?a=1", "a", "", "https://example.com/path?a="},
};
System.out.println("开始运行 HttpUtils.replaceUrlQuery 测试...");
for (int i = 0; i < testCases.length; i++) {
String[] currentCase = testCases[i]; // 必须先取出当前这一行的数组
String url = currentCase[0];
String key = currentCase[1];
String value = currentCase[2];
String expected = currentCase[3];
// 调用你优化后的方法
String actual = HttpUtils.replaceUrlQuery(url, key, value);
// 核心验证:断言实际结果必须等于期望结果
// 如果不相等,测试会直接报错,并打印出是哪一行错了
try {
assertEquals(expected, actual, "测试用例 " + (i + 1) + " 失败: " + url);
System.out.println("✅ 用例 " + (i + 1) + " 通过: " + actual);
} catch (AssertionError e) {
System.err.println("❌ 用例 " + (i + 1) + " 失败!");
System.err.println(" 输入: " + url + " | " + key + "=" + value);
System.err.println(" 期望: " + expected);
System.err.println(" 实际: " + actual);
throw e; // 抛出错误,让测试标记为失败
}
}
} }
@Test
public void testReplaceUrlQuery_add() {
// 准备参数
String url = "https://www.iocoder.cn/path?a=1";
// 调用
String result = HttpUtils.replaceUrlQuery(url, "b", "2");
// 断言
assertEquals("https://www.iocoder.cn/path?a=1&b=2", result);
}
@Test
public void testReplaceUrlQuery_noQuery() {
// 准备参数:原 URL 没有 query
String url = "https://www.iocoder.cn/path";
// 调用
String result = HttpUtils.replaceUrlQuery(url, "a", "1");
// 断言
assertEquals("https://www.iocoder.cn/path?a=1", result);
}
@Test
public void testReplaceUrlQuery_emptyValue() {
// 准备参数value 为空字符串
String url = "https://www.iocoder.cn/path?a=1";
// 调用
String result = HttpUtils.replaceUrlQuery(url, "a", "");
// 断言:保留 keyvalue 为空
assertEquals("https://www.iocoder.cn/path?a=", result);
}
} }

View File

@ -41,7 +41,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) { public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) {
// 校验关联数据是否存在 // 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds()); sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
adminUserApi.validateUserList(createReqVO.getReceiveUserIds()); adminUserApi.validateUserList(createReqVO.getReceiveUserIds()).checkError();
// 插入 // 插入
IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class); IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class);
@ -55,7 +55,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
validateAlertConfigExists(updateReqVO.getId()); validateAlertConfigExists(updateReqVO.getId());
// 校验关联数据是否存在 // 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds()); sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds()); adminUserApi.validateUserList(updateReqVO.getReceiveUserIds()).checkError();
// 更新 // 更新
IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class); IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class);

View File

@ -102,7 +102,7 @@ public class IotAlertTriggerSceneRuleAction implements IotSceneRuleAction {
break; break;
case NOTIFY: case NOTIFY:
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO().setUserId(userId) notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO().setUserId(userId)
.setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams)); .setTemplateCode(typeEnum.getTemplateCode()).setTemplateParams(templateParams)).checkError();
break; break;
} }
} catch (Exception ex) { } catch (Exception ex) {

View File

@ -14,7 +14,6 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
// TODO huihui单测需要补充
/** /**
* {@link TradePriceCalculator} * {@link TradePriceCalculator}
* *
@ -29,12 +28,12 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
@Override @Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1. 判断订单类型和是否具有拼团记录编号 // 1. 判断订单类型和是否具有砍价记录编号
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.BARGAIN.getType())) { if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.BARGAIN.getType())) {
return; return;
} }
Assert.isTrue(param.getItems().size() == 1, "砍价时,只允许选择一个商品"); Assert.isTrue(param.getItems().size() == 1, "砍价时,只允许选择一个商品");
Assert.isTrue(param.getItems().get(0).getCount() == 1, "砍价时,只允许选择一个商品"); Assert.isTrue(param.getItems().get(0).getCount() == 1, "砍价时,商品数量只允许为 1");
// 2. 校验是否可以参与砍价 // 2. 校验是否可以参与砍价
TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0); TradePriceCalculateRespBO.OrderItem orderItem = result.getItems().get(0);
BargainValidateJoinRespDTO bargainActivity = bargainRecordApi.validateJoinBargain( BargainValidateJoinRespDTO bargainActivity = bargainRecordApi.validateJoinBargain(
@ -44,7 +43,7 @@ public class TradeBargainActivityPriceCalculator implements TradePriceCalculator
Integer discountPrice = orderItem.getPayPrice() - bargainActivity.getBargainPrice() * orderItem.getCount(); Integer discountPrice = orderItem.getPayPrice() - bargainActivity.getBargainPrice() * orderItem.getCount();
// TODO 芋艿:极端情况,优惠金额为负数,需要处理 // TODO 芋艿:极端情况,优惠金额为负数,需要处理
TradePriceCalculatorHelper.addPromotion(result, orderItem, TradePriceCalculatorHelper.addPromotion(result, orderItem,
param.getSeckillActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(), bargainActivity.getActivityId(), bargainActivity.getName(), PromotionTypeEnum.BARGAIN_ACTIVITY.getType(),
StrUtil.format("砍价活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)), StrUtil.format("砍价活动:省 {} 元", TradePriceCalculatorHelper.formatPrice(discountPrice)),
discountPrice); discountPrice);
// 3.2 更新 SKU 优惠金额 // 3.2 更新 SKU 优惠金额

View File

@ -213,7 +213,7 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
} }
double totalChargeValue = getTotalChargeValue(orderItems, chargeMode); double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems); double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
return totalChargeValue <= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice(); return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
} }
private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) { private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.service.price.calculator; package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
@ -61,7 +62,7 @@ public class TradePriceCalculatorHelper {
// spu 信息 // spu 信息
orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId()) orderItem.setSpuName(spu.getName()).setCategoryId(spu.getCategoryId())
.setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId()) .setDeliveryTypes(spu.getDeliveryTypes()).setDeliveryTemplateId(spu.getDeliveryTemplateId())
.setGivePoint(spu.getGiveIntegral()).setUsePoint(0); .setGivePoint(ObjectUtil.defaultIfNull(spu.getGiveIntegral(), 0)).setUsePoint(0);
if (StrUtil.isBlank(orderItem.getPicUrl())) { if (StrUtil.isBlank(orderItem.getPicUrl())) {
orderItem.setPicUrl(spu.getPicUrl()); orderItem.setPicUrl(spu.getPicUrl());
} }

View File

@ -217,7 +217,7 @@ public class MesQcIndicatorResultServiceImpl implements MesQcIndicatorResultServ
if (Objects.equals(resultType, MesQcResultValueTypeEnum.DICT.getType())) { if (Objects.equals(resultType, MesQcResultValueTypeEnum.DICT.getType())) {
String dictType = indicator.getResultSpecification(); String dictType = indicator.getResultSpecification();
if (StrUtil.isNotBlank(dictType)) { if (StrUtil.isNotBlank(dictType)) {
dictDataApi.validateDictDataList(dictType, Collections.singleton(item.getValue())); dictDataApi.validateDictDataList(dictType, Collections.singleton(item.getValue())).checkError();
} }
} }
if (Objects.equals(resultType, MesQcResultValueTypeEnum.FILE.getType()) if (Objects.equals(resultType, MesQcResultValueTypeEnum.FILE.getType())

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.system.job.token;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* N Job
*
* @author preschooler
*/
@Component
@Slf4j
public class TokenCleanJob {
@Resource
private OAuth2TokenService oauth2TokenService;
/**
* 14
*/
private static final Integer JOB_CLEAN_RETAIN_DAY = 14;
/**
*
*/
private static final Integer DELETE_LIMIT = 100;
@XxlJob("tokenCleanJob")
@TenantIgnore
public void execute() {
Integer refreshCount = oauth2TokenService.cleanRefreshToken(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);
log.info("[execute][定时执行清理刷新令牌数量 ({}) 个]", refreshCount);
Integer accessCount = oauth2TokenService.cleanAccessToken(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);
log.info("[execute][定时执行清理访问令牌数量 ({}) 个]", accessCount);
XxlJobHelper.handleSuccess(
String.format("定时执行清理刷新令牌数量 %s 个,访问令牌数量 %s 个", refreshCount, accessCount));
}
}