新增 minio-s3-api 模块,该模块定义了MinIO S3 标准接口,目前设计了两个实现,一个是基于 MinIO 官方SDK的实现,一个是自定义实现。

This commit is contained in:
刘小平 2024-06-04 11:08:56 +08:00
parent 112aba97da
commit 701a6be443
16 changed files with 290 additions and 356 deletions

View File

@ -1,317 +0,0 @@
//import cn.hutool.core.collection.CollUtil;
//import cn.hutool.core.util.StrUtil;
//import cn.hutool.http.HttpRequest;
//import cn.hutool.http.HttpResponse;
//import cn.hutool.http.HttpUtil;
//import cn.hutool.http.Method;
//import com.google.common.base.Joiner;
//import com.google.common.collect.ImmutableSet;
//import com.google.common.collect.Multimap;
//import com.google.common.collect.MultimapBuilder;
//import com.google.common.io.BaseEncoding;
//import io.minio.PartSource;
//import io.minio.Signer;
//import io.minio.Xml;
//import io.minio.credentials.Credentials;
//import io.minio.errors.*;
//import io.minio.http.HttpUtils;
//import io.minio.messages.ErrorResponse;
//import okhttp3.*;
//
//import javax.crypto.Mac;
//import javax.crypto.spec.SecretKeySpec;
//import java.io.IOException;
//import java.io.PrintWriter;
//import java.nio.charset.StandardCharsets;
//import java.security.InvalidKeyException;
//import java.security.NoSuchAlgorithmException;
//import java.time.ZonedDateTime;
//import java.util.*;
//import java.util.stream.Collectors;
//
//public class MinIORestful2 {
//
// static final String serviceName = "s3";
// static final String US_EAST_1 = "us-east-1";
// private static final Set<String> IGNORED_HEADERS = ImmutableSet.of("accept-encoding", "authorization", "content-type", "content-length", "user-agent");
//
// static String accessKey = "minioadmin";
// static String secretKey = "minioadmin";
// static String ZERO_MD5_HASH = "1B2M2Y8AsgTpgAmY7PhCfg==";
// static String ZERO_SHA256_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
// static String backend = "http://localhost:9000";
//
// public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
//
// // 动态传入参数
// String url = "http://localhost:9000/document";
//
// HttpResponse httpResponse = request(url,"","/document","head");
//
// System.out.println("httpResponse.isOk()="+httpResponse.isOk());
// System.out.println("httpResponse.getStatus()="+httpResponse.getStatus());
// System.out.println("httpResponse.headers()="+httpResponse.headers());
// System.out.println("httpResponse.body()="+httpResponse.body());
//
// }
//
// public static HttpResponse request(String url,String params,String path,String method) throws NoSuchAlgorithmException, InvalidKeyException {
//
// // 取得当前时间
// ZonedDateTime date = ZonedDateTime.now();
//
// // 创建除Authorization外所有header
// Map<String, List<String>> headers = new HashMap<>();
// headers.put("Host", CollUtil.newArrayList(backend.replace("http://","").replace("https://","")));
// headers.put("Accept-Encoding", CollUtil.newArrayList("identity"));
// headers.put("User-Agent", CollUtil.newArrayList("MinIO (Windows 10; amd64) minio-java/8.3.3"));
// headers.put("Content-MD5", CollUtil.newArrayList(ZERO_MD5_HASH));
// headers.put("x-amz-content-sha256", CollUtil.newArrayList(ZERO_SHA256_HASH));
// headers.put("x-amz-date", CollUtil.newArrayList(date.format(Time.AMZ_DATE_FORMAT)));
//
// // 计算scope
// String scope =buildScope(serviceName,date);
//
// // 计算signedHeaders
// Map<String, String> canonicalHeaders = buildCanonicalHeaders(headers,IGNORED_HEADERS);
//
// // 计算signedHeaders
// String signedHeaders = buildSignedHeaders(canonicalHeaders);
//
// // 计算canonicalQueryString
// String canonicalQueryString = buildCanonicalQueryString(params);
//
// // 计算buildCanonicalRequest
// String canonicalRequestHash = buildCanonicalRequest(canonicalHeaders,signedHeaders,canonicalQueryString,method,path);
//
// // 计算stringToSign
// String stringToSign = buildStringToSign(date,scope,canonicalRequestHash);
//
// // 计算signingKey
// byte[] signingKey = buildSigningKey(serviceName,date);
//
// // 计算signature
// String signature = buildSignature(signingKey,stringToSign);
//
// // 计算authorization
// String authorization = buildAuthorization(accessKey,scope,signedHeaders,signature);
//
// HttpRequest httpRequest = HttpUtil.createRequest(Method.HEAD, url);
// httpRequest.header(headers,true);
// httpRequest.header("Authorization", authorization);
//
//
//
// System.out.println("scope="+scope);
// System.out.println("canonicalHeaders="+canonicalHeaders);
// System.out.println("signedHeaders="+signedHeaders);
// System.out.println("canonicalQueryString="+canonicalQueryString);
// System.out.println("canonicalRequestHash="+canonicalRequestHash);
// System.out.println("stringToSign="+stringToSign);
// System.out.println("signingKey="+signingKey);
// System.out.println("signature="+signature);
// System.out.println("authorization="+authorization);
//
// System.out.println("httpRequest.headers()="+httpRequest.headers());
//
// return httpRequest.execute();
// }
//
//
// protected void execute(
// io.minio.http.Method method,
// String bucketName,
// String objectName,
// String region,
// Headers headers,
// Multimap<String, String> queryParamMap,
// Object body,
// int length)
// throws ErrorResponseException, InsufficientDataException, InternalException,
// InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
// ServerException, XmlParserException {
// boolean traceRequestBody = false;
// if (body != null && !(body instanceof PartSource || body instanceof byte[])) {
// byte[] bytes;
// if (body instanceof CharSequence) {
// bytes = body.toString().getBytes(StandardCharsets.UTF_8);
// } else {
// bytes = Xml.marshal(body).getBytes(StandardCharsets.UTF_8);
// }
//
// body = bytes;
// length = bytes.length;
// traceRequestBody = true;
// }
//
// if (body == null && (method == io.minio.http.Method.PUT || method == io.minio.http.Method.POST))
// body = HttpUtils.EMPTY_BODY;
//
// HttpUrl url = buildUrl(method, bucketName, objectName, region, queryParamMap);
// Credentials creds = (provider == null) ? null : provider.fetch();
// Request request = createRequest(url, method, headers, body, length, creds);
// if (creds != null) {
// request =
// Signer.signV4S3(
// request,
// region,
// creds.accessKey(),
// creds.secretKey(),
// request.header("x-amz-content-sha256"));
// }
//
// StringBuilder traceBuilder =
// newTraceBuilder(
// request, traceRequestBody ? new String((byte[]) body, StandardCharsets.UTF_8) : null);
// PrintWriter traceStream = this.traceStream;
// if (traceStream != null) traceStream.println(traceBuilder.toString());
// traceBuilder.append("\n");
//
// OkHttpClient httpClient = this.httpClient;
// if (!(body instanceof byte[]) && (method == io.minio.http.Method.PUT || method == io.minio.http.Method.POST)) {
// // Issue #924: disable connection retry for PUT and POST methods for other than byte array.
// httpClient = this.httpClient.newBuilder().retryOnConnectionFailure(false).build();
// }
//
// Response response = httpClient.newCall(request).execute();
//// String trace =
//// response.protocol().toString().toUpperCase(Locale.US)
//// + " "
//// + response.code()
//// + "\n"
//// + response.headers();
//// traceBuilder.append(trace).append("\n");
//// if (traceStream != null) traceStream.println(trace);
////
//// if (response.isSuccessful()) {
//// if (traceStream != null) {
//// // Trace response body only if the request is not GetObject/ListenBucketNotification S3 API.
//// Set<String> keys = queryParamMap.keySet();
//// if ((method != io.minio.http.Method.GET
//// || objectName == null
//// || !Collections.disjoint(keys, TRACE_QUERY_PARAMS))
//// && !(keys.contains("events") && (keys.contains("prefix") || keys.contains("suffix")))) {
//// ResponseBody responseBody = response.peekBody(1024 * 1024);
//// traceStream.println(responseBody.string());
//// }
//// traceStream.println(END_HTTP);
//// }
//// return response;
//// }
////
//// String errorXml = null;
//// try (ResponseBody responseBody = response.body()) {
//// errorXml = responseBody.string();
//// }
////
//// if (!("".equals(errorXml) && method.equals(io.minio.http.Method.HEAD))) {
//// traceBuilder.append(errorXml).append("\n");
//// if (traceStream != null) traceStream.println(errorXml);
//// }
////
//// traceBuilder.append(END_HTTP).append("\n");
//// if (traceStream != null) traceStream.println(END_HTTP);
////
//// // Error in case of Non-XML response from server for non-HEAD requests.
//// String contentType = response.headers().get("content-type");
//// if (!method.equals(io.minio.http.Method.HEAD)
//// && (contentType == null
//// || !Arrays.asList(contentType.split(";")).contains("application/xml"))) {
//// throw new InvalidResponseException(
//// response.code(),
//// contentType,
//// errorXml.substring(0, errorXml.length() > 1024 ? 1024 : errorXml.length()),
//// traceBuilder.toString());
//// }
////
//// ErrorResponse errorResponse = null;
//// if (!"".equals(errorXml)) {
//// errorResponse = Xml.unmarshal(ErrorResponse.class, errorXml);
//// } else if (!method.equals(io.minio.http.Method.HEAD)) {
//// throw new InvalidResponseException(
//// response.code(), contentType, errorXml, traceBuilder.toString());
//// }
////
//// if (errorResponse == null) {
//// String code = null;
//// String message = null;
//// switch (response.code()) {
//// case 301:
//// case 307:
//// case 400:
//// String[] result = handleRedirectResponse(method, bucketName, response, true);
//// code = result[0];
//// message = result[1];
//// break;
//// case 404:
//// if (objectName != null) {
//// code = "NoSuchKey";
//// message = "Object does not exist";
//// } else if (bucketName != null) {
//// code = NO_SUCH_BUCKET;
//// message = NO_SUCH_BUCKET_MESSAGE;
//// } else {
//// code = "ResourceNotFound";
//// message = "Request resource not found";
//// }
//// break;
//// case 501:
//// case 405:
//// code = "MethodNotAllowed";
//// message = "The specified method is not allowed against this resource";
//// break;
//// case 409:
//// if (bucketName != null) {
//// code = NO_SUCH_BUCKET;
//// message = NO_SUCH_BUCKET_MESSAGE;
//// } else {
//// code = "ResourceConflict";
//// message = "Request resource conflicts";
//// }
//// break;
//// case 403:
//// code = "AccessDenied";
//// message = "Access denied";
//// break;
//// case 412:
//// code = "PreconditionFailed";
//// message = "At least one of the preconditions you specified did not hold";
//// break;
//// case 416:
//// code = "InvalidRange";
//// message = "The requested range cannot be satisfied";
//// break;
//// default:
//// if (response.code() >= 500) {
//// throw new ServerException(
//// "server failed with HTTP status code " + response.code(), traceBuilder.toString());
//// }
////
//// throw new InternalException(
//// "unhandled HTTP code "
//// + response.code()
//// + ". Please report this issue at "
//// + "https://github.com/minio/minio-java/issues",
//// traceBuilder.toString());
//// }
////
//// errorResponse =
//// new ErrorResponse(
//// code,
//// message,
//// bucketName,
//// objectName,
//// request.url().encodedPath(),
//// response.header("x-amz-request-id"),
//// response.header("x-amz-id-2"));
//// }
////
//// // invalidate region cache if needed
//// if (errorResponse.code().equals(NO_SUCH_BUCKET) || errorResponse.code().equals(RETRY_HEAD)) {
//// regionCache.remove(bucketName);
//// }
////
//// throw new ErrorResponseException(errorResponse, response, traceBuilder.toString());
// }
//
//}

