最近更新时间:2025-04-29 14:35:02
该方法为生成上传文件的预签名链接,用户可以使用该链接上传文件。
该方法上传的单文件大小不能超过5GB,超过5GB的文件请使用分块上传 。
// 初始化KS3Client,详情请参见文档:https://docs.ksyun.com/documents/40559
Ks3 client = initKs3Client();
GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest();
req.setMethod(HttpMethod.PUT);
// 文件上传的bucket
req.setBucket(bucket);
// 文件名
req.setKey(key);
// 不指定的话则默认为15分钟后过期
req.setExpiration(<生成的外链的过期时间>);
// 设置acl为公开读,不加该header则默认为私有,生成外链时设置了header,则在使用外链的时候也需要添加相应的header
req.getRequestConfig().getExtendHeaders().put("x-kss-acl", "public-read");
// 设置文件的Content-Type,具体值请根据时间情况设定。在使用外链的时候需要把Content-Type设置成指定的值
req.setContentType("application/octet-stream");
// req.setSseAlgorithm("AES256"); //设置服务端加密
String url = client.generatePresignedUrl(req);
当用户拿到该URL之后便可以通过以下代码上传文件。(请自行拆分uri和host)
PUT /{uri} HTTP/1.1
Host: {host}
Date: {当前时间}
x-kss-acl:public-read //因为在生成外链的时候设置了该header,所以在发送的时候也需要带上
Content-Type:application/octet-stream //同上
Content-Length:100
[100 bytes of data]
1. 单文件大小大于5GB时必须采用分块上传。
2. 使用预签名链接时,请注意请求头必须与生成链接时的请求头一致,否则会有签名不一致的问题,原因是部分请求头会参与签名的计算,如:Content-Type
、Content-MD5
、以x-kss-
开头的请求头等。
/**
* 生成列举分块上传任务的预签名链接
*
* @param bucketName 桶名
* @param params 请求参数,如:prefix、delimiter、max-uploads、upload-id-marker、key-marker 等,可为空
* @return 预签名链接
*/
public String generateListMultipartUploadsPresignedUrl(String bucketName, Map<String, String> params) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest();
request.setBucket(bucketName);
if (params != null) {
request.setRequestParameters(params);
}
request.getRequestParameters().put("uploads", "");
// 设置链接有效期为15分钟
request.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
return ks3Client.generatePresignedUrl(request);
}
/**
* 生成列举指定上传任务中所有已上传块的预签名链接
*
* @param bucketName 桶名
* @param objectKey 对象名
* @param uploadId 分块上传 ID
* @param params 请求参数,如:max-parts、part-number-marker 等,可为空
* @return 预签名链接
*/
public String generateListPartsPresignedUrl(String bucketName, String objectKey, String uploadId, Map<String, String> params) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest();
request.setBucket(bucketName);
request.setKey(objectKey);
if (params != null) {
request.setRequestParameters(params);
}
request.getRequestParameters().put("uploadId", uploadId);
// 设置链接有效期为15分钟
request.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
return ks3Client.generatePresignedUrl(request);
}
/**
* 生成初始化分块上传的预签名链接
*
* @param bucketName 桶名
* @param objectKey 对象名
* @param headers 请求头
* @return 预签名链接
*/
public String generateInitMultipartUploadPresignedUrl(String bucketName, String objectKey, Map<String, String> headers) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest();
request.setBucket(bucketName);
request.setKey(objectKey);
request.setMethod(HttpMethod.POST);
request.getRequestParameters().put("uploads", "");
request.getRequestConfig().setExtendHeaders(headers);
// 设置链接有效期为15分钟
request.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
return ks3Client.generatePresignedUrl(request);
}
/**
* 生成上传分块的预签名链接
*
* @param bucketName 桶名
* @param objectKey 对象名
* @param uploadId 分块上传ID
* @param partNumber 分块号
* @return 预签名链接
*/
public String generateUploadPartPresignedUrl(String bucketName, String objectKey, String uploadId, int partNumber) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest();
request.setBucket(bucketName);
request.setKey(objectKey);
request.setMethod(HttpMethod.PUT);
request.getRequestParameters().put("partNumber", String.valueOf(partNumber));
request.getRequestParameters().put("uploadId", uploadId);
// 设置链接有效期为15分钟
request.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
return ks3Client.generatePresignedUrl(request);
}
/**
* 生成完成分块上传的预签名链接
*
* @param bucketName 桶名
* @param objectKey 对象名
* @param uploadId 分块上传 ID
* @return 预签名链接
*/
public String generateCompleteMultipartUploadPresignedUrl(String bucketName, String objectKey, String uploadId) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest();
request.setBucket(bucketName);
request.setKey(objectKey);
request.setMethod(HttpMethod.POST);
request.getRequestParameters().put("uploadId", uploadId);
// 设置请求头,使用该链接时需要带上以下请求头
request.getRequestConfig().getExtendHeaders().put("Content-Type", "application/xml");
// 设置链接有效期为15分钟
request.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
return ks3Client.generatePresignedUrl(request);
}
/**
* 生成终止分块上传任务的预签名链接
*
* @param bucketName 桶名
* @param objectKey 对象名
* @param uploadId 分块上传ID
* @return 预签名链接
*/
public String generateAbortMultipartUploadPresignedUrl(String bucketName, String objectKey, String uploadId) {
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest();
request.setBucket(bucketName);
request.setKey(objectKey);
request.setMethod(HttpMethod.DELETE);
request.getRequestParameters().put("uploadId", uploadId);
request.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
return ks3Client.generatePresignedUrl(request);
}
/**
* 使用预签名链接初始化分块
*
* @param url 预签名链接
* @param headers 请求头
* @param httpClient http客户端
* @return uploadId 分块任务ID
* @throws IOException IO异常
*/
public String initMultipartUpload(String url, Map<String, String> headers, CloseableHttpClient httpClient) throws IOException {
HttpPost httpInit = new HttpPost(url);
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpInit.setHeader(entry.getKey(), entry.getValue());
}
try (CloseableHttpResponse response = httpClient.execute(httpInit)) {
String content = StringUtils.inputStream2String(response.getEntity().getContent());
if (response.getStatusLine().getStatusCode() != 200) {
throw new RuntimeException("Init multipart upload failed, response: " + content);
}
// 解析返回的xml获取uploadId
int startIndex = content.indexOf("<UploadId>");
int endIndex = content.indexOf("</UploadId>");
return content.substring(startIndex + "<UploadId>".length(), endIndex);
}
}
/**
* 使用预签名链接上传分块
*
* @param url 预签名链接
* @param file 文件
* @param partNumber 分块号
* @param partSize 分块大小
* @param httpClient http 客户端
* @return partETag 分块的ETag值
* @throws IOException IO异常
*/
public Map<String, String> uploadPart(String url, File file, int partNumber, long partSize, CloseableHttpClient httpClient) throws IOException {
// 获取文件流
long fileLength = file.length();
long offset = (partNumber - 1) * partSize;
long currentPartSize = Math.min(partSize, fileLength - offset);
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.skip(offset);
// 生成请求体
BasicHttpEntity httpEntity = new BasicHttpEntity();
httpEntity.setContent(fileInputStream);
httpEntity.setContentLength(currentPartSize);
HttpPut httpPut = new HttpPut(url);
httpPut.setEntity(httpEntity);
// 上传分块
try (CloseableHttpResponse response = httpClient.execute(httpPut)) {
if (response.getStatusLine().getStatusCode() != 200) {
String content = "";
if (response.getEntity() != null && response.getEntity().getContent() != null) {
content = StringUtils.inputStream2String(response.getEntity().getContent());
}
throw new RuntimeException("Upload part failed, status code: " + response.getStatusLine().getStatusCode() + ", response: " + content);
}
// 解析响应头获取ETag、crc64
String crc64 = response.getFirstHeader("x-kss-checksum-crc64ecma").getValue();
String eTag = response.getFirstHeader("ETag").getValue();
Map<String, String> partETag = new HashMap<>();
partETag.put("PartNumber", String.valueOf(partNumber));
partETag.put("ETag", eTag);
partETag.put("crc64", crc64);
return partETag;
}
}
/**
* 使用预签名链接完成分块上传
*
* @param url 预签名链接
* @param partETags 分块ETag
* @param httpClient http客户端
* @throws IOException IO异常
*/
public void completeMultipartUpload(String url, List<Map<String, String>> partETags, CloseableHttpClient httpClient) throws IOException {
// 生成 CompleteMultipartUpload 请求的 xml
StringBuilder completeXml = new StringBuilder();
completeXml.append("<CompleteMultipartUpload>");
for (Map<String, String> partETag : partETags) {
completeXml.append("<Part>");
completeXml.append("<PartNumber>").append(partETag.get("PartNumber")).append("</PartNumber>");
completeXml.append("<ETag>").append(partETag.get("ETag")).append("</ETag>");
completeXml.append("<ChecksumCRC64ECMA>").append(partETag.get("crc64")).append("</ChecksumCRC64ECMA>");
completeXml.append("</Part>");
}
completeXml.append("</CompleteMultipartUpload>");
// 设置请求体
byte[] bytes = completeXml.toString().getBytes(StandardCharsets.UTF_8);
int length = bytes.length;
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
BasicHttpEntity httpEntity = new BasicHttpEntity();
httpEntity.setContent(inputStream);
httpEntity.setContentLength(length);
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/xml");
httpPost.setEntity(httpEntity);
// 发送CompleteMultipartUpload请求
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200) {
String content = "";
if (response.getEntity() != null && response.getEntity().getContent() != null) {
content = StringUtils.inputStream2String(response.getEntity().getContent());
}
throw new RuntimeException("Complete multipart upload failed, status code: " + statusCode + ", response: " + content);
}
}
}
/**
* 使用预签名链接分块上传文件的完整示例
*
* @param bucketName 桶名
* @param objectKey 对象名
* @param file 本地文件
*/
public void uploadFileByMultipartUploadPreSignedUrls(String bucketName, String objectKey, File file) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 1. 初始化分块上传
Map<String, String> headers = new HashMap<>();
// 设置存储类型
headers.put("x-kss-storage-class", "STANDARD");
// 设置访问权限
headers.put("x-kss-acl", "private");
// 设置禁止覆盖
headers.put("x-kss-forbid-overwrite", "false");
// 设置标签
headers.put("x-kss-tagging", "key1=value1&key2=value2");
// 设置自定义元数据
headers.put("x-kss-meta-key1", "value1");
// 设置内容类型
headers.put("Content-Type", "text/plain");
// 生成初始化分块上传的预签名链接
String initUrl = generateInitMultipartUploadPresignedUrl(bucketName, objectKey, headers);
// 初始化分块上传,获取uploadId。注意:务必带上生成预签名链接时设置的请求头
String uploadId = initMultipartUpload(initUrl, headers, httpClient);
log.info("Init multipart upload successfully, uploadId: " + uploadId);
// 2. 上传分块
long partSize = 50 * 1024 * 1024;
long fileLength = file.length();
// 计算分块数量
int partCount = (int) (fileLength / partSize);
if (fileLength % partSize != 0) {
partCount++;
}
List<Map<String, String>> partETags = new ArrayList<>();
for (int i = 0; i < partCount; i++) {
// 计算分块号、偏移量、当前分块大小,分块号从 1 开始
int partNumber = i + 1;
long offset = i * partSize;
long currentPartSize = Math.min(partSize, fileLength - offset);
log.info("Uploading part " + partNumber);
// 生成上传分块的预签名链接
String uploadPartPresignedUrl = generateUploadPartPresignedUrl(bucketName, objectKey, uploadId, partNumber);
// 上传分块,获取partETag
Map<String, String> partETag = uploadPart(uploadPartPresignedUrl, file, partNumber, currentPartSize, httpClient);
log.info("Upload part " + partNumber + " successfully");
partETags.add(partETag);
}
// 3. 完成分块上传
String completeUrl = generateCompleteMultipartUploadPresignedUrl(bucketName, objectKey, uploadId);
completeMultipartUpload(completeUrl, partETags, httpClient);
log.info("Upload file by multipart upload pre-signed urls successfully");
} catch (IOException e) {
log.error("Upload file by multipart upload pre-signed urls failed", e);
}
}
纯净模式