打包下载问题修改
This commit is contained in:
parent
5da3559551
commit
6265691055
|
|
@ -5,7 +5,6 @@ import com.bonus.common.core.constant.ServiceNameConstants;
|
||||||
import com.bonus.common.core.domain.R;
|
import com.bonus.common.core.domain.R;
|
||||||
import com.bonus.system.api.domain.DownloadTask;
|
import com.bonus.system.api.domain.DownloadTask;
|
||||||
import com.bonus.system.api.domain.FileVo;
|
import com.bonus.system.api.domain.FileVo;
|
||||||
import com.bonus.system.api.domain.ZipFileMapping;
|
|
||||||
import com.bonus.system.api.factory.RemoteUploadUtilsFallbackFactory;
|
import com.bonus.system.api.factory.RemoteUploadUtilsFallbackFactory;
|
||||||
import com.bonus.system.api.model.UploadFileVo;
|
import com.bonus.system.api.model.UploadFileVo;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
|
@ -145,5 +144,5 @@ public interface RemoteUploadUtilsService {
|
||||||
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||||
|
|
||||||
@PostMapping(value = "/uploadFile/zipFile")
|
@PostMapping(value = "/uploadFile/zipFile")
|
||||||
R<DownloadTask> zipFile(@RequestBody List<ZipFileMapping> objectNames, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
R<DownloadTask> zipFile(@RequestBody String objectNames, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import com.bonus.common.core.domain.R;
|
||||||
import com.bonus.system.api.RemoteUploadUtilsService;
|
import com.bonus.system.api.RemoteUploadUtilsService;
|
||||||
import com.bonus.system.api.domain.DownloadTask;
|
import com.bonus.system.api.domain.DownloadTask;
|
||||||
import com.bonus.system.api.domain.FileVo;
|
import com.bonus.system.api.domain.FileVo;
|
||||||
import com.bonus.system.api.domain.ZipFileMapping;
|
|
||||||
import com.bonus.system.api.model.UploadFileVo;
|
import com.bonus.system.api.model.UploadFileVo;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -78,11 +77,10 @@ public class RemoteUploadUtilsFallbackFactory implements FallbackFactory<RemoteU
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<DownloadTask> zipFile(List<ZipFileMapping> objectNames, String source) {
|
public R<DownloadTask> zipFile(String objectNames, String source) {
|
||||||
return R.fail("文件打包异常:" + throwable.getMessage());
|
return R.fail("文件打包异常:" + throwable.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,7 @@ import com.bonus.bmw.service.impl.FileUploadUtils;
|
||||||
import com.bonus.common.core.web.domain.AjaxResult;
|
import com.bonus.common.core.web.domain.AjaxResult;
|
||||||
import com.bonus.system.api.domain.DownloadTask;
|
import com.bonus.system.api.domain.DownloadTask;
|
||||||
import com.bonus.system.api.domain.ZipFileMapping;
|
import com.bonus.system.api.domain.ZipFileMapping;
|
||||||
import io.minio.GetObjectArgs;
|
|
||||||
import io.minio.MinioClient;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.compress.archivers.zip.Zip64Mode;
|
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
|
@ -19,7 +14,9 @@ import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
@ -44,10 +41,16 @@ public class ZipDownloadController {
|
||||||
|
|
||||||
@PostMapping("/createZipTask")
|
@PostMapping("/createZipTask")
|
||||||
public AjaxResult createZipTask(@RequestBody BmWorkerAtt o) {
|
public AjaxResult createZipTask(@RequestBody BmWorkerAtt o) {
|
||||||
|
try {
|
||||||
//查询文件地址
|
//查询文件地址
|
||||||
List<ZipFileMapping> objectNames = service.getFileList(o);
|
List<ZipFileMapping> objectNames = service.getFileList(o);
|
||||||
DownloadTask task = fileUploadUtils.zipFile(objectNames);
|
DownloadTask task = fileUploadUtils.zipFile(objectNames);
|
||||||
return AjaxResult.success(task);
|
return AjaxResult.success(task);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("打包任务异常", e);
|
||||||
|
return AjaxResult.error("打包任务异常");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,12 +58,18 @@ public class ZipDownloadController {
|
||||||
*/
|
*/
|
||||||
@GetMapping("/taskStatus/{taskId}")
|
@GetMapping("/taskStatus/{taskId}")
|
||||||
public AjaxResult getTaskStatus(@PathVariable String taskId) {
|
public AjaxResult getTaskStatus(@PathVariable String taskId) {
|
||||||
|
try {
|
||||||
String redisKey = TASK_PREFIX + taskId;
|
String redisKey = TASK_PREFIX + taskId;
|
||||||
DownloadTask task = redisTemplate.opsForValue().get(redisKey);
|
DownloadTask task = redisTemplate.opsForValue().get(redisKey);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
return AjaxResult.error("打包下载任务未查询");
|
return AjaxResult.error("未查询打包任务");
|
||||||
}
|
}
|
||||||
return AjaxResult.success(task);
|
return AjaxResult.success(task);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询打包任务异常", e);
|
||||||
|
return AjaxResult.error("查询打包任务异常");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,6 +77,7 @@ public class ZipDownloadController {
|
||||||
*/
|
*/
|
||||||
@GetMapping("/downloadFile/{filename:.+}")
|
@GetMapping("/downloadFile/{filename:.+}")
|
||||||
public void serveZipFile(@PathVariable String filename, HttpServletResponse response) throws IOException {
|
public void serveZipFile(@PathVariable String filename, HttpServletResponse response) throws IOException {
|
||||||
|
try {
|
||||||
// 1. 基础安全:禁止路径遍历
|
// 1. 基础安全:禁止路径遍历
|
||||||
if (filename.contains("..") || filename.startsWith("/") || filename.startsWith("\\")) {
|
if (filename.contains("..") || filename.startsWith("/") || filename.startsWith("\\")) {
|
||||||
response.sendError(403, "Path traversal not allowed");
|
response.sendError(403, "Path traversal not allowed");
|
||||||
|
|
@ -97,7 +107,6 @@ public class ZipDownloadController {
|
||||||
response.setContentType("application/zip");
|
response.setContentType("application/zip");
|
||||||
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
|
response.setHeader("Content-Disposition", "attachment; filename=" + filename);
|
||||||
response.setContentLengthLong(Files.size(zipPath));
|
response.setContentLengthLong(Files.size(zipPath));
|
||||||
|
|
||||||
try (InputStream is = Files.newInputStream(zipPath);
|
try (InputStream is = Files.newInputStream(zipPath);
|
||||||
OutputStream os = response.getOutputStream()) {
|
OutputStream os = response.getOutputStream()) {
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
|
|
@ -106,52 +115,14 @@ public class ZipDownloadController {
|
||||||
os.write(buffer, 0, n);
|
os.write(buffer, 0, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载文件异常", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用 Apache Commons Compress 从 MinIO 打包 ZIP(支持 UTF-8 中文)
|
|
||||||
*/
|
|
||||||
public static void generateZipFromMinIO(MinioClient minioClient, String bucket, List<ZipFileMapping> fileMappings, String zipPath) throws Exception {
|
|
||||||
Path outputPath = Paths.get(zipPath);
|
|
||||||
Files.createDirectories(outputPath.getParent());
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(outputPath.toFile());
|
|
||||||
BufferedOutputStream bos = new BufferedOutputStream(fos, 64 * 1024);
|
|
||||||
ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(bos)) {
|
|
||||||
zipOut.setEncoding("UTF-8");
|
|
||||||
zipOut.setUseZip64(Zip64Mode.AsNeeded);
|
|
||||||
for (ZipFileMapping mapping : fileMappings) {
|
|
||||||
String objectName = mapping.getObjectName();
|
|
||||||
String targetPath = mapping.getTargetPath();
|
|
||||||
// 安全校验
|
|
||||||
if (objectName == null || targetPath == null ||
|
|
||||||
objectName.contains("..") || targetPath.contains("..") ||
|
|
||||||
objectName.startsWith("/") || targetPath.startsWith("/")) {
|
|
||||||
continue; // 跳过非法项
|
|
||||||
}
|
|
||||||
// 统一使用正斜杠(兼容 Windows)
|
|
||||||
String entryName = targetPath.replace('\\', '/');
|
|
||||||
try (InputStream objectStream = minioClient.getObject(
|
|
||||||
GetObjectArgs.builder()
|
|
||||||
.bucket(bucket)
|
|
||||||
.object(objectName)
|
|
||||||
.build())) {
|
|
||||||
ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
|
|
||||||
zipOut.putArchiveEntry(entry);
|
|
||||||
byte[] buffer = new byte[8192];
|
|
||||||
int len;
|
|
||||||
while ((len = objectStream.read(buffer)) > 0) {
|
|
||||||
zipOut.write(buffer, 0, len);
|
|
||||||
}
|
|
||||||
zipOut.closeArchiveEntry();
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Failed to process object: " + objectName + " -> " + targetPath);
|
|
||||||
// 可选择继续或中断
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zipOut.finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定时清理过期文件
|
* 定时清理过期文件
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.bonus.bmw.service.impl;
|
package com.bonus.bmw.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.bonus.common.core.constant.SecurityConstants;
|
import com.bonus.common.core.constant.SecurityConstants;
|
||||||
import com.bonus.common.core.domain.R;
|
import com.bonus.common.core.domain.R;
|
||||||
import com.bonus.system.api.RemoteUploadUtilsService;
|
import com.bonus.system.api.RemoteUploadUtilsService;
|
||||||
|
|
@ -28,6 +29,7 @@ public class FileUploadUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单文件上传
|
* 单文件上传
|
||||||
|
*
|
||||||
* @param file -必填
|
* @param file -必填
|
||||||
* @param sourceTable -必填
|
* @param sourceTable -必填
|
||||||
* @param sourceId -必填
|
* @param sourceId -必填
|
||||||
|
|
@ -44,8 +46,10 @@ public class FileUploadUtils {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单文件上传-bast64
|
* 单文件上传-bast64
|
||||||
|
*
|
||||||
* @param file -必填
|
* @param file -必填
|
||||||
* @param sourceTable -必填
|
* @param sourceTable -必填
|
||||||
* @param sourceId -必填
|
* @param sourceId -必填
|
||||||
|
|
@ -66,6 +70,7 @@ public class FileUploadUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多文件上传-bast64
|
* 多文件上传-bast64
|
||||||
|
*
|
||||||
* @param files 必填
|
* @param files 必填
|
||||||
* @param sourceTable 必填
|
* @param sourceTable 必填
|
||||||
* @param sourceId 必填
|
* @param sourceId 必填
|
||||||
|
|
@ -85,6 +90,7 @@ public class FileUploadUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多文件上传
|
* 多文件上传
|
||||||
|
*
|
||||||
* @param files 必填
|
* @param files 必填
|
||||||
* @param sourceTable 必填
|
* @param sourceTable 必填
|
||||||
* @param sourceId 必填
|
* @param sourceId 必填
|
||||||
|
|
@ -103,7 +109,6 @@ public class FileUploadUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param id -选择性必填 id查询数据
|
* @param id -选择性必填 id查询数据
|
||||||
* @param sourceId --选择性必填 依据资源id查询
|
* @param sourceId --选择性必填 依据资源id查询
|
||||||
* @param sourceTable --非必填 依据资源id和 表机构id查询
|
* @param sourceTable --非必填 依据资源id和 表机构id查询
|
||||||
|
|
@ -122,6 +127,7 @@ public class FileUploadUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* id /source 必传一个
|
* id /source 必传一个
|
||||||
|
*
|
||||||
* @param id -非必填 依据fileid删除
|
* @param id -非必填 依据fileid删除
|
||||||
* @param sourceId --非必填 依据资源id删除
|
* @param sourceId --非必填 依据资源id删除
|
||||||
* @param sourceTable --非必填 依据资源id和 表机构id删除
|
* @param sourceTable --非必填 依据资源id和 表机构id删除
|
||||||
|
|
@ -139,6 +145,7 @@ public class FileUploadUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* id /source 必传一个
|
* id /source 必传一个
|
||||||
|
*
|
||||||
* @param id -选择性必填 依据fileid删除
|
* @param id -选择性必填 依据fileid删除
|
||||||
* @param sourceId --选择性必填 依据资源id删除
|
* @param sourceId --选择性必填 依据资源id删除
|
||||||
* @param sourceTable --非必填 依据资源id和 表机构id删除
|
* @param sourceTable --非必填 依据资源id和 表机构id删除
|
||||||
|
|
@ -169,11 +176,19 @@ public class FileUploadUtils {
|
||||||
* 打包文件
|
* 打包文件
|
||||||
*/
|
*/
|
||||||
public DownloadTask zipFile(List<ZipFileMapping> objectNames) {
|
public DownloadTask zipFile(List<ZipFileMapping> objectNames) {
|
||||||
R<DownloadTask> res = service.zipFile(objectNames, SecurityConstants.INNER);
|
String params= JSON.toJSONString(objectNames);
|
||||||
|
R<DownloadTask> res = service.zipFile(params, SecurityConstants.INNER);
|
||||||
if (res.getCode() == R.SUCCESS) {
|
if (res.getCode() == R.SUCCESS) {
|
||||||
return res.getData();
|
return res.getData();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// 工具方法:过滤字符串中的非法控制字符(复用之前的逻辑)
|
||||||
|
public String cleanIllegalChars(String str) {
|
||||||
|
if (str == null) return "";
|
||||||
|
// 匹配ASCII 0-31除\r\n\t的控制字符,替换为下划线
|
||||||
|
return str.replaceAll("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]", "_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.bonus.file.controller;
|
package com.bonus.file.controller;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.bonus.common.core.domain.R;
|
import com.bonus.common.core.domain.R;
|
||||||
|
import com.bonus.common.core.utils.StringUtils;
|
||||||
import com.bonus.file.service.impl.FileUtilsServiceImpl;
|
import com.bonus.file.service.impl.FileUtilsServiceImpl;
|
||||||
import com.bonus.system.api.domain.DownloadTask;
|
import com.bonus.system.api.domain.DownloadTask;
|
||||||
import com.bonus.system.api.domain.FileVo;
|
import com.bonus.system.api.domain.FileVo;
|
||||||
|
|
@ -234,12 +236,12 @@ public class FileUtilController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/zipFile")
|
@PostMapping("/zipFile")
|
||||||
public R<DownloadTask> zipFile(@RequestBody List<ZipFileMapping> objectNames) {
|
public R<DownloadTask> zipFile(@RequestBody String objectNames) {
|
||||||
if (objectNames == null || objectNames.isEmpty()) {
|
if(StringUtils.isEmpty(objectNames)){
|
||||||
log.warn("提示:Received null or empty zip file list");
|
|
||||||
return R.fail("文件列表不能为空");
|
return R.fail("文件列表不能为空");
|
||||||
}
|
}
|
||||||
return R.ok(service.zipFile(objectNames));
|
List<ZipFileMapping> objectNameList = JSON.parseArray(objectNames, ZipFileMapping.class);
|
||||||
|
return R.ok(service.zipFile(objectNameList));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue