Explorar o código

修改core-fss,去掉aliyun-sdk依赖,改为原生实现

Signed-off-by: luoshi <luoshi@qmth.com.cn>
luoshi hai 11 meses
pai
achega
17bf872a9e

+ 3 - 19
core-fss/pom.xml

@@ -14,7 +14,7 @@
     <dependencies>
         <dependency>
             <groupId>com.qmth.boot</groupId>
-            <artifactId>tools-common</artifactId>
+            <artifactId>core-retrofit</artifactId>
         </dependency>
         <dependency>
             <groupId>com.qmth.boot</groupId>
@@ -22,11 +22,11 @@
         </dependency>
         <dependency>
             <groupId>com.qmth.boot</groupId>
-            <artifactId>tools-common</artifactId>
+            <artifactId>core-logging</artifactId>
         </dependency>
         <dependency>
             <groupId>com.qmth.boot</groupId>
-            <artifactId>core-logging</artifactId>
+            <artifactId>tools-common</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -40,22 +40,6 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
-        <dependency>
-            <groupId>com.aliyun.oss</groupId>
-            <artifactId>aliyun-sdk-oss</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.xml.bind</groupId>
-            <artifactId>jaxb-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.activation</groupId>
-            <artifactId>activation</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.glassfish.jaxb</groupId>
-            <artifactId>jaxb-runtime</artifactId>
-        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>

+ 2 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/config/FssAutoConfiguration.java

@@ -8,9 +8,11 @@ import com.qmth.boot.core.fss.store.FileStore;
 import com.qmth.boot.core.fss.utils.FileStoreBuilder;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
