天天看點

Amazon S3 使用Java sdk實作檔案分段上傳 背景+前端Vue背景前端效果注意事項

背景

官方文檔

一,s3 sdk 依賴

<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk</artifactId>
	<version>1.11.126</version>
</dependency>
           

二,上傳工具類

package test.s3;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class AmazonS3Util {

    /**
     * S3伺服器位址
     */
    private final static String S3_URL = "";

    /**
     * 賬号key
     */
    private final static String ACCESS_KEY = "";

    /**
     * 密碼key
     */
    private final static String SECRET_KEY = "";

    /**
     * 存放合并資訊集合 如果服務是叢集或多活部署,需改成redis存儲
     */
    private final static Map<String, Map<String, Object>> uploadIdMap = new ConcurrentHashMap<>();

    private static Logger logger = LoggerFactory.getLogger(AmazonS3Util.class);

    /**
     * 擷取連接配接
     * @return
     * @throws Exception
     */
    public static AmazonS3 connectS3Client() throws Exception {
        URL url = new URL(S3_URL);
        ClientConfiguration config = new ClientConfiguration().withProtocol(Protocol.HTTP);
        AmazonS3 client = AmazonS3ClientBuilder.standard()
                .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(url.toString(),Regions.CN_NORTH_1.toString()))
                .withClientConfiguration(config)
                .withPathStyleAccessEnabled(true)
                .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY)))
                .build();
        return client;
    }

    /**
     * 上傳檔案
     * @param upload
     * @return
     * @throws Exception
     */
    public static void uploadFile(Upload upload) throws Exception {
        AmazonS3 s3client = connectS3Client();
        InputStream inputStream = null;
        try {
            String fileKey = getFileKey(upload.getMultipartFile().getOriginalFilename());
            inputStream = upload.getMultipartFile().getInputStream();
            PutObjectRequest request = new PutObjectRequest(upload.getBucketName(), fileKey, inputStream,null);
            s3client.putObject(request);
        } catch (Exception e) {
            throw e;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    throw e;
                }
            }
        }
    }

    /**
     * 分段上傳檔案
     * @param upload
     * @return
     * @throws Exception
     */
    public static String mutilpartUpload(Upload upload) throws Exception {
        logger.info("開始上傳");
        AmazonS3 s3client = connectS3Client();
        InputStream inputStream = null;
        try {
            String fileKey = null;
            String uploadId = null;
            List<PartETag> partETagList = new ArrayList<>();
            Map<String, Object> result = uploadIdMap.get(upload.getIdentifier());
            if (result == null) {
                fileKey = getFileKey(upload.getMultipartFile().getOriginalFilename());
                InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(upload.getBucketName(), fileKey);
                InitiateMultipartUploadResult initResponse = s3client.initiateMultipartUpload(initRequest);
                uploadId = initResponse.getUploadId();
                result = new HashMap<>();
                result.put("uploadId", uploadId);
                result.put("partETagList", partETagList);
                result.put("fileKey", fileKey);
            } else {
                uploadId = result.get("uploadId").toString();
                partETagList = (List<PartETag>) result.get("partETagList");
                fileKey = result.get("fileKey").toString();
            }

            // 分段上傳
            inputStream = upload.getMultipartFile().getInputStream();
            UploadPartRequest uploadRequest = new UploadPartRequest()
                    .withBucketName(upload.getBucketName())
                    .withKey(fileKey)
                    .withUploadId(uploadId)
                    .withPartNumber(upload.getChunkNumber())
                    .withInputStream(inputStream)
                    .withPartSize(upload.getMultipartFile().getSize());
            UploadPartResult uploadResult = s3client.uploadPart(uploadRequest);

            partETagList.add(uploadResult.getPartETag());

            logger.info("上傳完成: upload.getTotalChunks():{}, upload.getChunkNumber():{}", upload.getTotalChunks(), upload.getChunkNumber());
            if (upload.getTotalChunks() == upload.getChunkNumber()) {
                logger.info("開始合并");
                CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(upload.getBucketName(), fileKey, uploadId, partETagList);
                s3client.completeMultipartUpload(compRequest);
                // 删除map資料
                uploadIdMap.remove(upload.getIdentifier());
            } else {
                // 更新map資料
                uploadIdMap.put(upload.getIdentifier(), result);
            }
            return fileKey;
        } catch (Exception e) {
            throw e;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    throw e;
                }
            }
        }
    }

    /**
     * 下載下傳
     * @param bucketName
     * @param fileKey
     * @return
     * @throws Exception
     */
    public static InputStream downloadFile(String bucketName, String fileKey) throws Exception{
        AmazonS3 s3client = connectS3Client();
        GetObjectRequest request = new GetObjectRequest(bucketName, fileKey);
        S3Object response = s3client.getObject(request);
        InputStream input = response.getObjectContent();
        return input;
    }

    /**
     * 删除檔案
     * @param bucketName
     * @param fileKey
     * @throws Exception
     */
    public static void deleteFile(String bucketName, String fileKey) throws Exception {
        AmazonS3 s3client = connectS3Client();
        DeleteObjectRequest request = new DeleteObjectRequest(bucketName, fileKey);
        s3client.deleteObject(request);
    }

    /**
     * Bucket清單
     * @return
     * @throws Exception
     */
    public static List<Bucket> listFile() throws  Exception{
        AmazonS3 s3client = connectS3Client();
        ListBucketsRequest request = new ListBucketsRequest();
        List<Bucket> list = s3client.listBuckets(request);
        return list;
    }

    /**
     * 是否存在Bucket
     * @param bucketName
     * @return
     * @throws Exception
     */
    public static boolean isExistBucket(String bucketName) throws Exception{
        AmazonS3 s3client = connectS3Client();
        try {
            HeadBucketRequest request = new HeadBucketRequest(bucketName);
            s3client.headBucket(request);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 建立Bucket
     * @param bucketName
     * @return
     * @throws Exception
     */
    public static void createBucket(String bucketName) throws Exception{
        boolean isBucketExists = isExistBucket(bucketName);
        AmazonS3 s3client = null;
        if (!isBucketExists) {
            s3client = connectS3Client();
            CreateBucketRequest request = new CreateBucketRequest(bucketName);
            s3client.createBucket(request);
        }
    }

    /**
     * 删除Bucket
     * @param bucketName
     * @return
     * @throws Exception
     */
    public static void deleteBucket(String bucketName) throws Exception {
        AmazonS3 s3client = connectS3Client();
        DeleteBucketRequest request = new DeleteBucketRequest(bucketName);
        s3client.deleteBucket(request);
    }

    /**
     * fileKey是否存在
     * @param bucketName
     * @param fileKey
     * @return
     * @throws Exception
     */
    public static boolean isExistFileKey(String bucketName, String fileKey) throws  Exception{
        AmazonS3 s3client = connectS3Client();
        GetObjectRequest request = new GetObjectRequest(bucketName,fileKey);
        S3Object response = s3client.getObject(request);
        boolean result =  response != null &&  fileKey.equals(response.getKey());
        return result;
    }

    /**
     * 擷取檔案key
     * @param fileName
     * @return
     * @throws Exception
     */
    private static String getFileKey(String fileName) throws Exception{
        String uuid = UUID.randomUUID().toString().replaceAll("-","");
        String dateDir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String fileKey = dateDir + "/" + uuid + "/" + fileName;
        return fileKey;
    }

}

           

封裝參數實體類

package test.s3;

import org.springframework.web.multipart.MultipartFile;

public class Upload {

	/**
	 * 桶名稱
	 */
	private String bucketName;

	/**
	 * 總塊數
	 */
	private int totalChunks;

	/**
	 * 目前塊數,從1開始
	 */
	private int chunkNumber;

	/**
	 * 上傳id,每個檔案唯一
	 */
	private String identifier;

	/**
	 * 上傳檔案資訊, 需手動指派
	 */
    private MultipartFile multipartFile;

	public int getTotalChunks() {
		return totalChunks;
	}

	public String getBucketName() {
		return bucketName;
	}

	public void setBucketName(String bucketName) {
		this.bucketName = bucketName;
	}

	public void setTotalChunks(int totalChunks) {
		this.totalChunks = totalChunks;
	}

	public int getChunkNumber() {
		return chunkNumber;
	}

	public void setChunkNumber(int chunkNumber) {
		this.chunkNumber = chunkNumber;
	}

	public String getIdentifier() { return identifier; }

	public void setIdentifier(String identifier) { this.identifier = identifier; }

	public MultipartFile getMultipartFile() {
		return multipartFile;
	}

	public void setMultipartFile(MultipartFile multipartFile) { this.multipartFile = multipartFile; }
}

           

控制類

package test.s3;

import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;

@RestController
@RequestMapping("/s3")
public class FileS3Controller {

	@ApiOperation("上傳")
	@RequestMapping(method = RequestMethod.POST, value = "/upload")
	public String upload(Upload upload, MultipartFile file) throws Exception {
		upload.setMultipartFile(file);
		return AmazonS3Util.mutilpartUpload(upload);
	}

	@ApiOperation("下載下傳")
	@RequestMapping(method = RequestMethod.GET, value = "/download")
	public void download(String bucketName, String fileKey, String fileName, HttpServletResponse response) throws Exception {
		InputStream inputStream = AmazonS3Util.downloadFile(bucketName, fileKey);

		response.setContentType( "application/x-msdownload");
		fileName = new String(fileName.getBytes("gbk"), "iso8859-1");
		response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
		ServletOutputStream out = response.getOutputStream();
		byte[] bffer = new byte[1024];
		int r = 0;
		while ((r = inputStream.read(bffer, 0, 1024)) != -1) {
			out.write(bffer, 0, r);
		}
		try {
			inputStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@ApiOperation("删除")
	@RequestMapping(value = "/delete", method = RequestMethod.GET)
	public String delete(String bucketName, String fileKey) throws Exception {
		AmazonS3Util.deleteFile(bucketName, fileKey);
		return  "删除成功";
	}
}

           

前端

官方文檔

一,元件

npm install vue-simple-uploader --save
           

二,main.js

import Vue from 'vue'
import App from './App.vue'
import uploader from 'vue-simple-uploader'

Vue.config.productionTip = false
Vue.use(uploader)

new Vue({
  render: h => h(App),
}).$mount('#app')

           

三,App.vue

<template>
  <uploader :options="options" class="uploader-example">
    <uploader-unsupport></uploader-unsupport>
    <uploader-drop>
      <p>Drop files here to upload or</p>
      <uploader-btn>select files</uploader-btn>
      <uploader-btn :attrs="attrs">select images</uploader-btn>
      <uploader-btn :directory="true">select folder</uploader-btn>
    </uploader-drop>
    <uploader-list></uploader-list>
  </uploader>
</template>

<script>
  export default {
    data () {
      return {
        options: {
          target: '//localhost:8082/s3/upload',
          query: { // 格外參數
              bucketName: 'test'
          },
          chunkSize: 10*1024*1024, // 分塊大小
          forceChunkSize: true, // 是否強制所有的塊都是小于等于 chunkSize 的值。預設是 false
          simultaneousUploads: 1, // 并發上傳數,預設 3
          maxChunkRetries: 3, // 最大自動失敗重試上傳次數,值可以是任意正整數,如果是 undefined 則代表無限次,預設 0
          testChunks: false
        },
        attrs: {
          accept: 'image/*'
        }
      }
    }
  }
</script>

<style>
  .uploader-example {
    width: 880px;
    padding: 15px;
    margin: 40px auto 0;
    font-size: 12px;
    box-shadow: 0 0 10px rgba(0, 0, 0, .4);
  }
  .uploader-example .uploader-btn {
    margin-right: 4px;
  }
  .uploader-example .uploader-list {
    max-height: 440px;
    overflow: auto;
    overflow-x: hidden;
    overflow-y: auto;
  }
</style>
           

效果

Amazon S3 使用Java sdk實作檔案分段上傳 背景+前端Vue背景前端效果注意事項

注意事項

1、以上代碼不支援并發上傳。并發上傳後續再研究解決

2、如果服務是多活部署(叢集部署),AmazonS3Util 工具類裡的uploadIdMap要換成redis存儲