Преглед на файлове

腾讯COS-文件存储API实现

deason преди 1 година
родител
ревизия
7afc1d5401

+ 1 - 1
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/FssFactory.java

@@ -16,7 +16,7 @@ public class FssFactory {
         if (FssType.ALIYUN_OSS == FssProperty.FSS_TYPE) {
             return new AliyunOssService().setInternal(internal);
         } else if (FssType.TENCENT_COS == FssProperty.FSS_TYPE) {
-            return new TencentCosService();
+            return new TencentCosService().setInternal(internal);
         } else {
             throw new RuntimeException("尚未配置文件存储方式!");
         }

+ 4 - 11
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/FssProperty.java

@@ -13,9 +13,7 @@ public class FssProperty {
 
     public static String FSS_BUCKET;
 
-    public static String FSS_ENDPOINT;
-
-    public static String FSS_INTERNAL_ENDPOINT;
+    public static String FSS_REGION_ID;
 
     public static String FSS_ACCESS_KEY_ID;
 
@@ -33,14 +31,9 @@ public class FssProperty {
         FSS_BUCKET = fssBucket;
     }
 
-    @Value("${examcloud.fss.endpoint:}")
-    public void fssEndpoint(String fssEndpoint) {
-        FSS_ENDPOINT = fssEndpoint;
-    }
-
-    @Value("${examcloud.fss.internalEndpoint:}")
-    public void fssInternalEndpoint(String fssInternalEndpoint) {
-        FSS_INTERNAL_ENDPOINT = fssInternalEndpoint;
+    @Value("${examcloud.fss.regionId:}")
+    public void fssRegionId(String fssRegionId) {
+        FSS_REGION_ID = fssRegionId;
     }
 
     @Value("${examcloud.fss.accessKeyId:}")

+ 6 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/FssService.java

@@ -14,4 +14,10 @@ public interface FssService {
 
     byte[] readFile(String filePath);
 
+    boolean deleteFile(String filePath);
+
+    boolean existFile(String filePath);
+
+    void refreshFile(String fileUrls);
+
 }

+ 87 - 35
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/impl/AliyunOssService.java

@@ -9,6 +9,11 @@ import com.aliyun.oss.OSS;
 import com.aliyun.oss.OSSClientBuilder;
 import com.aliyun.oss.model.GetObjectRequest;
 import com.aliyun.oss.model.OSSObject;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.cdn.model.v20180510.RefreshObjectCachesRequest;
+import com.aliyuncs.cdn.model.v20180510.RefreshObjectCachesResponse;
+import com.aliyuncs.profile.DefaultProfile;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,27 +39,26 @@ public class AliyunOssService implements FssService {
 
     @Override
     public FileInfo writeFile(String filePath, File file, String md5) {
-        if (StringUtils.isEmpty(md5)) {
-            throw new StatusException("文件MD5值不能为空!");
-        }
+        if (StringUtils.isNotEmpty(md5)) {
+            String realMd5 = FssHelper.getFileMD5(file);
 
-        String realMd5 = FssHelper.getFileMD5(file);
-        if (!realMd5.equals(md5)) {
-            log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
-            throw new StatusException("文件MD5值校验不通过!");
+            if (!md5.equals(realMd5)) {
+                log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
+                throw new StatusException("文件MD5值校验不通过!");
+            }
         }
 
         filePath = this.fixOssFilePath(filePath);
-        OSS ossClient = this.getClient();
+        OSS client = this.getClient();
 
         try {
-            ossClient.putObject(FssProperty.FSS_BUCKET, filePath, file);
+            client.putObject(FssProperty.FSS_BUCKET, filePath, file);
         } catch (Exception e) {
             log.error("文件上传失败!filePath:{} err:{}", filePath, e.getMessage());
             throw new StatusException("文件上传失败!");
         } finally {
             try {
-                ossClient.shutdown();
+                client.shutdown();
             } catch (Exception e) {
                 // ignore
             }
@@ -70,27 +74,26 @@ public class AliyunOssService implements FssService {
 
     @Override
     public FileInfo writeFile(String filePath, byte[] bytes, String md5) {
-        if (StringUtils.isEmpty(md5)) {
-            throw new StatusException("文件MD5值不能为空!");
-        }
+        if (StringUtils.isNotEmpty(md5)) {
+            String realMd5 = FssHelper.getFileMD5(bytes);
 
-        String realMd5 = FssHelper.getFileMD5(bytes);
-        if (!realMd5.equals(md5)) {
-            log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
-            throw new StatusException("文件MD5值校验不通过!");
+            if (!md5.equals(realMd5)) {
+                log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
+                throw new StatusException("文件MD5值校验不通过!");
+            }
         }
 
         filePath = this.fixOssFilePath(filePath);
-        OSS ossClient = this.getClient();
+        OSS client = this.getClient();
 
         try (ByteArrayInputStream is = new ByteArrayInputStream(bytes);) {
-            ossClient.putObject(FssProperty.FSS_BUCKET, filePath, is);
+            client.putObject(FssProperty.FSS_BUCKET, filePath, is);
         } catch (Exception e) {
             log.error("文件上传失败!filePath:{} err:{}", filePath, e.getMessage());
             throw new StatusException("文件上传失败!");
         } finally {
             try {
-                ossClient.shutdown();
+                client.shutdown();
             } catch (Exception e) {
                 // ignore
             }
@@ -107,19 +110,19 @@ public class AliyunOssService implements FssService {
     @Override
     public void readFile(String filePath, File toFile) {
         filePath = this.fixOssFilePath(filePath);
-        OSS ossClient = this.getClient();
+        OSS client = this.getClient();
 
         try {
             GetObjectRequest request = new GetObjectRequest(FssProperty.FSS_BUCKET, filePath);
 
             FssHelper.makeDirs(toFile.getParent());
-            ossClient.getObject(request, toFile);
+            client.getObject(request, toFile);
         } catch (Exception e) {
             log.error("文件下载失败!filePath:{} err:{}", filePath, e.getMessage());
             throw new StatusException("文件下载失败!");
         } finally {
             try {
-                ossClient.shutdown();
+                client.shutdown();
             } catch (Exception e) {
                 // ignore
             }
@@ -129,12 +132,12 @@ public class AliyunOssService implements FssService {
     @Override
     public byte[] readFile(String filePath) {
         filePath = this.fixOssFilePath(filePath);
-        OSS ossClient = this.getClient();
+        OSS client = this.getClient();
 
         InputStream is = null;
         ByteArrayOutputStream bos = null;
         try {
-            OSSObject ossObject = ossClient.getObject(FssProperty.FSS_BUCKET, filePath);
+            OSSObject ossObject = client.getObject(FssProperty.FSS_BUCKET, filePath);
             is = ossObject.getObjectContent();
             bos = new ByteArrayOutputStream();
 
@@ -159,38 +162,87 @@ public class AliyunOssService implements FssService {
                     bos.close();
                 }
 
-                ossClient.shutdown();
+                client.shutdown();
             } catch (Exception e) {
                 // ignore
             }
         }
     }
 
+    @Override
+    public boolean deleteFile(String filePath) {
+        try {
+            filePath = this.fixOssFilePath(filePath);
+            this.getClient().deleteObject(FssProperty.FSS_BUCKET, filePath);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean existFile(String filePath) {
+        filePath = this.fixOssFilePath(filePath);
+        return this.getClient().doesObjectExist(FssProperty.FSS_BUCKET, filePath);
+    }
+
+    /**
+     * 刷新文件的CDN缓存
+     * 1、同一个账号 每天最多可提交2000条URL刷新和100个目录刷新
+     * 2、每次请求最多只能提交1000条URL刷新,多个URL之间需要用换行符\n分割
+     * 3、每秒最多50次请求
+     *
+     * @param fileUrls 完整访问路径
+     */
+    @Override
+    public void refreshFile(String fileUrls) {
+        try {
+            DefaultProfile profile = DefaultProfile.getProfile(FssProperty.FSS_REGION_ID, FssProperty.FSS_ACCESS_KEY_ID,
+                    FssProperty.FSS_ACCESS_KEY_SECRET);
+            IAcsClient client = new DefaultAcsClient(profile);
+
+            RefreshObjectCachesRequest request = new RefreshObjectCachesRequest();
+            request.setObjectPath(fileUrls);
+            request.setObjectType("file");
+
+            RefreshObjectCachesResponse resp = client.getAcsResponse(request);
+
+            log.info("refreshFile:{} taskId:{}", fileUrls, resp != null ? resp.getRefreshTaskId() : "none");
+        } catch (Exception e) {
+            log.error("refreshFile:{} err:{}", fileUrls, e.getMessage());
+        }
+    }
+
     private OSS getClient() {
         try {
             // ClientBuilderConfiguration configuration = new ClientBuilderConfiguration();
-            OSS ossClient = new OSSClientBuilder().build(
-                    internal ? FssProperty.FSS_INTERNAL_ENDPOINT : FssProperty.FSS_ENDPOINT,
+
+            final String endpoint = "https://" + FssProperty.FSS_REGION_ID + ".aliyuncs.com";
+            final String internalEndpoint = "https://" + FssProperty.FSS_REGION_ID + "-internal.aliyuncs.com";
+
+            OSS client = new OSSClientBuilder().build(this.internal ? internalEndpoint : endpoint,
                     FssProperty.FSS_ACCESS_KEY_ID, FssProperty.FSS_ACCESS_KEY_SECRET);
-            // ossClient.setBucketTransferAcceleration(FssProperty.FSS_BUCKET, internal);
-            return ossClient;
+            // client.setBucketTransferAcceleration(FssProperty.FSS_BUCKET, true);
+
+            return client;
         } catch (Exception e) {
             log.error(e.getMessage(), e);
             throw new StatusException("OSS客户端初始化失败!");
         }
     }
 
-    private String fixOssFilePath(String ossFilePath) {
-        if (StringUtils.isEmpty(ossFilePath)) {
+    private String fixOssFilePath(String filePath) {
+        if (StringUtils.isEmpty(filePath)) {
             throw new StatusException("文件存储路径不能为空!");
         }
 
         // OSS存储路径不允许以 / 开头,需要去掉 /
-        if (ossFilePath.startsWith("/")) {
-            return ossFilePath.substring(1);
+        if (filePath.startsWith("/")) {
+            return filePath.substring(1);
         }
 
-        return ossFilePath;
+        return filePath;
     }
 
 }

+ 196 - 4
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/impl/TencentCosService.java

@@ -1,34 +1,226 @@
 package cn.com.qmth.examcloud.support.fss.impl;
 
-import cn.com.qmth.examcloud.support.fss.model.FileInfo;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.support.fss.FssHelper;
+import cn.com.qmth.examcloud.support.fss.FssProperty;
 import cn.com.qmth.examcloud.support.fss.FssService;
+import cn.com.qmth.examcloud.support.fss.model.FileInfo;
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.ClientConfig;
+import com.qcloud.cos.auth.BasicCOSCredentials;
+import com.qcloud.cos.auth.COSCredentials;
+import com.qcloud.cos.http.HttpProtocol;
+import com.qcloud.cos.model.COSObject;
+import com.qcloud.cos.model.GetObjectRequest;
+import com.qcloud.cos.model.ObjectMetadata;
+import com.qcloud.cos.model.PutObjectRequest;
+import com.qcloud.cos.region.Region;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.InputStream;
 
 public class TencentCosService implements FssService {
 
     private static final Logger log = LoggerFactory.getLogger(TencentCosService.class);
 
+    /**
+     * 是否内网访问
+     */
+    private boolean internal;
+
+    public TencentCosService setInternal(boolean internal) {
+        this.internal = internal;
+        return this;
+    }
+
     @Override
     public FileInfo writeFile(String filePath, File file, String md5) {
-        return null;
+        if (StringUtils.isNotEmpty(md5)) {
+            String realMd5 = FssHelper.getFileMD5(file);
+            if (!md5.equals(realMd5)) {
+                log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
+                throw new StatusException("文件MD5值校验不通过!");
+            }
+        }
+
+        filePath = this.fixCosFilePath(filePath);
+        COSClient client = this.getClient();
+
+        try {
+            PutObjectRequest objectRequest = new PutObjectRequest(FssProperty.FSS_BUCKET, filePath, file);
+            client.putObject(objectRequest);
+        } catch (Exception e) {
+            log.error("文件上传失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件上传失败!");
+        } finally {
+            try {
+                client.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+
+        filePath = FssProperty.FSS_SEPARATOR + filePath;
+        FileInfo result = new FileInfo();
+        result.setFileName(FssHelper.getFileName(filePath));
+        result.setFilePath(filePath);
+        result.setFileUrl(FssProperty.FSS_URL_PREFIX + filePath);
+        return result;
     }
 
     @Override
     public FileInfo writeFile(String filePath, byte[] bytes, String md5) {
-        return null;
+        if (StringUtils.isNotEmpty(md5)) {
+            String realMd5 = FssHelper.getFileMD5(bytes);
+            if (!md5.equals(realMd5)) {
+                log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
+                throw new StatusException("文件MD5值校验不通过!");
+            }
+        }
+
+        filePath = this.fixCosFilePath(filePath);
+        COSClient client = this.getClient();
+
+        try (ByteArrayInputStream is = new ByteArrayInputStream(bytes);) {
+            ObjectMetadata objectMetadata = new ObjectMetadata();
+            objectMetadata.setContentLength(bytes.length);
+            PutObjectRequest objectRequest = new PutObjectRequest(FssProperty.FSS_BUCKET, filePath, is, objectMetadata);
+            client.putObject(objectRequest);
+        } catch (Exception e) {
+            log.error("文件上传失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件上传失败!");
+        } finally {
+            try {
+                client.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+
+        filePath = FssProperty.FSS_SEPARATOR + filePath;
+        FileInfo result = new FileInfo();
+        result.setFileName(FssHelper.getFileName(filePath));
+        result.setFilePath(filePath);
+        result.setFileUrl(FssProperty.FSS_URL_PREFIX + filePath);
+        return result;
     }
 
     @Override
     public void readFile(String filePath, File toFile) {
+        filePath = this.fixCosFilePath(filePath);
+        COSClient client = this.getClient();
+
+        try {
+            GetObjectRequest request = new GetObjectRequest(FssProperty.FSS_BUCKET, filePath);
 
+            FssHelper.makeDirs(toFile.getParent());
+            client.getObject(request, toFile);
+        } catch (Exception e) {
+            log.error("文件下载失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件下载失败!");
+        } finally {
+            try {
+                client.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
     }
 
     @Override
     public byte[] readFile(String filePath) {
-        return new byte[0];
+        filePath = this.fixCosFilePath(filePath);
+        COSClient client = this.getClient();
+
+        InputStream is = null;
+        ByteArrayOutputStream bos = null;
+        try {
+            COSObject cosObject = client.getObject(FssProperty.FSS_BUCKET, filePath);
+            is = cosObject.getObjectContent();
+            bos = new ByteArrayOutputStream();
+
+            int len;
+            byte[] bytes = new byte[1024];
+            while ((len = is.read(bytes)) != -1) {
+                bos.write(bytes, 0, len);
+            }
+            bos.flush();
+            cosObject.close();
+
+            return bos.toByteArray();
+        } catch (Exception e) {
+            log.error("文件下载失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件下载失败!");
+        } finally {
+            try {
+                if (is != null) {
+                    is.close();
+                }
+                if (bos != null) {
+                    bos.close();
+                }
+
+                client.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
+
+    @Override
+    public boolean deleteFile(String filePath) {
+        try {
+            filePath = this.fixCosFilePath(filePath);
+            this.getClient().deleteObject(FssProperty.FSS_BUCKET, filePath);
+            return true;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean existFile(String filePath) {
+        filePath = this.fixCosFilePath(filePath);
+        return this.getClient().doesObjectExist(FssProperty.FSS_BUCKET, filePath);
+    }
+
+    @Override
+    public void refreshFile(String fileUrls) {
+        //todo
+    }
+
+    private COSClient getClient() {
+        try {
+            COSCredentials cred = new BasicCOSCredentials(FssProperty.FSS_ACCESS_KEY_ID,
+                    FssProperty.FSS_ACCESS_KEY_SECRET);
+            Region region = new Region(FssProperty.FSS_REGION_ID);
+            ClientConfig clientConfig = new ClientConfig(region);
+            clientConfig.setHttpProtocol(HttpProtocol.https);
+
+            return new COSClient(cred, clientConfig);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("COS客户端初始化失败!");
+        }
+    }
+
+    private String fixCosFilePath(String filePath) {
+        if (StringUtils.isEmpty(filePath)) {
+            throw new StatusException("文件存储路径不能为空!");
+        }
+
+        // 存储路径统一去掉 / 开头
+        if (filePath.startsWith("/")) {
+            return filePath.substring(1);
+        }
+
+        return filePath;
     }
 
 }

+ 9 - 6
examcloud-support/src/test/java/cn/com/qmth/examcloud/support/test/OssClientTest.java

@@ -26,16 +26,15 @@ public class OssClientTest {
     public void demoTest() throws Exception {
         FssProperty.FSS_TYPE = FssType.ALIYUN_OSS;
         FssProperty.FSS_BUCKET = "examcloud-test";
-        FssProperty.FSS_ENDPOINT = "https://oss-cn-shenzhen.aliyuncs.com";
-        FssProperty.FSS_INTERNAL_ENDPOINT = "https://oss-cn-shenzhen-internal.aliyuncs.com";
+        FssProperty.FSS_REGION_ID = "oss-cn-shenzhen";
         FssProperty.FSS_ACCESS_KEY_ID = "LTAI4FboXLCJzrjVo5dUoXaU";
         FssProperty.FSS_ACCESS_KEY_SECRET = "xxx";
+        // FssProperty.FSS_URL_PREFIX = "https://examcloud-test.oss-cn-shenzhen.aliyuncs.com";
         FssProperty.FSS_URL_PREFIX = "https://ecs-test-static.qmth.com.cn";
 
         // FssProperty.FSS_TYPE = FssType.TENCENT_COS;
         // FssProperty.FSS_BUCKET = "examcloud-1252178304";
-        // FssProperty.FSS_ENDPOINT = "https://cos.ap-guangzhou.myqcloud.com";
-        // FssProperty.FSS_INTERNAL_ENDPOINT = "https://cos.ap-guangzhou.myqcloud.com";
+        // FssProperty.FSS_REGION_ID = "ap-guangzhou";
         // FssProperty.FSS_ACCESS_KEY_ID = "AKID51lO89AEFNRNA90jGZiw9E5kCQh7djpn";
         // FssProperty.FSS_ACCESS_KEY_SECRET = "xxx";
         // FssProperty.FSS_URL_PREFIX = "https://examcloud-1252178304.cos.ap-guangzhou.myqcloud.com";
@@ -52,11 +51,15 @@ public class OssClientTest {
         FileInfo result = fssService.writeFile(filePath, testBytes, fileMd5);
         System.out.println(result.getFileUrl());
 
-        File toFile = new File(testDir + "/ddd/abc-new.png");
+        File toFile = new File(testDir + "/abc-new.png");
         // fssService.readFile(filePath, toFile);
-
         byte[] bytes = fssService.readFile(filePath);
         FileUtil.saveToFile(bytes, toFile);
+
+        System.out.println("exist:" + fssService.existFile(filePath));
+        fssService.refreshFile(result.getFileUrl());
+
+        // fssService.deleteFile(filePath);
     }
 
 }