From 5adf07b766ba0256531273883ea710dcfeee17c8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 26 Feb 2026 22:18:39 +0800 Subject: [PATCH 1/5] feat: add validation for website parameter in TenantController and AppTenantController --- .../system/controller/admin/tenant/TenantController.java | 6 +++++- .../system/controller/app/tenant/AppTenantController.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java index c15c5fa349..fba522ae6b 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/TenantController.java @@ -20,7 +20,9 @@ import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -33,6 +35,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @Tag(name = "管理后台 - 租户") @RestController @RequestMapping("/system/tenant") +@Validated public class TenantController { @Resource @@ -63,7 +66,8 @@ public class TenantController { @TenantIgnore @Operation(summary = "使用域名,获得租户信息", description = "登录界面,根据用户的域名,获得租户信息") @Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn") - public CommonResult getTenantByWebsite(@RequestParam("website") String website) { + public CommonResult getTenantByWebsite( + @RequestParam("website") @Pattern(regexp = "^[a-zA-Z0-9.-]+$", message = "网站域名格式不正确") String website) { TenantDO tenant = tenantService.getTenantByWebsite(website); if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) { return success(null); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/app/tenant/AppTenantController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/app/tenant/AppTenantController.java index 4655da1d98..022037d875 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/app/tenant/AppTenantController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/app/tenant/AppTenantController.java @@ -12,6 +12,8 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; +import jakarta.validation.constraints.Pattern; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -22,6 +24,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "用户 App - 租户") @RestController @RequestMapping("/system/tenant") +@Validated public class AppTenantController { @Resource @@ -32,7 +35,8 @@ public class AppTenantController { @TenantIgnore @Operation(summary = "使用域名,获得租户信息", description = "根据用户的域名,获得租户信息") @Parameter(name = "website", description = "域名", required = true, example = "www.iocoder.cn") - public CommonResult getTenantByWebsite(@RequestParam("website") String website) { + public CommonResult getTenantByWebsite( + @RequestParam("website") @Pattern(regexp = "^[a-zA-Z0-9.-]+$", message = "网站域名格式不正确") String website) { TenantDO tenant = tenantService.getTenantByWebsite(website); if (tenant == null || CommonStatusEnum.isDisable(tenant.getStatus())) { return success(null); From ba6cdabe62768f71b7bbccac58869c3423d346b3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 7 Mar 2026 18:01:11 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix(bpm):=20=E4=B8=B2=E8=A1=8C=E5=A4=9A?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E5=AE=A1=E6=89=B9=E4=BD=BF=E7=94=A8=20Linked?= =?UTF-8?q?HashSet=20=E4=BF=9D=E8=AF=81=E5=AE=A1=E6=89=B9=E4=BA=BA?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/behavior/BpmSequentialMultiInstanceBehavior.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index 8848f81836..65e6b29172 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java @@ -14,6 +14,7 @@ import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; import org.flowable.engine.impl.persistence.entity.ExecutionEntity; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -53,7 +54,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB @SuppressWarnings("unchecked") Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class); if (assigneeUserIds == null) { - assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsersByTask(execution)); if (CollUtil.isEmpty(assigneeUserIds)) { // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 From c88f33c919666907020240a1dd3346810101e226 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 7 Mar 2026 19:24:10 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix(infra):=20=E4=BF=AE=E5=A4=8D=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=E5=8C=85=E5=90=AB=20+=20=E5=8F=B7=E6=97=B6?= =?UTF-8?q?=E9=A2=84=E7=AD=BE=E5=90=8D=20URL=20=E8=A7=A3=E7=A0=81=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/util/http/HttpUtils.java | 18 +++++++++++++++++- .../file/core/client/s3/S3FileClient.java | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java index 10744d76bc..9de0377582 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/http/HttpUtils.java @@ -37,8 +37,10 @@ public class HttpUtils { } /** - * 解码 URL 参数 + * 解码 URL 参数(query parameter) + * 注意:此方法会将 + 解码为空格,适用于 query parameter,不适用于 URL path * + * @see #decodeUrlPath(String) * @param value 参数 * @return 解码后的参数 */ @@ -46,6 +48,20 @@ public class HttpUtils { return URLDecoder.decode(value, StandardCharsets.UTF_8); } + /** + * 解码 URL 路径 + * 与 {@link #decodeUtf8(String)} 不同,此方法不会将 + 解码为空格,保持 + 为字面字符 + * 适用于 URL path 部分的解码 + * + * @param path URL 路径 + * @return 解码后的路径 + */ + public static String decodeUrlPath(String path) { + // 先将 + 替换为 %2B,避免被 URLDecoder 解码为空格 + String encoded = path.replace("+", "%2B"); + return URLDecoder.decode(encoded, StandardCharsets.UTF_8); + } + @SuppressWarnings("unchecked") public static String replaceUrlQuery(String url, String key, String value) { UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java index 74a9361f99..dede821525 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java +++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java @@ -116,7 +116,7 @@ public class S3FileClient extends AbstractFileClient { public String presignGetUrl(String url, Integer expirationSeconds) { // 1. 将 url 转换为 path String path = StrUtil.removePrefix(url, config.getDomain() + "/"); - path = HttpUtils.decodeUtf8(HttpUtils.removeUrlQuery(path)); + path = HttpUtils.decodeUrlPath(HttpUtils.removeUrlQuery(path)); // 2.1 情况一:公开访问:无需签名 // 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名 From 67426d72f4b59347475b41b0dca434481faf6c19 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 Mar 2026 09:49:39 +0800 Subject: [PATCH 4/5] =?UTF-8?q?refactor(ip):=20=E4=BD=BF=E7=94=A8=20static?= =?UTF-8?q?=20=E5=9D=97=E4=BC=98=E5=8C=96=20IPUtils=20=E5=92=8C=20AreaUtil?= =?UTF-8?q?s=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/ip/core/utils/AreaUtils.java | 61 ++++++++++--------- .../framework/ip/core/utils/IPUtils.java | 22 +++---- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java index 8e27a31acb..413167283a 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.ip.core.Area; import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; import lombok.NonNull; +import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; @@ -25,44 +26,46 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. * @author 芋道源码 */ @Slf4j +@UtilityClass public class AreaUtils { - /** - * 初始化 SEARCHER - */ - @SuppressWarnings("InstantiationOfUtilityClass") - private final static AreaUtils INSTANCE = new AreaUtils(); - /** * Area 内存缓存,提升访问速度 */ private static Map areas; - private AreaUtils() { - long now = System.currentTimeMillis(); - areas = new HashMap<>(); - areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0, - null, new ArrayList<>())); - // 从 csv 中加载数据 - List rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows(); - rows.remove(0); // 删除 header - for (CsvRow row : rows) { - // 创建 Area 对象 - Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)), - null, new ArrayList<>()); - // 添加到 areas 中 - areas.put(area.getId(), area); - } + static { + init(); + } - // 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取 - for (CsvRow row : rows) { - Area area = areas.get(Integer.valueOf(row.get(0))); // 自己 - Area parent = areas.get(Integer.valueOf(row.get(3))); // 父 - Assert.isTrue(area != parent, "{}:父子节点相同", area.getName()); - area.setParent(parent); - parent.getChildren().add(area); + /** + * 初始化 + */ + private static void init() { + try { + long now = System.currentTimeMillis(); + areas = new HashMap<>(); + areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0, null, new ArrayList<>())); + // 从 csv 中加载数据 + List rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows(); + rows.remove(0); // 删除 header + for (CsvRow row : rows) { + Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)), null, new ArrayList<>()); + areas.put(area.getId(), area); + } + + // 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取 + for (CsvRow row : rows) { + Area area = areas.get(Integer.valueOf(row.get(0))); // 自己 + Area parent = areas.get(Integer.valueOf(row.get(3))); // 父 + Assert.isTrue(area != parent, "{}:父子节点相同", area.getName()); + area.setParent(parent); + parent.getChildren().add(area); + } + log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now); + } catch (Exception e) { + throw new RuntimeException("AreaUtils 初始化失败", e); } - log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now); } /** diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java index f74f848641..f616414e94 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java @@ -3,11 +3,10 @@ package cn.iocoder.yudao.framework.ip.core.utils; import cn.hutool.core.io.resource.ResourceUtil; import cn.iocoder.yudao.framework.ip.core.Area; import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.lionsoul.ip2region.xdb.Searcher; -import java.io.IOException; - /** * IP 工具类 * @@ -16,30 +15,29 @@ import java.io.IOException; * @author wanglhup */ @Slf4j +@UtilityClass public class IPUtils { - /** - * 初始化 SEARCHER - */ - @SuppressWarnings("InstantiationOfUtilityClass") - private final static IPUtils INSTANCE = new IPUtils(); - /** * IP 查询器,启动加载到内存中 */ private static Searcher SEARCHER; + static { + init(); + } + /** - * 私有化构造 + * 初始化 */ - private IPUtils() { + private static void init() { try { long now = System.currentTimeMillis(); byte[] bytes = ResourceUtil.readBytes("ip2region.xdb"); SEARCHER = Searcher.newWithBuffer(bytes); log.info("启动加载 IPUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now); - } catch (IOException e) { - log.error("启动加载 IPUtils 失败", e); + } catch (Exception e) { + throw new RuntimeException("IPUtils 初始化失败", e); } } From d844f06f5c2fb305a28c117f1a0721123426b937 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 Mar 2026 09:54:55 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(trade):=20=E4=BF=AE=E5=A4=8D=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E9=A1=B9=E4=BB=B7=E6=A0=BC=E8=AE=A1=E7=AE=97=E9=80=BB?= =?UTF-8?q?=E8=BE=91,=E5=88=86=E6=91=8A=E6=AF=94=E4=BE=8B=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=88=90=E7=94=A8=E5=AD=90=E8=AE=A2=E5=8D=95=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E9=87=91=E9=A2=9D=E6=9D=A5=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/trade/dal/mysql/aftersale/AfterSaleLogMapper.java | 2 +- .../trade/framework/aftersale/core/aop/AfterSaleLogAspect.java | 2 +- .../service/price/calculator/TradePriceCalculatorHelper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleLogMapper.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleLogMapper.java index d5453c946f..22d5f40d72 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleLogMapper.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleLogMapper.java @@ -13,7 +13,7 @@ public interface AfterSaleLogMapper extends BaseMapperX { default List selectListByAfterSaleId(Long afterSaleId) { return selectList(new LambdaQueryWrapper() .eq(AfterSaleLogDO::getAfterSaleId, afterSaleId) - .orderByDesc(AfterSaleLogDO::getCreateTime)); + .orderByDesc(AfterSaleLogDO::getId)); } } diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java index ba443e8a17..c484a292c8 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java @@ -79,7 +79,7 @@ public class AfterSaleLogAspect { Integer beforeStatus = BEFORE_STATUS.get(); Integer afterStatus = AFTER_STATUS.get(); Map exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap()); - String content = StrUtil.format(afterSaleLog.operateType().getContent(), exts); + String content = StrUtil.format(operateType.getContent(), exts); // 2. 记录日志 AfterSaleLogCreateReqBO createBO = new AfterSaleLogCreateReqBO() diff --git a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java index 0b24e2ea03..6585c9da21 100644 --- a/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java +++ b/yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradePriceCalculatorHelper.java @@ -258,7 +258,7 @@ public class TradePriceCalculatorHelper { TradeOrderItemDO orderItem = items.get(i); int partPrice; if (i < items.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减 - partPrice = (int) (price * (1.0D * orderItem.getPrice() / total)); + partPrice = (int) (price * (1.0D * orderItem.getPayPrice() / total)); remainPrice -= partPrice; } else { partPrice = remainPrice;