From e0da79d745c03d3d23716fecfe21d3db173cdba8 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 13 Oct 2026 12:44:39 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=20MinIO=20=E9=A2=84?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E8=B5=B0=E5=A4=96=E7=BD=91=20HTTPS=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E8=BF=87=E6=BB=A4=E6=97=A0=E6=95=88=E5=BA=94=E6=94=B6?= =?UTF-8?q?=E5=BA=94=E4=BB=98=E5=8F=98=E6=9B=B4=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../springblade/file/config/FileConfig.java | 8 +- .../service/impl/MinioFileServiceImpl.java | 90 +++++++++++++++++-- .../ReceivablePayableDetailServiceImpl.java | 87 ++++++++++++++---- doc/nacos/blade-prod.yaml | 6 ++ org/springblade/core/tool/node/TreeNode.java | 72 --------------- 5 files changed, 166 insertions(+), 97 deletions(-) delete mode 100644 org/springblade/core/tool/node/TreeNode.java diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java index 64632b22..88d14991 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/config/FileConfig.java @@ -95,7 +95,13 @@ public class FileConfig { FileStorageProperties.StoreProperties mergedProperties = new FileStorageProperties.StoreProperties(); String fileStoragePrefix = "file.storage." + prefix; mergedProperties.setEndpoint(firstNotBlank(properties.getEndpoint(), environment.getProperty(fileStoragePrefix + ".endpoint"), environment.getProperty(prefix + ".endpoint"))); - mergedProperties.setTransformEndpoint(firstNotBlank(properties.getTransformEndpoint(), environment.getProperty(fileStoragePrefix + ".transform-endpoint"), environment.getProperty(prefix + ".transform-endpoint"))); + mergedProperties.setTransformEndpoint(firstNotBlank( + properties.getTransformEndpoint(), + environment.getProperty(fileStoragePrefix + ".transform-endpoint"), + environment.getProperty(prefix + ".transform-endpoint"), + // 兼容 blade-resource / oss 配置,避免仅改对象存储外网地址时分片上传仍走内网 HTTP + environment.getProperty("oss.transform-endpoint") + )); mergedProperties.setAccessKeyId(firstNotBlank(properties.getAccessKeyId(), environment.getProperty(fileStoragePrefix + ".access-key-id"), environment.getProperty(fileStoragePrefix + ".access-key"), environment.getProperty(prefix + ".access-key-id"), environment.getProperty(prefix + ".access-key"))); mergedProperties.setAccessKeySecret(firstNotBlank(properties.getAccessKeySecret(), environment.getProperty(fileStoragePrefix + ".access-key-secret"), environment.getProperty(fileStoragePrefix + ".secret-key"), environment.getProperty(prefix + ".access-key-secret"), environment.getProperty(prefix + ".secret-key"))); mergedProperties.setBucketName(firstNotBlank(properties.getBucketName(), environment.getProperty(fileStoragePrefix + ".bucket-name"), environment.getProperty(prefix + ".bucket-name"))); diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java index 5214c628..4c8f53a5 100644 --- a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java +++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/MinioFileServiceImpl.java @@ -25,11 +25,11 @@ */ package org.springblade.file.service.impl; +import io.minio.ComposeObjectArgs; +import io.minio.ComposeSource; import io.minio.GetObjectArgs; import io.minio.GetPresignedObjectUrlArgs; import io.minio.MinioClient; -import io.minio.ComposeObjectArgs; -import io.minio.ComposeSource; import io.minio.PutObjectArgs; import io.minio.RemoveObjectArgs; import io.minio.UploadObjectArgs; @@ -42,6 +42,7 @@ import org.springblade.file.config.FileStorageProperties; import java.io.File; import java.io.InputStream; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -64,6 +65,7 @@ public class MinioFileServiceImpl extends AbstractFileService { private final FileStorageProperties.StoreProperties properties; private final MinioClient minioClient; + private volatile MinioClient transformPresignClient; @Override public String initiateMultipartUpload(String objectKey) { @@ -73,9 +75,13 @@ public class MinioFileServiceImpl extends AbstractFileService { @Override public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) { try { - return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() - .method(Method.PUT).bucket(properties.getBucketName()).object(partObjectKey(objectKey, uploadId, partNumber)) - .expiry(Math.toIntExact(resolveExpires(expires))).build()); + String presignedUrl = resolvePresignClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.PUT) + .bucket(properties.getBucketName()) + .object(partObjectKey(objectKey, uploadId, partNumber)) + .expiry(Math.toIntExact(resolveExpires(expires))) + .build()); + return applyTransformPathPrefix(presignedUrl); } catch (Exception exception) { log.error("MinIO获取分片上传链接异常 objectKey:{}", objectKey, exception); throw new ServiceException("获取分片上传链接异常"); @@ -143,12 +149,13 @@ public class MinioFileServiceImpl extends AbstractFileService { return null; } try { - return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + String presignedUrl = resolvePresignClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(properties.getBucketName()) .object(resolveObjectKey(objectKey)) .expiry(Math.toIntExact(resolveExpires(expires))) .build()); + return applyTransformPathPrefix(presignedUrl); } catch (Exception exception) { log.error("MinIO获取文件链接异常 objectKey:{}", objectKey, exception); throw new ServiceException("MinIO获取文件链接异常"); @@ -170,6 +177,77 @@ public class MinioFileServiceImpl extends AbstractFileService { } } + /** + * 浏览器侧预签名优先使用外网地址,避免 HTTPS 页面访问 HTTP MinIO 被 Mixed Content 拦截。 + * MinIO SDK 不允许 endpoint 带 path,因此外网地址中的 /oss 等前缀在签名后单独拼回。 + */ + private MinioClient resolvePresignClient() { + String transformEndpoint = properties.getTransformEndpoint(); + if (StringUtil.isBlank(transformEndpoint)) { + return minioClient; + } + MinioClient cachedClient = transformPresignClient; + if (cachedClient != null) { + return cachedClient; + } + synchronized (this) { + if (transformPresignClient == null) { + transformPresignClient = MinioClient.builder() + .endpoint(resolveTransformOrigin(transformEndpoint)) + .credentials(properties.getAccessKeyId(), properties.getAccessKeySecret()) + .build(); + } + return transformPresignClient; + } + } + + private String resolveTransformOrigin(String transformEndpoint) { + URI transformUri = URI.create(transformEndpoint.trim()); + if (StringUtil.isBlank(transformUri.getScheme()) || StringUtil.isBlank(transformUri.getHost())) { + throw new ServiceException("MinIO外网地址配置不正确"); + } + StringBuilder origin = new StringBuilder(); + origin.append(transformUri.getScheme()).append("://").append(transformUri.getHost()); + if (transformUri.getPort() > 0) { + origin.append(':').append(transformUri.getPort()); + } + return origin.toString(); + } + + private String applyTransformPathPrefix(String presignedUrl) { + String transformEndpoint = properties.getTransformEndpoint(); + if (StringUtil.isBlank(transformEndpoint) || StringUtil.isBlank(presignedUrl)) { + return presignedUrl; + } + String pathPrefix = normalizePathPrefix(URI.create(transformEndpoint.trim()).getPath()); + if (StringUtil.isBlank(pathPrefix)) { + return presignedUrl; + } + URI url = URI.create(presignedUrl); + String path = url.getRawPath() == null ? "" : url.getRawPath(); + if (path.equals(pathPrefix) || path.startsWith(pathPrefix + "/")) { + return presignedUrl; + } + StringBuilder result = new StringBuilder(); + result.append(url.getScheme()).append("://").append(url.getHost()); + if (url.getPort() > 0) { + result.append(':').append(url.getPort()); + } + result.append(pathPrefix).append(path); + if (StringUtil.isNotBlank(url.getRawQuery())) { + result.append('?').append(url.getRawQuery()); + } + return result.toString(); + } + + private String normalizePathPrefix(String path) { + if (StringUtil.isBlank(path) || "/".equals(path)) { + return ""; + } + String normalized = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; + return normalized.startsWith("/") ? normalized : "/" + normalized; + } + private String resolveObjectKey(String objectKey) { if (StringUtil.isBlank(properties.getRootDirectory())) { return objectKey; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index 9edd897e..0ce145f2 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -432,13 +432,23 @@ public class ReceivablePayableDetailServiceImpl @Override public IPage changeRecords(IPage page, Long detailId) { - LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() - .eq(ReceivablePayableChangeRecord::getDetailId, detailId) - .eq(ReceivablePayableChangeRecord::getIsDeleted, 0) - .orderByDesc(ReceivablePayableChangeRecord::getAdjustTime); - IPage entityPage = changeRecordMapper.selectPage(new Page<>(page.getCurrent(), page.getSize()), wrapper); - Page voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); - voPage.setRecords(entityPage.getRecords().stream().map(record -> Objects.requireNonNull( + List meaningfulRecords = changeRecordMapper.selectList( + Wrappers.lambdaQuery() + .eq(ReceivablePayableChangeRecord::getDetailId, detailId) + .eq(ReceivablePayableChangeRecord::getIsDeleted, 0) + .orderByDesc(ReceivablePayableChangeRecord::getAdjustTime)) + .stream() + .filter(record -> hasMeaningfulChange(record.getChangeContent())) + .toList(); + long current = page.getCurrent(); + long size = page.getSize(); + int fromIndex = (int) Math.max(0L, (current - 1L) * size); + int toIndex = (int) Math.min(meaningfulRecords.size(), fromIndex + size); + List pageRecords = fromIndex >= meaningfulRecords.size() + ? List.of() + : meaningfulRecords.subList(fromIndex, toIndex); + Page voPage = new Page<>(current, size, meaningfulRecords.size()); + voPage.setRecords(pageRecords.stream().map(record -> Objects.requireNonNull( BeanUtil.copyProperties(record, ReceivablePayableChangeRecordVO.class))).toList()); return voPage; } @@ -613,14 +623,8 @@ public class ReceivablePayableDetailServiceImpl appendChange(changes, "运输单价", existing.getUnitPrice(), adjusted.getUnitPrice()); appendChange(changes, "里程", existing.getMileage(), mileage); appendChange(changes, "运输费", existing.getFreightAmount(), freightAmount); - if (!Objects.equals(existing.getRemark(), adjusted.getRemark())) { - changes.add("【备注】从[" + Objects.toString(existing.getRemark(), "") + "]调整为[" - + Objects.toString(adjusted.getRemark(), "") + "]"); - } - if (!Objects.equals(existing.getChangeReason(), adjusted.getChangeReason())) { - changes.add("【变更原因】从[" + Objects.toString(existing.getChangeReason(), "") + "]调整为[" - + Objects.toString(adjusted.getChangeReason(), "") + "]"); - } + appendChange(changes, "备注", existing.getRemark(), adjusted.getRemark()); + appendChange(changes, "变更原因", existing.getChangeReason(), adjusted.getChangeReason()); Map oldFeeItems = parseMap(existing.getFeeItemsJson()); for (String name : allowedFeeItems) { appendChange(changes, name, decimal(oldFeeItems.get(name)), money(effectiveFeeItems.get(name))); @@ -2367,12 +2371,56 @@ public class ReceivablePayableDetailServiceImpl } private void appendChange(List changes, String field, String before, String after) { - if (!Objects.equals(before, after)) { - changes.add("【" + field + "】从[" + Objects.toString(before, "") + "]调整为[" - + Objects.toString(after, "") + "]"); + String oldValue = normalizeChangeText(before); + String newValue = normalizeChangeText(after); + if (!Objects.equals(oldValue, newValue)) { + changes.add("【" + field + "】从[" + oldValue + "]调整为[" + newValue + "]"); } } + /** + * 变更文本归一化:null / 空白视为相同,避免出现「从[]调整为[]」无效记录 + */ + private String normalizeChangeText(String value) { + return value == null ? "" : value.trim(); + } + + /** + * 是否存在实际前后差异;整段内容均无变化时不展示 + */ + private boolean hasMeaningfulChange(String content) { + if (Func.isBlank(content)) { + return false; + } + String text = content.trim(); + int suffixStart = text.lastIndexOf('('); + if (suffixStart > 0 && text.endsWith(")")) { + text = text.substring(0, suffixStart); + } + boolean hasPart = false; + for (String part : text.split(";")) { + if (Func.isBlank(part)) { + continue; + } + hasPart = true; + if (isMeaningfulChangePart(part.trim())) { + return true; + } + } + return !hasPart; + } + + private boolean isMeaningfulChangePart(String part) { + int fromIdx = part.indexOf("从["); + int toIdx = part.indexOf("]调整为["); + if (fromIdx < 0 || toIdx < fromIdx || !part.endsWith("]")) { + return true; + } + String before = part.substring(fromIdx + 2, toIdx); + String after = part.substring(toIdx + 5, part.length() - 1); + return !Objects.equals(before, after); + } + private String formatValue(BigDecimal value) { return money(value).stripTrailingZeros().toPlainString(); } @@ -2617,6 +2665,9 @@ public class ReceivablePayableDetailServiceImpl } private void saveChangeRecord(ReceivablePayableDetail detail, String content, String reason, String lineNo) { + if (!hasMeaningfulChange(content)) { + return; + } ReceivablePayableChangeRecord record = new ReceivablePayableChangeRecord(); record.setDetailId(detail.getId()); record.setLineNo(lineNo); diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index ab3d3562..5aabf2ac 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -120,6 +120,12 @@ baidu: request-timeout: 30s token-refresh-advance: 1m +# 凭证分片上传等走 blade-file,外网地址需单独配置(与 /resource/oss 的 transformEndpoint 保持一致) +file: + storage: + minio: + transform-endpoint: ${MINIO_TRANSFORM_ENDPOINT:https://tms-prod.xinghy56.com/oss} + powerjob: worker: server-address: 172.16.203.228:7700 diff --git a/org/springblade/core/tool/node/TreeNode.java b/org/springblade/core/tool/node/TreeNode.java deleted file mode 100644 index 114aa0c0..00000000 --- a/org/springblade/core/tool/node/TreeNode.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * BladeX Commercial License Agreement - * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. - *

- * Use of this software is governed by the Commercial License Agreement - * obtained after purchasing a license from BladeX. - *

- * 1. This software is for development use only under a valid license - * from BladeX. - *

- * 2. Redistribution of this software's source code to any third party - * without a commercial license is strictly prohibited. - *

- * 3. Licensees may copyright their own code but cannot use segments - * from this software for such purposes. Copyright of this software - * remains with BladeX. - *

- * Using this software signifies agreement to this License, and the software - * must not be used for illegal purposes. - *

- * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is - * not liable for any claims arising from secondary or illegal development. - *

- * Author: Chill Zhuang (bladejava@qq.com) - */ -package org.springblade.core.tool.node; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import lombok.Data; -import org.springblade.core.tool.utils.Func; - -import java.io.Serial; -import java.util.Objects; - -/** - * 树型节点类 - * - * @author smallchill - */ -@Data -public class TreeNode extends BaseNode { - - @Serial - private static final long serialVersionUID = 1L; - - private String title; - - @JsonSerialize(using = ToStringSerializer.class) - private Long key; - - @JsonSerialize(using = ToStringSerializer.class) - private Long value; - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - TreeNode other = (TreeNode) obj; - return Func.equals(this.getId(), other.getId()); - } - - @Override - public int hashCode() { - return Objects.hash(id, parentId); - } - -}