s3依次拉取文件下载到客户端
废话不说,直接上代码:
使用zip方式下载和tar压缩方式下载
package com.lenovo.controller;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* desc:交付物接口
*
* @author qinzl2
* @date 2020/11/11
*/
@RestController
@RequestMapping("/test/download")
public class DownloadController {
private static final Logger logger = LoggerFactory.getLogger(DownloadController.class);
@Autowired
private AmazonS3 amazonS3;
/**
* 接收anywhere_delivery job 的回调,更新delivery的stage
*/
@RequestMapping(value = "zip")
public void zip(HttpServletResponse response) throws UnsupportedEncodingException {
// 清空response
response.reset();
String fileName = URLEncoder.encode("安裝包.zip", StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("APPLICATION/zip");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
// 设置response的Header
try (OutputStream out = response.getOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(out);
WritableByteChannel destChannel = Channels.newChannel(zipOut)) {
String bucketName = "scello";
String fileKey = "ansible-deploy-playbook/53d72cf64d3ef43f60a22461cc0e49bc";
writeFile(destChannel, zipOut, bucketName, fileKey, "11111.sh");
fileKey = "ansible-deploy-playbook/5dfd961a8a121f6cba4093a7b802012c";
writeFile(destChannel, zipOut, bucketName, fileKey, "22222.sh");
fileKey = "sql/137cb351039ce656ba16070e5a201d18";
writeFile(destChannel, zipOut, bucketName, fileKey, "flyway.config");
zipOut.flush();
zipOut.finish();
} catch (Exception e) {
logger.error("download file error.", e);
}
}
private void writeFile(WritableByteChannel destChannel, ZipOutputStream zipOut, String bucketName, String fileKey, String fileName) throws IOException {
S3Object object = amazonS3.getObject(new GetObjectRequest(bucketName, fileKey));
try (InputStream s3ObjectInputStream = object.getObjectContent();
ReadableByteChannel sourceChannel = Channels.newChannel(s3ObjectInputStream)) {
zipOut.putNextEntry(new ZipEntry(fileName));
channelCopy(sourceChannel, destChannel);
zipOut.closeEntry();
} catch (IOException e) {
throw e;
}
}
/**
* 接收anywhere_delivery job 的回调,更新delivery的stage
*/
@RequestMapping(value = "tar")
public void tar(HttpServletResponse response) throws UnsupportedEncodingException {
// 清空response
response.reset();
String fileName = URLEncoder.encode("安裝包.tar.gz", StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("APPLICATION/zip");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
// 设置response的Header
try (OutputStream out = response.getOutputStream();
BufferedOutputStream buffOut = new BufferedOutputStream(out);
GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(buffOut);
TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzOut);
WritableByteChannel destChannel = Channels.newChannel(tarOut)) {
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
tarOut.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
String bucketName = "scello";
String fileKey = "ansible-deploy-playbook/53d72cf64d3ef43f60a22461cc0e49bc";
writeTarFile(destChannel, tarOut, bucketName, fileKey, "11111.sh");
fileKey = "ansible-deploy-playbook/5dfd961a8a121f6cba4093a7b802012c";
writeTarFile(destChannel, tarOut, bucketName, fileKey, "22222.sh");
fileKey = "sql/137cb351039ce656ba16070e5a201d18";
writeTarFile(destChannel, tarOut, bucketName, fileKey, "flyway.config");
// tarOut.closeArchiveEntry();
tarOut.flush();
tarOut.finish();
} catch (Exception e) {
logger.error("download file error.", e);
}
}
private void writeTarFile(WritableByteChannel destChannel, TarArchiveOutputStream tarOut, String bucketName, String fileKey, String fileName) throws IOException {
S3Object object = amazonS3.getObject(new GetObjectRequest(bucketName, fileKey));
try (InputStream s3ObjectInputStream = object.getObjectContent();
ReadableByteChannel sourceChannel = Channels.newChannel(s3ObjectInputStream)) {
long size = object.getObjectMetadata().getInstanceLength();
TarArchiveEntry tarEntry = new TarArchiveEntry("/"+fileName);
tarEntry.setSize(size);
tarOut.putArchiveEntry(tarEntry);
channelCopy(sourceChannel, destChannel);
tarOut.closeArchiveEntry();
} catch (IOException e) {
throw e;
}
}
private void channelCopy(ReadableByteChannel src, WritableByteChannel dest) throws IOException {
ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024);//TODO 目前先使用这种方式申请对外内存,以后有时间弄一个资源池,10k
while (src.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
dest.write(buffer);
}
buffer.clear();
}
}
}
可以加上异常处理代码
package com.knowdee.server.share.controler;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
/**
* desc:交付物接口
*
* @author qinzl2
* @since 2024/04/10
*/
@RestController
@RequestMapping("/download")
@Slf4j
public class DownloadController {
private static final String splitString = "/";
@Autowired
private AmazonS3 amazonS3;
@GetMapping(value = "/schema/{entity}/{version}/{fileName}")
public void download(HttpServletResponse response, @PathVariable("entity") String entity, @PathVariable("version") String version, @PathVariable("fileName") String fileName) throws IOException {
String bucketName = "cyxinda";
String fileKey = "schema" + splitString + entity + splitString + version + splitString + fileName;
try(
S3Object object = amazonS3.getObject(new GetObjectRequest(bucketName, fileKey));
OutputStream out = response.getOutputStream();
WritableByteChannel destinationChannel = Channels.newChannel(out);
InputStream s3ObjectInputStream = object.getObjectContent();
ReadableByteChannel sourceChannel = Channels.newChannel(s3ObjectInputStream)) {
// 清空response
// 设置response的Header,会影响前端浏览器的行为;如果设置,发生错误的化,仍然会弹出下载页面,不利于错误信息提示。
// 比如,如果把Content-Disposition和setContentType去掉后,不会下载文件;会在浏览器上面看到文件内容;详情见:https://zhuanlan.zhihu.com/p/682763203
response.reset();
String name = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
response.setHeader("Content-Disposition", "attachment;filename=" + name);
response.setContentType("APPLICATION/zip");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
channelCopy(sourceChannel, destinationChannel);
out.flush();
} catch (Exception e) {
log.error("download file error.", e);
response.sendError(HttpServletResponse.SC_NOT_FOUND, "error......");
}
}
private void channelCopy(ReadableByteChannel src, WritableByteChannel destinationChannel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024);
while (src.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
destinationChannel.write(buffer);
}
buffer.clear();
}
}
}
另一种更好的写法:
package com.knowdee.server.share.controler;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* desc:交付物接口
*
* @author qinzl2
* @since 2024/04/10
*/
@RestController
@RequestMapping("/download")
@Slf4j
public class DownloadController {
private static final String splitString = "/";
@Autowired
private AmazonS3 amazonS3;
@GetMapping(value = "/schema/{entity}/{version}/{fileName}")
public ResponseEntity<InputStreamResource> download(@PathVariable("entity") String entity, @PathVariable("version") String version, @PathVariable("fileName") String fileName) throws IOException {
// 清空response
// 设置response的Header
String bucketName = "cyxinda";
String fileKey = "schema" + splitString + entity + splitString + version + splitString + fileName;
return writeFile(bucketName, fileKey);
}
private ResponseEntity<InputStreamResource> writeFile(String bucketName, String fileKey) throws IOException {
try {
S3Object object = amazonS3.getObject(new GetObjectRequest(bucketName, fileKey));
InputStream s3ObjectInputStream = object.getObjectContent();
InputStreamResource resource = new InputStreamResource(s3ObjectInputStream);
if (resource.exists() || resource.isReadable()) {
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(resource);
} else {
return ResponseEntity.badRequest().body(stringToInputStreamResource("File not found!"));
}
} catch (SdkClientException e) {
log.error("download file error.", e);
Resource page = new ClassPathResource("templates/page-404.html");
InputStreamResource inputStreamResource = new InputStreamResource(page.getInputStream());
return ResponseEntity.status(404).contentType(MediaType.TEXT_HTML).body(inputStreamResource);
// return ResponseEntity.status(404).body(stringToInputStreamResource("An error occurred while processing your request."));
}
}
private InputStreamResource stringToInputStreamResource(String str) {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
return new InputStreamResource(byteArrayInputStream);
}
}
src/main/resources/templates/page-404.html的内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Download Error</title>
</head>
<body>
File not found
</body>
</html>