mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-08 11:00:52 +08:00
chore: 调整代码格式 (mvn compile)
This commit is contained in:
@@ -131,9 +131,10 @@ public class SaTokenConfiguration {
|
|||||||
}).filter(Objects::nonNull).toList();
|
}).filter(Objects::nonNull).toList();
|
||||||
if (!additionalExcludes.isEmpty()) {
|
if (!additionalExcludes.isEmpty()) {
|
||||||
// 合并现有的 excludes 和新扫描到的
|
// 合并现有的 excludes 和新扫描到的
|
||||||
String[] existingExcludes = Optional.ofNullable(properties.getSecurity().getExcludes()).orElse(new String[0]);
|
String[] existingExcludes = Optional.ofNullable(properties.getSecurity().getExcludes())
|
||||||
|
.orElse(new String[0]);
|
||||||
String[] combinedExcludes = Stream.concat(Arrays.stream(existingExcludes), additionalExcludes.stream())
|
String[] combinedExcludes = Stream.concat(Arrays.stream(existingExcludes), additionalExcludes.stream())
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
properties.getSecurity().setExcludes(combinedExcludes);
|
properties.getSecurity().setExcludes(combinedExcludes);
|
||||||
}
|
}
|
||||||
log.debug("缓存 CRUD API 权限前缀完成:{}", CrudApiPermissionPrefixCache.getAll().values());
|
log.debug("缓存 CRUD API 权限前缀完成:{}", CrudApiPermissionPrefixCache.getAll().values());
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
* <p>
|
*
|
||||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
* <p>
|
*
|
||||||
* http://www.gnu.org/licenses/lgpl.html
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* <p>
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package top.continew.admin.system.controller;
|
package top.continew.admin.system.controller;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -47,8 +63,10 @@ public class MultipartUploadController {
|
|||||||
*/
|
*/
|
||||||
@Operation(summary = "上传分片", description = "上传单个分片")
|
@Operation(summary = "上传分片", description = "上传单个分片")
|
||||||
@PostMapping("/part")
|
@PostMapping("/part")
|
||||||
public MultipartUploadResp uploadPart(@RequestPart("file") MultipartFile file, @RequestParam("uploadId") String uploadId,
|
public MultipartUploadResp uploadPart(@RequestPart("file") MultipartFile file,
|
||||||
@RequestParam("partNumber") Integer partNumber, @RequestParam("path") String path) {
|
@RequestParam("uploadId") String uploadId,
|
||||||
|
@RequestParam("partNumber") Integer partNumber,
|
||||||
|
@RequestParam("path") String path) {
|
||||||
return multipartUploadService.uploadPart(file, uploadId, partNumber, path);
|
return multipartUploadService.uploadPart(file, uploadId, partNumber, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,5 +92,4 @@ public class MultipartUploadController {
|
|||||||
multipartUploadService.cancelMultipartUpload(uploadId);
|
multipartUploadService.cancelMultipartUpload(uploadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package top.continew.admin.system.dao;
|
package top.continew.admin.system.dao;
|
||||||
|
|
||||||
import top.continew.admin.system.model.resp.file.FilePartInfo;
|
import top.continew.admin.system.model.resp.file.FilePartInfo;
|
||||||
@@ -72,7 +88,7 @@ public interface MultipartUploadDao {
|
|||||||
/**
|
/**
|
||||||
* 设置缓存分片信息
|
* 设置缓存分片信息
|
||||||
*
|
*
|
||||||
* @param uploadId 上传ID
|
* @param uploadId 上传ID
|
||||||
* @param filePartInfo 分片信息
|
* @param filePartInfo 分片信息
|
||||||
*/
|
*/
|
||||||
void setFilePart(String uploadId, FilePartInfo filePartInfo);
|
void setFilePart(String uploadId, FilePartInfo filePartInfo);
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
* <p>
|
*
|
||||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
* <p>
|
*
|
||||||
* http://www.gnu.org/licenses/lgpl.html
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* <p>
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -95,7 +95,8 @@ public class RedisMultipartUploadDaoDaoImpl implements MultipartUploadDao {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 缓存初始化信息
|
// 缓存初始化信息
|
||||||
RedisUtils.set(key, JSONUtil.toJsonStr(initResp), Duration.ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
|
RedisUtils.set(key, JSONUtil.toJsonStr(initResp), Duration
|
||||||
|
.ofHours(MultipartUploadConstants.DEFAULT_EXPIRE_HOURS));
|
||||||
|
|
||||||
// 缓存元数据
|
// 缓存元数据
|
||||||
if (metadata != null && !metadata.isEmpty()) {
|
if (metadata != null && !metadata.isEmpty()) {
|
||||||
@@ -156,7 +157,7 @@ public class RedisMultipartUploadDaoDaoImpl implements MultipartUploadDao {
|
|||||||
public void deleteMultipartUploadAll(String uploadId) {
|
public void deleteMultipartUploadAll(String uploadId) {
|
||||||
this.deleteMultipartUpload(uploadId);
|
this.deleteMultipartUpload(uploadId);
|
||||||
this.deleteFileParts(uploadId);
|
this.deleteFileParts(uploadId);
|
||||||
// this.deleteMd5Mapping();
|
// this.deleteMd5Mapping();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -185,10 +186,10 @@ public class RedisMultipartUploadDaoDaoImpl implements MultipartUploadDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entries.values()
|
return entries.values()
|
||||||
.stream()
|
.stream()
|
||||||
.map(value -> JSONUtil.toBean(value.toString(), FilePartInfo.class))
|
.map(value -> JSONUtil.toBean(value.toString(), FilePartInfo.class))
|
||||||
.sorted(Comparator.comparing(FilePartInfo::getPartNumber))
|
.sorted(Comparator.comparing(FilePartInfo::getPartNumber))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取分片列表失败: uploadId={}", uploadId, e);
|
log.error("获取分片列表失败: uploadId={}", uploadId, e);
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
@@ -233,7 +234,7 @@ public class RedisMultipartUploadDaoDaoImpl implements MultipartUploadDao {
|
|||||||
if (value != null) {
|
if (value != null) {
|
||||||
try {
|
try {
|
||||||
LocalDateTime expireTime = LocalDateTime.parse(value
|
LocalDateTime expireTime = LocalDateTime.parse(value
|
||||||
.toString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
.toString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||||
if (now.isAfter(expireTime)) {
|
if (now.isAfter(expireTime)) {
|
||||||
expiredUploadIds.add(uploadId);
|
expiredUploadIds.add(uploadId);
|
||||||
}
|
}
|
||||||
|
@@ -44,13 +44,13 @@ public class S3ClientFactory {
|
|||||||
String key = storage.getEndpoint() + "|" + storage.getAccessKey();
|
String key = storage.getEndpoint() + "|" + storage.getAccessKey();
|
||||||
return CLIENT_CACHE.computeIfAbsent(key, k -> {
|
return CLIENT_CACHE.computeIfAbsent(key, k -> {
|
||||||
StaticCredentialsProvider auth = StaticCredentialsProvider.create(AwsBasicCredentials.create(storage
|
StaticCredentialsProvider auth = StaticCredentialsProvider.create(AwsBasicCredentials.create(storage
|
||||||
.getAccessKey(), storage.getSecretKey()));
|
.getAccessKey(), storage.getSecretKey()));
|
||||||
return S3Client.builder()
|
return S3Client.builder()
|
||||||
.credentialsProvider(auth)
|
.credentialsProvider(auth)
|
||||||
.endpointOverride(URI.create(storage.getEndpoint()))
|
.endpointOverride(URI.create(storage.getEndpoint()))
|
||||||
.region(Region.US_EAST_1)
|
.region(Region.US_EAST_1)
|
||||||
.serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build())
|
.serviceConfiguration(S3Configuration.builder().chunkedEncodingEnabled(false).build())
|
||||||
.build();
|
.build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package top.continew.admin.system.factory;/*
|
/*
|
||||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@@ -14,6 +14,21 @@ package top.continew.admin.system.factory;/*
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
package top.continew.admin.system.factory;/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -30,6 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
/**
|
/**
|
||||||
* 存储处理器工厂
|
* 存储处理器工厂
|
||||||
* <p>按类型分发 StorageHandler</p>
|
* <p>按类型分发 StorageHandler</p>
|
||||||
|
*
|
||||||
* @author KAI
|
* @author KAI
|
||||||
* @since 2025/07/24 13:35
|
* @since 2025/07/24 13:35
|
||||||
*/
|
*/
|
||||||
@@ -52,6 +68,6 @@ public class StorageHandlerFactory {
|
|||||||
*/
|
*/
|
||||||
public StorageHandler createHandler(StorageTypeEnum type) {
|
public StorageHandler createHandler(StorageTypeEnum type) {
|
||||||
return Optional.ofNullable(HANDLER_MAP.get(type))
|
return Optional.ofNullable(HANDLER_MAP.get(type))
|
||||||
.orElseThrow(() -> new BaseException(StrUtil.format("不存在此类型存储处理器:{}: ", type)));
|
.orElseThrow(() -> new BaseException(StrUtil.format("不存在此类型存储处理器:{}: ", type)));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package top.continew.admin.system.handler;
|
package top.continew.admin.system.handler;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -30,7 +46,11 @@ public interface StorageHandler {
|
|||||||
* @param file 文件对象
|
* @param file 文件对象
|
||||||
* @return {@link MultipartUploadResp} 分片上传结果
|
* @return {@link MultipartUploadResp} 分片上传结果
|
||||||
*/
|
*/
|
||||||
MultipartUploadResp uploadPart(StorageDO storageDO, String path, String uploadId, Integer partNumber, MultipartFile file);
|
MultipartUploadResp uploadPart(StorageDO storageDO,
|
||||||
|
String path,
|
||||||
|
String uploadId,
|
||||||
|
Integer partNumber,
|
||||||
|
MultipartFile file);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 合并分片
|
* 合并分片
|
||||||
@@ -38,7 +58,11 @@ public interface StorageHandler {
|
|||||||
* @param storageDO 存储实体
|
* @param storageDO 存储实体
|
||||||
* @param uploadId 上传Id
|
* @param uploadId 上传Id
|
||||||
*/
|
*/
|
||||||
void completeMultipartUpload(StorageDO storageDO, List<MultipartUploadResp> parts, String path, String uploadId, boolean needVerify);
|
void completeMultipartUpload(StorageDO storageDO,
|
||||||
|
List<MultipartUploadResp> parts,
|
||||||
|
String path,
|
||||||
|
String uploadId,
|
||||||
|
boolean needVerify);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清楚分片
|
* 清楚分片
|
||||||
|
@@ -64,7 +64,9 @@ public class LocalStorageHandler implements StorageHandler {
|
|||||||
String parentPath = req.getParentPath();
|
String parentPath = req.getParentPath();
|
||||||
String fileName = req.getFileName();
|
String fileName = req.getFileName();
|
||||||
StrUtil.blankToDefault(parentPath, StrUtil.SLASH);
|
StrUtil.blankToDefault(parentPath, StrUtil.SLASH);
|
||||||
String relativePath = StrUtil.endWith(parentPath, StrUtil.SLASH) ? parentPath + fileName : parentPath + StrUtil.SLASH + fileName;
|
String relativePath = StrUtil.endWith(parentPath, StrUtil.SLASH)
|
||||||
|
? parentPath + fileName
|
||||||
|
: parentPath + StrUtil.SLASH + fileName;
|
||||||
try {
|
try {
|
||||||
// 创建临时目录用于存储分片
|
// 创建临时目录用于存储分片
|
||||||
String tempDirPath = buildTempDirPath(bucket, uploadId);
|
String tempDirPath = buildTempDirPath(bucket, uploadId);
|
||||||
@@ -93,7 +95,11 @@ public class LocalStorageHandler implements StorageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultipartUploadResp uploadPart(StorageDO storageDO, String path, String uploadId, Integer partNumber, MultipartFile file) {
|
public MultipartUploadResp uploadPart(StorageDO storageDO,
|
||||||
|
String path,
|
||||||
|
String uploadId,
|
||||||
|
Integer partNumber,
|
||||||
|
MultipartFile file) {
|
||||||
try {
|
try {
|
||||||
long size = file.getSize();
|
long size = file.getSize();
|
||||||
String bucket = storageDO.getBucketName();
|
String bucket = storageDO.getBucketName();
|
||||||
@@ -135,7 +141,11 @@ public class LocalStorageHandler implements StorageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completeMultipartUpload(StorageDO storageDO, List<MultipartUploadResp> parts, String path, String uploadId, boolean needVerify) {
|
public void completeMultipartUpload(StorageDO storageDO,
|
||||||
|
List<MultipartUploadResp> parts,
|
||||||
|
String path,
|
||||||
|
String uploadId,
|
||||||
|
boolean needVerify) {
|
||||||
String bucket = storageDO.getBucketName(); // 本地存储中,bucket是存储根路径
|
String bucket = storageDO.getBucketName(); // 本地存储中,bucket是存储根路径
|
||||||
String tempDirPath = buildTempDirPath(bucket, uploadId);
|
String tempDirPath = buildTempDirPath(bucket, uploadId);
|
||||||
|
|
||||||
@@ -146,13 +156,13 @@ public class LocalStorageHandler implements StorageHandler {
|
|||||||
|
|
||||||
// 合并分片
|
// 合并分片
|
||||||
try (OutputStream out = Files
|
try (OutputStream out = Files
|
||||||
.newOutputStream(targetPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
.newOutputStream(targetPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
|
|
||||||
// 按分片编号排序
|
// 按分片编号排序
|
||||||
List<MultipartUploadResp> sortedParts = parts.stream()
|
List<MultipartUploadResp> sortedParts = parts.stream()
|
||||||
.filter(MultipartUploadResp::isSuccess)
|
.filter(MultipartUploadResp::isSuccess)
|
||||||
.sorted(Comparator.comparingInt(MultipartUploadResp::getPartNumber))
|
.sorted(Comparator.comparingInt(MultipartUploadResp::getPartNumber))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// 逐个读取并写入
|
// 逐个读取并写入
|
||||||
for (MultipartUploadResp part : sortedParts) {
|
for (MultipartUploadResp part : sortedParts) {
|
||||||
@@ -206,10 +216,10 @@ public class LocalStorageHandler implements StorageHandler {
|
|||||||
* @return 临时目录路径
|
* @return 临时目录路径
|
||||||
*/
|
*/
|
||||||
private String buildTempDirPath(String bucket, String uploadId) {
|
private String buildTempDirPath(String bucket, String uploadId) {
|
||||||
return StrUtil.appendIfMissing(bucket, File.separator) + MultipartUploadConstants.TEMP_DIR_NAME + File.separator + uploadId;
|
return StrUtil
|
||||||
|
.appendIfMissing(bucket, File.separator) + MultipartUploadConstants.TEMP_DIR_NAME + File.separator + uploadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建目标文件路径
|
* 构建目标文件路径
|
||||||
*
|
*
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package top.continew.admin.system.handler.impl;
|
package top.continew.admin.system.handler.impl;
|
||||||
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
@@ -46,20 +62,22 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
String fileName = req.getFileName();
|
String fileName = req.getFileName();
|
||||||
String contentType = req.getContentType();
|
String contentType = req.getContentType();
|
||||||
StrUtil.blankToDefault(parentPath, StrUtil.SLASH);
|
StrUtil.blankToDefault(parentPath, StrUtil.SLASH);
|
||||||
String relativePath = StrUtil.endWith(parentPath, StrUtil.SLASH) ? parentPath + fileName : parentPath + StrUtil.SLASH + fileName;
|
String relativePath = StrUtil.endWith(parentPath, StrUtil.SLASH)
|
||||||
|
? parentPath + fileName
|
||||||
|
: parentPath + StrUtil.SLASH + fileName;
|
||||||
|
|
||||||
fileService.createParentDir(parentPath, storageDO);
|
fileService.createParentDir(parentPath, storageDO);
|
||||||
try {
|
try {
|
||||||
// 构建请求
|
// 构建请求
|
||||||
CreateMultipartUploadRequest.Builder requestBuilder = CreateMultipartUploadRequest.builder()
|
CreateMultipartUploadRequest.Builder requestBuilder = CreateMultipartUploadRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
.key(buildS3Key(relativePath))
|
.key(buildS3Key(relativePath))
|
||||||
.contentType(contentType);
|
.contentType(contentType);
|
||||||
|
|
||||||
// 添加元数据 暂时注释掉 mataData传递中文会导致签名校验不通过
|
// 添加元数据 暂时注释掉 mataData传递中文会导致签名校验不通过
|
||||||
// if (metaData != null && !metaData.isEmpty()) {
|
// if (metaData != null && !metaData.isEmpty()) {
|
||||||
// requestBuilder.metadata(metaData);
|
// requestBuilder.metadata(metaData);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
S3Client s3Client = s3ClientFactory.getClient(storageDO);
|
S3Client s3Client = s3ClientFactory.getClient(storageDO);
|
||||||
log.info("S3初始化分片上传: bucket={}, key={}, contentType={}", bucket, buildS3Key(relativePath), contentType);
|
log.info("S3初始化分片上传: bucket={}, key={}, contentType={}", bucket, buildS3Key(relativePath), contentType);
|
||||||
@@ -85,14 +103,17 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
log.info("S3初始化分片上传成功: uploadId={}, path={}", uploadId, relativePath);
|
log.info("S3初始化分片上传成功: uploadId={}, path={}", uploadId, relativePath);
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new BaseException("S3初始化分片上传失败: " + e.getMessage(), e);
|
throw new BaseException("S3初始化分片上传失败: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultipartUploadResp uploadPart(StorageDO storageDO, String path, String uploadId, Integer partNumber, MultipartFile file) {
|
public MultipartUploadResp uploadPart(StorageDO storageDO,
|
||||||
|
String path,
|
||||||
|
String uploadId,
|
||||||
|
Integer partNumber,
|
||||||
|
MultipartFile file) {
|
||||||
try {
|
try {
|
||||||
String bucket = storageDO.getBucketName();
|
String bucket = storageDO.getBucketName();
|
||||||
// 读取数据到内存(注意:实际使用时可能需要优化大文件处理)
|
// 读取数据到内存(注意:实际使用时可能需要优化大文件处理)
|
||||||
@@ -100,12 +121,12 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
|
|
||||||
// 构建请求
|
// 构建请求
|
||||||
UploadPartRequest request = UploadPartRequest.builder()
|
UploadPartRequest request = UploadPartRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
.key(buildS3Key(path))
|
.key(buildS3Key(path))
|
||||||
.uploadId(uploadId)
|
.uploadId(uploadId)
|
||||||
.partNumber(partNumber)
|
.partNumber(partNumber)
|
||||||
.contentLength((long) bytes.length)
|
.contentLength((long)bytes.length)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 执行上传
|
// 执行上传
|
||||||
S3Client s3Client = s3ClientFactory.getClient(storageDO);
|
S3Client s3Client = s3ClientFactory.getClient(storageDO);
|
||||||
@@ -125,13 +146,18 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
result.setPartNumber(partNumber);
|
result.setPartNumber(partNumber);
|
||||||
result.setSuccess(false);
|
result.setSuccess(false);
|
||||||
result.setErrorMessage(e.getMessage());
|
result.setErrorMessage(e.getMessage());
|
||||||
log.error("S3上传分片失败: partNumber={} for key={} with uploadId={} errorMessage={}", partNumber, path, uploadId, e.getMessage());
|
log.error("S3上传分片失败: partNumber={} for key={} with uploadId={} errorMessage={}", partNumber, path, uploadId, e
|
||||||
|
.getMessage());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completeMultipartUpload(StorageDO storageDO, List<MultipartUploadResp> parts, String path, String uploadId, boolean needVerify) {
|
public void completeMultipartUpload(StorageDO storageDO,
|
||||||
|
List<MultipartUploadResp> parts,
|
||||||
|
String path,
|
||||||
|
String uploadId,
|
||||||
|
boolean needVerify) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
throw new BaseException("无效的uploadId: " + uploadId);
|
throw new BaseException("无效的uploadId: " + uploadId);
|
||||||
}
|
}
|
||||||
@@ -144,18 +170,18 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
}
|
}
|
||||||
// 构建已完成的分片列表
|
// 构建已完成的分片列表
|
||||||
List<CompletedPart> completedParts = parts.stream()
|
List<CompletedPart> completedParts = parts.stream()
|
||||||
.filter(MultipartUploadResp::isSuccess)
|
.filter(MultipartUploadResp::isSuccess)
|
||||||
.map(part -> CompletedPart.builder().partNumber(part.getPartNumber()).eTag(part.getPartETag()).build())
|
.map(part -> CompletedPart.builder().partNumber(part.getPartNumber()).eTag(part.getPartETag()).build())
|
||||||
.sorted(Comparator.comparingInt(CompletedPart::partNumber))
|
.sorted(Comparator.comparingInt(CompletedPart::partNumber))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 构建请求
|
// 构建请求
|
||||||
CompleteMultipartUploadRequest request = CompleteMultipartUploadRequest.builder()
|
CompleteMultipartUploadRequest request = CompleteMultipartUploadRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
.key(buildS3Key(path))
|
.key(buildS3Key(path))
|
||||||
.uploadId(uploadId)
|
.uploadId(uploadId)
|
||||||
.multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build())
|
.multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 完成上传
|
// 完成上传
|
||||||
s3Client.completeMultipartUpload(request);
|
s3Client.completeMultipartUpload(request);
|
||||||
@@ -169,26 +195,25 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
S3Client s3Client = s3ClientFactory.getClient(storageDO);
|
S3Client s3Client = s3ClientFactory.getClient(storageDO);
|
||||||
|
|
||||||
// 列出所有未完成的分片上传
|
// 列出所有未完成的分片上传
|
||||||
ListMultipartUploadsRequest listRequest = ListMultipartUploadsRequest.builder()
|
ListMultipartUploadsRequest listRequest = ListMultipartUploadsRequest.builder().bucket(bucket).build();
|
||||||
.bucket(bucket)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ListMultipartUploadsResponse listResponse = s3Client.listMultipartUploads(listRequest);
|
ListMultipartUploadsResponse listResponse = s3Client.listMultipartUploads(listRequest);
|
||||||
|
|
||||||
// 查找匹配的上传任务
|
// 查找匹配的上传任务
|
||||||
Optional<MultipartUpload> targetUpload = listResponse.uploads().stream()
|
Optional<MultipartUpload> targetUpload = listResponse.uploads()
|
||||||
.filter(upload -> upload.uploadId().equals(uploadId))
|
.stream()
|
||||||
.findFirst();
|
.filter(upload -> upload.uploadId().equals(uploadId))
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
if (targetUpload.isPresent()) {
|
if (targetUpload.isPresent()) {
|
||||||
MultipartUpload upload = targetUpload.get();
|
MultipartUpload upload = targetUpload.get();
|
||||||
|
|
||||||
// 取消分片上传
|
// 取消分片上传
|
||||||
AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest.builder()
|
AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
.key(upload.key())
|
.key(upload.key())
|
||||||
.uploadId(uploadId)
|
.uploadId(uploadId)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
s3Client.abortMultipartUpload(abortRequest);
|
s3Client.abortMultipartUpload(abortRequest);
|
||||||
log.info("S3清理分片上传成功: bucket={}, key={}, uploadId={}", bucket, upload.key(), uploadId);
|
log.info("S3清理分片上传成功: bucket={}, key={}, uploadId={}", bucket, upload.key(), uploadId);
|
||||||
@@ -207,14 +232,17 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
return StorageTypeEnum.OSS;
|
return StorageTypeEnum.OSS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 列出已上传的分片
|
* 列出已上传的分片
|
||||||
*/
|
*/
|
||||||
public List<MultipartUploadResp> listParts(String bucket, String path, String uploadId, S3Client s3Client) {
|
public List<MultipartUploadResp> listParts(String bucket, String path, String uploadId, S3Client s3Client) {
|
||||||
try {
|
try {
|
||||||
// 构建请求
|
// 构建请求
|
||||||
ListPartsRequest request = ListPartsRequest.builder().bucket(bucket).key(buildS3Key(path)).uploadId(uploadId).build();
|
ListPartsRequest request = ListPartsRequest.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.key(buildS3Key(path))
|
||||||
|
.uploadId(uploadId)
|
||||||
|
.build();
|
||||||
|
|
||||||
// 获取分片列表
|
// 获取分片列表
|
||||||
ListPartsResponse response = s3Client.listParts(request);
|
ListPartsResponse response = s3Client.listParts(request);
|
||||||
@@ -241,10 +269,10 @@ public class S3StorageHandler implements StorageHandler {
|
|||||||
*/
|
*/
|
||||||
private void validateParts(List<MultipartUploadResp> recordParts, List<MultipartUploadResp> s3Parts) {
|
private void validateParts(List<MultipartUploadResp> recordParts, List<MultipartUploadResp> s3Parts) {
|
||||||
Map<Integer, String> recordMap = recordParts.stream()
|
Map<Integer, String> recordMap = recordParts.stream()
|
||||||
.collect(Collectors.toMap(MultipartUploadResp::getPartNumber, MultipartUploadResp::getPartETag));
|
.collect(Collectors.toMap(MultipartUploadResp::getPartNumber, MultipartUploadResp::getPartETag));
|
||||||
|
|
||||||
Map<Integer, String> s3Map = s3Parts.stream()
|
Map<Integer, String> s3Map = s3Parts.stream()
|
||||||
.collect(Collectors.toMap(MultipartUploadResp::getPartNumber, MultipartUploadResp::getPartETag));
|
.collect(Collectors.toMap(MultipartUploadResp::getPartNumber, MultipartUploadResp::getPartETag));
|
||||||
|
|
||||||
// 检查分片数量
|
// 检查分片数量
|
||||||
if (recordMap.size() != s3Map.size()) {
|
if (recordMap.size() != s3Map.size()) {
|
||||||
|
@@ -77,5 +77,5 @@ public class MultipartUploadInitReq implements Serializable {
|
|||||||
* 文件元信息
|
* 文件元信息
|
||||||
*/
|
*/
|
||||||
@Schema(description = "文件元信息")
|
@Schema(description = "文件元信息")
|
||||||
private Map<String,String> metaData;
|
private Map<String, String> metaData;
|
||||||
}
|
}
|
@@ -116,5 +116,4 @@ public class MultipartUploadInitResp implements Serializable {
|
|||||||
@Schema(description = "已上传分片编号集合")
|
@Schema(description = "已上传分片编号集合")
|
||||||
private Set<Integer> uploadedPartNumbers;
|
private Set<Integer> uploadedPartNumbers;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package top.continew.admin.system.service;
|
package top.continew.admin.system.service;
|
||||||
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -16,7 +32,7 @@ public interface MultipartUploadService {
|
|||||||
|
|
||||||
MultipartUploadInitResp initMultipartUpload(MultipartUploadInitReq multiPartUploadInitReq);
|
MultipartUploadInitResp initMultipartUpload(MultipartUploadInitReq multiPartUploadInitReq);
|
||||||
|
|
||||||
MultipartUploadResp uploadPart(MultipartFile file, String uploadId, Integer partNumber,String path);
|
MultipartUploadResp uploadPart(MultipartFile file, String uploadId, Integer partNumber, String path);
|
||||||
|
|
||||||
FileDO completeMultipartUpload(String uploadId);
|
FileDO completeMultipartUpload(String uploadId);
|
||||||
|
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
package top.continew.admin.system.service.impl;
|
package top.continew.admin.system.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
@@ -51,17 +67,18 @@ public class MultipartUploadServiceImpl implements MultipartUploadService {
|
|||||||
// 后续可以增加storageCode参数 指定某个存储平台 当前设计是默认存储平台
|
// 后续可以增加storageCode参数 指定某个存储平台 当前设计是默认存储平台
|
||||||
StorageDO storageDO = storageService.getByCode(null);
|
StorageDO storageDO = storageService.getByCode(null);
|
||||||
// 根据文件Md5查询当前存储平台是否初始化过分片
|
// 根据文件Md5查询当前存储平台是否初始化过分片
|
||||||
String uploadId = multipartUploadDao.getUploadIdByMd5(multiPartUploadInitReq
|
String uploadId = multipartUploadDao.getUploadIdByMd5(multiPartUploadInitReq.getFileMd5());
|
||||||
.getFileMd5());
|
|
||||||
if (StrUtil.isNotBlank(uploadId)) {
|
if (StrUtil.isNotBlank(uploadId)) {
|
||||||
MultipartUploadInitResp multipartUpload = multipartUploadDao.getMultipartUpload(uploadId);
|
MultipartUploadInitResp multipartUpload = multipartUploadDao.getMultipartUpload(uploadId);
|
||||||
//对比存储平台和分片大小是否一致 一致则返回结果
|
//对比存储平台和分片大小是否一致 一致则返回结果
|
||||||
if (multipartUpload != null
|
if (multipartUpload != null && multipartUpload.getPartSize()
|
||||||
&& multipartUpload.getPartSize().equals(MultipartUploadConstants.MULTIPART_UPLOAD_PART_SIZE)
|
.equals(MultipartUploadConstants.MULTIPART_UPLOAD_PART_SIZE) && multipartUpload.getPlatform()
|
||||||
&& multipartUpload.getPlatform().equals(storageDO.getCode())) {
|
.equals(storageDO.getCode())) {
|
||||||
// 获取已上传分片信息
|
// 获取已上传分片信息
|
||||||
List<FilePartInfo> fileParts = multipartUploadDao.getFileParts(uploadId);
|
List<FilePartInfo> fileParts = multipartUploadDao.getFileParts(uploadId);
|
||||||
Set<Integer> partNumbers = fileParts.stream().map(FilePartInfo::getPartNumber).collect(Collectors.toSet());
|
Set<Integer> partNumbers = fileParts.stream()
|
||||||
|
.map(FilePartInfo::getPartNumber)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
multipartUpload.setUploadedPartNumbers(partNumbers);
|
multipartUpload.setUploadedPartNumbers(partNumbers);
|
||||||
return multipartUpload;
|
return multipartUpload;
|
||||||
}
|
}
|
||||||
@@ -71,7 +88,8 @@ public class MultipartUploadServiceImpl implements MultipartUploadService {
|
|||||||
StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
|
StorageHandler storageHandler = storageHandlerFactory.createHandler(storageDO.getType());
|
||||||
//文件元信息
|
//文件元信息
|
||||||
Map<String, String> metaData = multiPartUploadInitReq.getMetaData();
|
Map<String, String> metaData = multiPartUploadInitReq.getMetaData();
|
||||||
MultipartUploadInitResp multipartUploadInitResp = storageHandler.initMultipartUpload(storageDO, multiPartUploadInitReq);
|
MultipartUploadInitResp multipartUploadInitResp = storageHandler
|
||||||
|
.initMultipartUpload(storageDO, multiPartUploadInitReq);
|
||||||
// 缓存文件信息,md5和uploadId映射
|
// 缓存文件信息,md5和uploadId映射
|
||||||
multipartUploadDao.setMultipartUpload(multipartUploadInitResp.getUploadId(), multipartUploadInitResp, metaData);
|
multipartUploadDao.setMultipartUpload(multipartUploadInitResp.getUploadId(), multipartUploadInitResp, metaData);
|
||||||
multipartUploadDao.setMd5Mapping(multiPartUploadInitReq.getFileMd5(), multipartUploadInitResp.getUploadId());
|
multipartUploadDao.setMd5Mapping(multiPartUploadInitReq.getFileMd5(), multipartUploadInitResp.getUploadId());
|
||||||
@@ -174,9 +192,9 @@ public class MultipartUploadServiceImpl implements MultipartUploadService {
|
|||||||
|
|
||||||
// 检查是否所有分片都成功
|
// 检查是否所有分片都成功
|
||||||
List<Integer> failedParts = parts.stream()
|
List<Integer> failedParts = parts.stream()
|
||||||
.filter(part -> !part.isSuccess())
|
.filter(part -> !part.isSuccess())
|
||||||
.map(MultipartUploadResp::getPartNumber)
|
.map(MultipartUploadResp::getPartNumber)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (!failedParts.isEmpty()) {
|
if (!failedParts.isEmpty()) {
|
||||||
throw new BaseException("存在失败的分片: " + failedParts);
|
throw new BaseException("存在失败的分片: " + failedParts);
|
||||||
|
Reference in New Issue
Block a user