+@ComponentScan("com.qmth.boot.core.fss")
 public class FssAutoConfiguration {
 
     /**

+ 32 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/exception/OssApiException.java

@@ -0,0 +1,32 @@
+package com.qmth.boot.core.fss.exception;
+
+public class OssApiException extends RuntimeException {
+
+    private static final long serialVersionUID = -4816424815121803936L;
+
+    private int code;
+
+    private String message;
+
+    public OssApiException(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}

+ 3 - 2
core-fss/src/main/java/com/qmth/boot/core/fss/store/FileStore.java

@@ -4,6 +4,7 @@ import com.qmth.boot.core.fss.utils.FssUtils;
 import com.qmth.boot.tools.models.ByteArray;
 import org.apache.commons.lang3.StringUtils;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.time.Duration;
 
@@ -120,14 +121,14 @@ public interface FileStore {
      * @param path
      * @return
      */
-    boolean exist(String path);
+    boolean exist(String path) throws IOException;
 
     /**
      * 删除已存在的文件
      *
      * @param path
      */
-    boolean delete(String path);
+    boolean delete(String path) throws IOException;
 
     /**
      * 将指定路径的文件复制到目标路径中

+ 99 - 83
core-fss/src/main/java/com/qmth/boot/core/fss/store/impl/OssStore.java

@@ -1,122 +1,138 @@
 package com.qmth.boot.core.fss.store.impl;
 
-import com.aliyun.oss.ClientBuilderConfiguration;
-import com.aliyun.oss.OSS;
-import com.aliyun.oss.OSSClientBuilder;
-import com.aliyun.oss.model.OSSObject;
-import com.aliyun.oss.model.ObjectMetadata;
+import com.qmth.boot.core.fss.exception.OssApiException;
 import com.qmth.boot.core.fss.store.FileStore;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.util.Assert;
-
+import com.qmth.boot.core.fss.utils.OssApiParam;
+import com.qmth.boot.core.fss.utils.OssConfig;
+import com.qmth.boot.core.fss.utils.OssSigner;
+import com.qmth.boot.tools.models.ByteArray;
+import okhttp3.*;
+
+import javax.activation.MimetypesFileTypeMap;
+import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.time.Duration;
-import java.util.Date;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
-/**
- * OSS文件管理工具
- */
 public class OssStore implements FileStore {
 
-    private static final String ENDPOINT_PREFIX_HTTPS = "https://";
-
-    private static final String ENDPOINT_PREFIX_HTTP = "http://";
-
-    private static final Pattern CONFIG_PATTERN = Pattern.compile("^oss://([\\w]+):([\\w]+)@([\\w-]+).([\\w-.]+)$");
-
-    private String server;
-
-    private String bucket;
-
-    private OSS ossClient;
-
-    private OSS temporaryUrlClient;
+    private OkHttpClient client;
 
-    public OssStore(String server, String config) {
-        String message = "fss.config: " + config + ": pattern error";
-        Matcher m = CONFIG_PATTERN.matcher(config);
-        if (m.find()) {
-            String accessKey = StringUtils.trimToNull(m.group(1));
-            String accessSecret = StringUtils.trimToNull(m.group(2));
-            this.bucket = StringUtils.trimToNull(m.group(3));
-            String endpoint = StringUtils.trimToNull(m.group(4));
+    private OssConfig config;
 
-            Assert.notNull(accessKey, message);
-            Assert.notNull(accessSecret, message);
-            Assert.notNull(bucket, message);
-            Assert.notNull(endpoint, message);
+    private MimetypesFileTypeMap mimeTypes;
 
-            this.server = server;
-            //判断是否阿里云OSS地址还是私服,使用不同的前缀
-            if (endpoint.contains("aliyun")) {
-                endpoint = ENDPOINT_PREFIX_HTTPS.concat(endpoint);
-            } else {
-                endpoint = ENDPOINT_PREFIX_HTTP.concat(endpoint);
-            }
-            this.ossClient = new OSSClientBuilder().build(endpoint, accessKey, accessSecret);
-            //为支持私有读的bucket,构造专门用于获取临时访问URL的client
-            ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
-            conf.setSupportCname(true);
-            this.temporaryUrlClient = new OSSClientBuilder().build(server, accessKey, accessSecret, conf);
-        } else {
-            throw new IllegalArgumentException(message);
-        }
+    public OssStore(OssConfig config) {
+        this.client = new OkHttpClient.Builder().connectionPool(new ConnectionPool())
+                .connectTimeout(Duration.ofSeconds(10)).readTimeout(Duration.ofSeconds(30)).build();
+        this.config = config;
+        this.mimeTypes = new MimetypesFileTypeMap();
     }
 
-    @Override
-    public String getServer() {
-        return server;
+    private Headers buildHeader(OssApiParam header, String authorization) {
+        Headers.Builder builder = new Headers.Builder();
+        builder.add("Date", OssSigner.getGmtDateTime(header.getDate()));
+        builder.add("x-oss-date", OssSigner.getIso8601DateTime(header.getDate()));
+        builder.add("x-oss-content-sha256", "UNSIGNED-PAYLOAD");
+        if (header.getCopySource() != null) {
+            builder.add("x-oss-copy-source", header.getCopySource());
+        }
+        if (header.getContentMd5() != null) {
+            builder.add("content-md5", header.getContentMd5());
+        }
+        if (header.getContentType() != null) {
+            builder.add("content-type", header.getContentType());
+        }
+        builder.add("Authorization", authorization);
+        return builder.build();
     }
 
     @Override
-    public String getPresignedUrl(String path, Duration expireDuration) {
-        return temporaryUrlClient.generatePresignedUrl(bucket, formatPath(path),
-                new Date(System.currentTimeMillis() + expireDuration.toMillis())).toString();
+    public void copy(String source, String target) throws Exception {
+        source = formatPath(source);
+        target = formatPath(target);
+        OssApiParam header = new OssApiParam().setCopySource("/" + config.getBucket() + "/" + source);
+        Request request = new Request.Builder().url(config.getEndpoint() + "/" + target)
+                .headers(buildHeader(header, OssSigner.buildHeader(config, "put", target, header)))
+                .put(RequestBody.create(null, new byte[0])).build();
+        Response response = client.newCall(request).execute();
+        if (!response.isSuccessful()) {
+            String error = response.body() != null ? new String(response.body().bytes(), StandardCharsets.UTF_8) : "";
+            throw new OssApiException(response.code(), error);
+        }
     }
 
     @Override
-    public void write(String path, InputStream ins, String md5) {
-        ObjectMetadata metadata = new ObjectMetadata();
-        metadata.setContentMD5(toBase64(md5));
-        ossClient.putObject(bucket, formatPath(path), ins, metadata);
+    public void write(String path, InputStream ins, String md5) throws Exception {
+        path = formatPath(path);
+        OssApiParam param = new OssApiParam().setContentMd5(toBase64(md5))
+                .setContentType(mimeTypes.getContentType(path));
+        RequestBody body = RequestBody
+                .create(MediaType.parse(param.getContentType()), ByteArray.fromInputStream(ins).value());
+        Request request = new Request.Builder().url(config.getEndpoint() + "/" + path)
+                .headers(buildHeader(param, OssSigner.buildHeader(config, "put", path, param))).put(body).build();
+        Response response = client.newCall(request).execute();
+        if (!response.isSuccessful()) {
+            String error = response.body() != null ? new String(response.body().bytes(), StandardCharsets.UTF_8) : "";
+            throw new OssApiException(response.code(), error);
+        }
     }
 
     @Override
-    public InputStream read(String path) {
-        OSSObject ossObject = ossClient.getObject(bucket, formatPath(path));
-        return ossObject.getObjectContent();
+    public InputStream read(String path) throws Exception {
+        path = formatPath(path);
+        OssApiParam param = new OssApiParam();
+        Request request = new Request.Builder().url(config.getEndpoint() + "/" + path)
+                .headers(buildHeader(param, OssSigner.buildHeader(config, "get", path, param))).get().build();
+        Response response = client.newCall(request).execute();
+        if (!response.isSuccessful()) {
+            String error = response.body() != null ? new String(response.body().bytes(), StandardCharsets.UTF_8) : "";
+            throw new OssApiException(response.code(), error);
+        } else {
+            return response.body() != null ? response.body().byteStream() : null;
+        }
     }
 
     @Override
-    public boolean exist(String path) {
-        try {
-            return ossClient.doesObjectExist(bucket, formatPath(path));
-        } catch (Exception e) {
+    public boolean exist(String path) throws IOException {
+        path = formatPath(path);
+        OssApiParam param = new OssApiParam();
+        Request request = new Request.Builder().url(config.getEndpoint() + "/" + path)
+                .headers(buildHeader(param, OssSigner.buildHeader(config, "head", path, param))).head().build();
+        Response response = client.newCall(request).execute();
+        if (response.isSuccessful()) {
+            return true;
+        } else if (response.code() == 404) {
             return false;
+        } else {
+            String error = response.body() != null ? new String(response.body().bytes(), StandardCharsets.UTF_8) : "";
+            throw new OssApiException(response.code(), error);
         }
     }
 
     @Override
-    public boolean delete(String path) {
-        ossClient.deleteObject(bucket, formatPath(path));
-        return true;
+    public boolean delete(String path) throws IOException {
+        path = formatPath(path);
+        OssApiParam param = new OssApiParam();
+        Request request = new Request.Builder().url(config.getEndpoint() + "/" + path)
+                .headers(buildHeader(param, OssSigner.buildHeader(config, "delete", path, param))).delete().build();
+        Response response = client.newCall(request).execute();
+        if (response.code() != 200 && response.code() != 204) {
+            throw new OssApiException(response.code(), "");
+        } else {
+            return true;
+        }
     }
 
     @Override
-    public void copy(String source, String target) throws Exception {
-        ossClient.copyObject(bucket, formatPath(source), bucket, formatPath(target));
+    public String getPresignedUrl(String path, Duration expireDuration) {
+        path = formatPath(path);
+        return config.getPortal() + "/" + OssSigner.buildQuery(config, "get", path, expireDuration);
     }
 
     @Override
-    public void close() {
-        if (ossClient != null) {
-            ossClient.shutdown();
-        }
-        if (temporaryUrlClient != null) {
-            temporaryUrlClient.shutdown();
-        }
+    public String getServer() {
+        return config.getPortal();
     }
 
 }

+ 1 - 1
core-fss/src/main/java/com/qmth/boot/core/fss/utils/FileStoreBuilder.java

@@ -15,7 +15,7 @@ public class FileStoreBuilder {
         }
         String config = fileStoreProperty.getConfig();
         if (config.startsWith("oss://")) {
-            return new OssStore(server, config);
+            return new OssStore(new OssConfig(fileStoreProperty));
         } else {
             return new DiskStore(server, config, System.getProperty("java.io.tmpdir"));
         }

+ 49 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/utils/OssApiParam.java

@@ -0,0 +1,49 @@
+package com.qmth.boot.core.fss.utils;
+
+import java.util.Date;
+
+public class OssApiParam {
+
+    private Date date;
+
+    private String copySource;
+
+    private String contentMd5;
+
+    private String contentType;
+
+    public OssApiParam() {
+        this.date = new Date();
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public String getCopySource() {
+        return copySource;
+    }
+
+    public OssApiParam setCopySource(String copySource) {
+        this.copySource = copySource;
+        return this;
+    }
+
+    public String getContentMd5() {
+        return contentMd5;
+    }
+
+    public OssApiParam setContentMd5(String contentMd5) {
+        this.contentMd5 = contentMd5;
+        return this;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public OssApiParam setContentType(String contentType) {
+        this.contentType = contentType;
+        return this;
+    }
+}

+ 107 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/utils/OssConfig.java

@@ -0,0 +1,107 @@
+package com.qmth.boot.core.fss.utils;
+
+import com.qmth.boot.core.fss.config.FileStoreProperty;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.Assert;
+
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class OssConfig {
+
+    private static final String ENDPOINT_PREFIX_HTTPS = "https://";
+
+    private static final String ENDPOINT_PREFIX_HTTP = "http://";
+
+    private static final Pattern CONFIG_PATTERN = Pattern.compile("^oss://([\\w]+):([\\w]+)@([\\w-.]+)$");
+
+    private static final Pattern ALIYUN_HOST_PATTERN = Pattern
+            .compile("^([\\w-]+).oss-(cn-[a-z]+)(-internal)?.aliyuncs.com$");
+
+    private static final Pattern OTHER_HOST_PATTERN = Pattern.compile("^([\\w-]+).([\\w-.]+)$");
+
+    private String endpoint;
+
+    private String host;
+
+    private String region;
+
+    private String bucket;
+
+    private String accessKey;
+
+    private String accessSecret;
+
+    private String portal;
+
+    public OssConfig(FileStoreProperty property) {
+        String message = "fss.config: " + property.getConfig() + ": pattern error";
+        Matcher m = CONFIG_PATTERN.matcher(property.getConfig());
+        if (m.find()) {
+            this.portal = property.getServer();
+            this.accessKey = StringUtils.trimToNull(m.group(1));
+            this.accessSecret = StringUtils.trimToNull(m.group(2));
+            this.host = StringUtils.trimToNull(m.group(3));
+            this.portal = property.getServer();
+
+            Assert.notNull(accessKey, message);
+            Assert.notNull(accessSecret, message);
+            Assert.notNull(host, message);
+
+            m = ALIYUN_HOST_PATTERN.matcher(this.host);
+            if (m.find()) {
+                this.bucket = m.group(1);
+                this.region = m.group(2);
+                this.endpoint = ENDPOINT_PREFIX_HTTPS + this.host;
+            } else {
+                m = OTHER_HOST_PATTERN.matcher(this.host);
+                if (m.find()) {
+                    this.bucket = m.group(1);
+                } else {
+                    this.bucket = "";
+                }
+                this.region = "";
+                this.endpoint = ENDPOINT_PREFIX_HTTP + this.host;
+            }
+        } else {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+    public String getEndpoint() {
+        return endpoint;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public String getRegion() {
+        return region;
+    }
+
+    public String getBucket() {
+        return bucket;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public String getAccessSecret() {
+        return accessSecret;
+    }
+
+    public String getPortal() {
+        return portal;
+    }
+
+    public String getPortalHost() {
+        try {
+            return new URL(portal).getHost();
+        } catch (Exception e) {
+            return "";
+        }
+    }
+}

+ 127 - 0
core-fss/src/main/java/com/qmth/boot/core/fss/utils/OssSigner.java

@@ -0,0 +1,127 @@
+package com.qmth.boot.core.fss.utils;
+
+import com.qmth.boot.tools.codec.CodecUtils;
+import com.qmth.boot.tools.models.ByteArray;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+
+/**
+ * OSS签名计算工具
+ */
+public class OssSigner {
+
+    public static String buildHeader(OssConfig config, String method, String path, OssApiParam param) {
+        String dateString = getIso8601Date(param.getDate());
+        String dateTime = getIso8601DateTime(param.getDate());
+        String uri = "/" + config.getBucket() + "/" + path;
+        // 步骤1:构造CanonicalRequest
+        String canonicalRequest = method.toUpperCase() + "\n" + uri + "\n\n" + (param.getContentMd5() != null ?
+                "content-md5:" + param.getContentMd5() + "\n" :
+                "") + (param.getContentType() != null ? "content-type:" + param.getContentType() + "\n" : "") + "host:"
+                + config.getHost() + "\n" + "x-oss-content-sha256:UNSIGNED-PAYLOAD\n" + (param.getCopySource() != null ?
+                "x-oss-copy-source:" + param.getCopySource() + "\n" :
+                "") + "x-oss-date:" + dateTime + "\n" + "\nhost\nUNSIGNED-PAYLOAD";
+        String canonicalDigest = ByteArray.sha256(canonicalRequest).toHexString().toLowerCase();
+        //System.out.println("canonicalRequest:" + canonicalRequest);
+        //System.out.println("canonicalDigest:" + canonicalDigest);
+        // 步骤2:构造待签名字符串(StringToSign)
+        String stringToSign = "OSS4-HMAC-SHA256\n" + dateTime + "\n" + dateString + "/" + config.getRegion()
+                + "/oss/aliyun_v4_request\n" + canonicalDigest;
+        //System.out.println("stringToSign:" + stringToSign);
+        // 步骤3:计算Signature。
+        // "accesskeysecret"填入用户AK,data参数填入实际日期如"20231203”
+        byte[] dateKey = hmacsha256(("aliyun_v4" + config.getAccessSecret()).getBytes(StandardCharsets.UTF_8),
+                dateString);
+        // 参数填入所在地区,如所在地域为杭州则填入"cn-hangzhou”
+        byte[] dateRegionKey = hmacsha256(dateKey, config.getRegion());
+        byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
+        byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");
+        byte[] result = hmacsha256(signingKey, stringToSign);
+        String signature = CodecUtils.toHexString(result).toLowerCase();
+        //System.out.println("signature:" + signature);
+
+        return "OSS4-HMAC-SHA256 Credential=" + config.getAccessKey() + "/" + dateString + "/" + config.getRegion()
+                + "/oss/aliyun_v4_request,AdditionalHeaders=host,Signature=" + signature;
+    }
+
+    /**
+     * 签名计算工具
+     *
+     * @return url
+     */
+    public static String buildQuery(OssConfig config, String method, String path, Duration expireDuration) {
+        Date date = new Date();
+        String dateString = getIso8601Date(date);
+        String dateTime = getIso8601DateTime(date);
+        long expire = expireDuration.getSeconds();
+        // 步骤1:构造CanonicalRequest。
+        String canonicalRequest = method.toUpperCase() + "\n" + "/" + config.getBucket() + "/" + path + "\n"
+                + "x-oss-additional-headers=host&x-oss-credential=" + config.getAccessKey() + "%2F" + dateString + "%2F"
+                + config.getRegion() + "%2Foss%2Faliyun_v4_request&x-oss-date=" + dateTime + "&x-oss-expires=" + expire
+                + "&x-oss-signature-version=OSS4-HMAC-SHA256\n" + "host:" + config.getPortalHost() + "\n" + "\n"
+                + "host\n" + "UNSIGNED-PAYLOAD";
+        String canonicalDigest = ByteArray.sha256(canonicalRequest).toHexString().toLowerCase();
+        // 步骤2:构造待签名字符串(StringToSign)。
+        String stringToSign = "OSS4-HMAC-SHA256\n" + dateTime + "\n" + dateString + "/" + config.getRegion()
+                + "/oss/aliyun_v4_request\n" + canonicalDigest;
+
+        // 步骤3:计算Signature。
+        byte[] dateKey = hmacsha256(("aliyun_v4" + config.getAccessSecret()).getBytes(StandardCharsets.UTF_8),
+                dateString);
+        byte[] dateRegionKey = hmacsha256(dateKey, config.getRegion());
+        byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
+        byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");
+
+        byte[] result = hmacsha256(signingKey, stringToSign);
+        String signature = CodecUtils.toHexString(result).toLowerCase();
+        //System.out.println("signature:" + signature);
+
+        // 步骤4:在URL中加入签名。
+        String queryString =
+                "x-oss-additional-headers=host&" + "x-oss-credential=" + config.getAccessKey() + "%2F" + dateString
+                        + "%2F" + config.getRegion() + "%2Foss%2Faliyun_v4_request&" + "x-oss-date=" + dateTime + "&"
+                        + "x-oss-expires=" + expire + "&" + "x-oss-signature=" + signature + "&"
+                        + "x-oss-signature-version=OSS4-HMAC-SHA256";
+        return path + "?" + queryString;
+    }
+
+    private static byte[] hmacsha256(byte[] key, String data) {
+        try {
+            // 初始化HMAC密钥规格,指定算法为HMAC-SHA256并使用提供的密钥。
+            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
+            // 获取Mac实例,并通过getInstance方法指定使用HMAC-SHA256算法。
+            Mac mac = Mac.getInstance("HmacSHA256");
+            // 使用密钥初始化Mac对象。
+            mac.init(secretKeySpec);
+            // 执行HMAC计算,通过doFinal方法接收需要计算的数据并返回计算结果的数组。
+            return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
+        }
+    }
+
+    public static String getIso8601Date(Date date) {
+        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd", Locale.US);
+        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
+        return df.format(date);
+    }
+
+    public static String getIso8601DateTime(Date date) {
+        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US);
+        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
+        return df.format(date);
+    }
+
+    public static String getGmtDateTime(Date date) {
+        SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
+        return df.format(date);
+    }
+}

+ 9 - 5
core-fss/src/test/java/com/qmth/boot/test/core/fss/OssStoreTest.java

@@ -1,6 +1,8 @@
 package com.qmth.boot.test.core.fss;
 
+import com.qmth.boot.core.fss.config.FileStoreProperty;
 import com.qmth.boot.core.fss.store.impl.OssStore;
+import com.qmth.boot.core.fss.utils.OssConfig;
 import com.qmth.boot.tools.models.ByteArray;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Assert;
@@ -10,7 +12,7 @@ import java.time.Duration;
 
 public class OssStoreTest {
 
-    private String server_cloud = "https://qmth-test.oss-cn-shenzhen.aliyuncs.com";
+    private String server_cloud = "https://static-test.qmth.com.cn";
 
     private String server_local = "http://oss-file.qmth.com.cn/test";
 
@@ -20,15 +22,17 @@ public class OssStoreTest {
 
     //@Test
     public void test() throws Exception {
-        OssStore store = new OssStore(server_local, config_local);
+        FileStoreProperty p = new FileStoreProperty();
+        p.setConfig(config_local);
+        p.setServer(server_local);
+        OssStore store = new OssStore(new OssConfig(p));
         String content = RandomStringUtils.random(128, true, true);
         ByteArray data = ByteArray.fromString(content);
         store.write("/test/1.txt", new ByteArrayInputStream(data.value()), ByteArray.md5(data.value()).toHexString());
-        //Assert.assertTrue(store.exist("test/1.txt"));
+        Assert.assertTrue(store.exist("test/1.txt"));
         Assert.assertEquals(content, ByteArray.fromInputStream(store.read("test/1.txt")).toString());
         String url = store.getPresignedUrl("/test/1.txt", Duration.ofMinutes(5));
-        //System.out.println(url);
-        Assert.assertTrue(url.startsWith(store.getServer()));
+        Assert.assertEquals(content, ByteArray.fromUrl(url).toString());
         store.close();
     }
 

+ 6 - 0
tools-common/src/main/java/com/qmth/boot/tools/codec/CodecUtils.java

@@ -15,6 +15,8 @@ public class CodecUtils {
 
     private static final String SHA1_ALGORITHM = "SHA-1";
 
+    private static final String SHA256_ALGORITHM = "SHA-256";
+
     private static final String MD5_ALGORITHM = "MD5";
 
     public static String toBase64(byte[] value) {
@@ -37,6 +39,10 @@ public class CodecUtils {
         return value != null ? digest(value.getBytes(StandardCharsets.UTF_8), SHA1_ALGORITHM) : null;
     }
 
+    public static byte[] sha256(String value) {
+        return value != null ? digest(value.getBytes(StandardCharsets.UTF_8), SHA256_ALGORITHM) : null;
+    }
+
     public static byte[] md5(String value) {
         return value != null ? digest(value.getBytes(StandardCharsets.UTF_8), MD5_ALGORITHM) : null;
     }

+ 10 - 0
tools-common/src/main/java/com/qmth/boot/tools/models/ByteArray.java

@@ -183,6 +183,16 @@ public class ByteArray {
         return new ByteArray(CodecUtils.sha1(value));
     }
 
+    /**
+     * SHA256字符串摘要并返回byte[]
+     *
+     * @param value
+     * @return
+     */
+    public static ByteArray sha256(String value) {
+        return new ByteArray(CodecUtils.sha256(value));
+    }
+
     /**
      * byte[]转String,使用UTF8
      *