🐛 修复 MinIO 预签名走外网 HTTPS,并过滤无效应收应付变更记录
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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")));
|
||||
|
||||
+84
-6
@@ -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;
|
||||
|
||||
+69
-18
@@ -432,13 +432,23 @@ public class ReceivablePayableDetailServiceImpl
|
||||
|
||||
@Override
|
||||
public IPage<ReceivablePayableChangeRecordVO> changeRecords(IPage<?> page, Long detailId) {
|
||||
LambdaQueryWrapper<ReceivablePayableChangeRecord> wrapper = Wrappers.<ReceivablePayableChangeRecord>lambdaQuery()
|
||||
.eq(ReceivablePayableChangeRecord::getDetailId, detailId)
|
||||
.eq(ReceivablePayableChangeRecord::getIsDeleted, 0)
|
||||
.orderByDesc(ReceivablePayableChangeRecord::getAdjustTime);
|
||||
IPage<ReceivablePayableChangeRecord> entityPage = changeRecordMapper.selectPage(new Page<>(page.getCurrent(), page.getSize()), wrapper);
|
||||
Page<ReceivablePayableChangeRecordVO> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
|
||||
voPage.setRecords(entityPage.getRecords().stream().map(record -> Objects.requireNonNull(
|
||||
List<ReceivablePayableChangeRecord> meaningfulRecords = changeRecordMapper.selectList(
|
||||
Wrappers.<ReceivablePayableChangeRecord>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<ReceivablePayableChangeRecord> pageRecords = fromIndex >= meaningfulRecords.size()
|
||||
? List.of()
|
||||
: meaningfulRecords.subList(fromIndex, toIndex);
|
||||
Page<ReceivablePayableChangeRecordVO> 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<String, Object> 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<String> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<TreeNode> {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user