View File

@ -85,7 +85,9 @@ public class MinioRepositoryImpl implements MinioRepository {
@Override
public ObjectWriteResponse completeMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate) {
try {
return this.getClient().completeMultipartUpload(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion(), multipartUploadCreate.getObjectName(), multipartUploadCreate.getUploadId(), multipartUploadCreate.getParts(), multipartUploadCreate.getHeaders(), multipartUploadCreate.getExtraQueryParams());
return this.getClient().completeMultipartUpload(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion()
, multipartUploadCreate.getObjectName(), multipartUploadCreate.getUploadId(), multipartUploadCreate.getParts(), multipartUploadCreate.getHeaders()
, multipartUploadCreate.getExtraQueryParams());
} catch (Exception e) {
log.error(MinioPlusErrorCode.COMPLETE_MULTIPART_FAILED.getMessage()+",uploadId:{},ObjectName:{},失败原因:{},", multipartUploadCreate.getUploadId(), multipartUploadCreate.getObjectName(), e.getMessage(), e);
throw new MinioPlusException(MinioPlusErrorCode.COMPLETE_MULTIPART_FAILED);

View File

@ -3,14 +3,19 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>minio-plus-api-wrapper</artifactId>
<artifactId>minio-s3-api</artifactId>
<groupId>org.liuxp</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>minio-plus-api-wrapper-custom</artifactId>
<artifactId>minio-s3-api-custom</artifactId>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,21 +1,16 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.io.BaseEncoding;
import io.minio.Digest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.*;
@ -25,7 +20,10 @@ public class MinIORestful {
static final String serviceName = "s3";
static final String US_EAST_1 = "us-east-1";
private static final Set<String> IGNORED_HEADERS = ImmutableSet.of("accept-encoding", "authorization", "content-type", "content-length", "user-agent");
private static final Set<String> IGNORED_HEADERS = CollUtil.set(true,"accept-encoding", "authorization", "content-type", "content-length", "user-agent");
static String accessKey = "minioadmin";
static String secretKey = "minioadmin";
@ -35,6 +33,8 @@ public class MinIORestful {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
// 动态传入参数
String url = "http://localhost:9000/document";
@ -146,7 +146,7 @@ public class MinIORestful {
* @return
*/
public static String buildSignedHeaders(Map<String, String> canonicalHeaders) {
return Joiner.on(";").join(canonicalHeaders.keySet());
return StrUtil.join(";", canonicalHeaders.keySet());
}
public static String buildCanonicalQueryString(String params){
@ -155,31 +155,51 @@ public class MinIORestful {
return "";
}
return params;
//TODO
// MapUtil
// Building a multimap which only order keys, ordering values is not performed
// until MinIO server supports it.
Multimap<String, String> signedQueryParams =
MultimapBuilder.treeKeys().arrayListValues().build();
for (String queryParam : params.split("&")) {
String[] tokens = queryParam.split("=");
if (tokens.length > 1) {
signedQueryParams.put(tokens[0], tokens[1]);
} else {
signedQueryParams.put(tokens[0], "");
}
}
return Joiner.on("&").withKeyValueSeparator("=").join(signedQueryParams.entries());
// Multimap<String, String> signedQueryParams =
// MultimapBuilder.treeKeys().arrayListValues().build();
//
// for (String queryParam : params.split("&")) {
// String[] tokens = queryParam.split("=");
// if (tokens.length > 1) {
// signedQueryParams.put(tokens[0], tokens[1]);
// } else {
// signedQueryParams.put(tokens[0], "");
// }
// }
//
// return Joiner.on("&").withKeyValueSeparator("=").join(signedQueryParams.entries());
}
public static String buildCanonicalRequest(Map<String, String> canonicalHeaders,String signedHeaders
,String canonicalQueryString,String method,String path) throws NoSuchAlgorithmException {
StringBuilder headers = new StringBuilder();
for (String key : canonicalHeaders.keySet()) {
headers.append(key);
headers.append(":");
headers.append(canonicalHeaders.get(key));
headers.append("\n");
}
String canonicalRequest = method + "\n" + path + "\n" + canonicalQueryString + "\n"
+ Joiner.on("\n").withKeyValueSeparator(":").join(canonicalHeaders) + "\n\n" + signedHeaders + "\n" + ZERO_SHA256_HASH;
+ headers + "\n" + signedHeaders + "\n" + ZERO_SHA256_HASH;
System.out.println("canonicalRequest="+canonicalRequest);
return Digest.sha256Hash(canonicalRequest);
byte[] data = canonicalRequest.getBytes(StandardCharsets.UTF_8);
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
sha256Digest.update(data, 0, data.length);
return HexUtil.encodeHexStr(sha256Digest.digest());
}
/**
@ -217,7 +237,7 @@ public class MinIORestful {
*/
public static String buildSignature(byte[] signingKey,String stringToSign) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] digest = sumHmac(signingKey, stringToSign.getBytes(StandardCharsets.UTF_8));
return BaseEncoding.base16().encode(digest).toLowerCase(Locale.US);
return HexUtil.encodeHexStr(digest);
}
/**

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>minio-s3-api</artifactId>
<groupId>org.liuxp</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>minio-s3-api-definition</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,52 @@
package org.liuxp.minioplus.s3.def;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@ToString
public class ListParts {
private String bucketName;
private String objectName;
private int maxParts;
private List<Part> partList = null;
@Getter
@Setter
@ToString
static class Part{
private int partNumber;
private String etag;
private ZonedDateTime lastModified;
private Long size;
}
ListParts addPart(int partNumber,String etag,ZonedDateTime lastModified,Long size){
Part part = new Part();
part.setPartNumber(partNumber);
part.setEtag(etag);
part.setLastModified(lastModified);
part.setSize(size);
if(this.partList == null){
partList = new ArrayList<>();
}
partList.add(part);
return this;
}
}

View File

@ -0,0 +1,110 @@
package org.liuxp.minioplus.s3.def;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* MinIO S3文件存储引擎接口定义
*
* @author contact@liuxp.me
* @since 2024/06/03
*/
public interface MinioS3Client {
/**
* 判断存储桶是否存在
* @param bucketName 桶名称
* @return 是否存在
*/
Boolean bucketExists(String bucketName);
/**
* 创建桶
* @param bucketName 桶名称
*/
void makeBucket(String bucketName);
/**
* 创建上传任务
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @return UploadId 上传任务编号
*/
String createMultipartUpload(String bucketName, String objectName);
/**
* 合并分片
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @param uploadId 上传任务编号
* @param parts 分片信息 partNumber & etag
* @return 是否成功
*/
Boolean completeMultipartUpload(String bucketName, String objectName, String uploadId, List<ListParts.Part> parts);
/**
* 获取分片信息列表
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @param maxParts 分片数量
* @param uploadId 上传任务编号
* @return 分片信息
*/
ListParts listParts(String bucketName,String objectName,Integer maxParts,String uploadId);
/**
* 获得对象&分片上传链接
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @param partNumber 分片序号
* @return {@link String}
*/
String getUploadObjectUrl(String bucketName, String objectName, String partNumber);
/**
* 取得下载链接
* @param fileName 文件全名含扩展名
* @param contentType 数据类型
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @return 下载地址
*/
String getDownloadUrl(String fileName, String contentType, String bucketName, String objectName);
/**
* 取得图片预览链接
* @param contentType 数据类型
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @return 图片预览链接
*/
String getPreviewUrl(String contentType, String bucketName, String objectName);
/**
* 写入文件
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @param stream 文件流
* @param size 文件长度
* @param contentType 文件类型
* @return 是否成功
*/
Boolean putObject(String bucketName, String objectName, InputStream stream, long size, String contentType);
/**
* 读取文件
* @param bucketName 桶名称
* @param objectName 对象名称含路径
* @return 文件流
*/
byte[] getObject(String bucketName, String objectName);
/**
* 删除文件
* @param bucketName 桶名称
* @param objectName 对象名称含路径
*/
void removeObject(String bucketName, String objectName);
}

View File

@ -3,13 +3,13 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>minio-plus-api-wrapper</artifactId>
<artifactId>minio-s3-api</artifactId>
<groupId>org.liuxp</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>minio-plus-api-wrapper-official</artifactId>
<artifactId>minio-s3-api-official</artifactId>
<dependencies>
<!-- minio -->
@ -28,10 +28,6 @@
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,44 @@
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage,
* (C) 2020 MinIO, Inc.
*
* 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 java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
/** Time formatters for S3 APIs. */
public class Time {
public static final ZoneId UTC = ZoneId.of("Z");
public static final DateTimeFormatter AMZ_DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'", Locale.US).withZone(UTC);
public static final DateTimeFormatter RESPONSE_DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH':'mm':'ss'.'SSS'Z'", Locale.US).withZone(UTC);
// Formatted string is convertible to LocalDate only, not to LocalDateTime or ZonedDateTime.
// Below example shows how to use this to get ZonedDateTime.
// LocalDate.parse("20200225", SIGNER_DATE_FORMAT).atStartOfDay(UTC);
public static final DateTimeFormatter SIGNER_DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyyMMdd", Locale.US).withZone(UTC);
public static final DateTimeFormatter HTTP_HEADER_DATE_FORMAT =
DateTimeFormatter.ofPattern("EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'", Locale.US).withZone(UTC);
public static final DateTimeFormatter EXPIRATION_DATE_FORMAT = RESPONSE_DATE_FORMAT;
private Time() {}
}

View File

@ -9,12 +9,13 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>minio-plus-api-wrapper</artifactId>
<artifactId>minio-s3-api</artifactId>
<packaging>pom</packaging>
<modules>
<module>minio-plus-api-wrapper-custom</module>
<module>minio-plus-api-wrapper-official</module>
<module>minio-s3-api-definition</module>
<module>minio-s3-api-custom</module>
<module>minio-s3-api-official</module>
</modules>
</project>

View File

@ -35,7 +35,6 @@
</scm>
<modules>
<module>minio-plus-api-wrapper</module>
<module>minio-plus-application</module>
<module>minio-plus-common</module>
<module>minio-plus-config</module>
@ -43,6 +42,7 @@
<module>minio-plus-extension</module>
<module>minio-plus-model</module>
<module>minio-plus-spring-boot-starter</module>
<module>minio-s3-api</module>
</modules>
<properties>