Browse Source

新增文件存储API实现

deason 1 năm trước cách đây
mục cha
commit
723c7892f1

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

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.support.fss;
+
+import cn.com.qmth.examcloud.support.fss.impl.AliyunOssService;
+import cn.com.qmth.examcloud.support.fss.impl.TencentCosService;
+import cn.com.qmth.examcloud.support.fss.model.FssType;
+
+public class FssFactory {
+
+    /**
+     * 获取 文件存储服务实例
+     *
+     * @param internal 是否内网访问
+     * @return
+     */
+    public static FssService getInstance(boolean internal) {
+        if (FssType.ALIYUN_OSS == FssProperty.FSS_TYPE) {
+            return new AliyunOssService().setInternal(internal);
+        } else if (FssType.TENCENT_COS == FssProperty.FSS_TYPE) {
+            return new TencentCosService();
+        } else {
+            throw new RuntimeException("尚未配置文件存储方式!");
+        }
+    }
+
+}

+ 84 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/FssHelper.java

@@ -0,0 +1,84 @@
+package cn.com.qmth.examcloud.support.fss;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.MessageDigest;
+
+public class FssHelper {
+
+    private static final Logger log = LoggerFactory.getLogger(FssHelper.class);
+
+    /**
+     * 创建文件目录
+     */
+    public static boolean makeDirs(String dirPath) {
+        if (StringUtils.isEmpty(dirPath)) {
+            return false;
+        }
+
+        File dir = new File(dirPath);
+        if (!dir.exists()) {
+            return dir.mkdirs();
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取文件名(包含后缀名)
+     */
+    public static String getFileName(String filePath) {
+        if (filePath != null) {
+            filePath = filePath.replace("\\", "/");
+            int index = filePath.lastIndexOf("/");
+            if (index >= 0) {
+                return filePath.substring(index + 1);
+            }
+        }
+
+        return filePath;
+    }
+
+    /**
+     * 获取文件MD5值
+     */
+    public static String getFileMD5(byte[] fileBytes) {
+        if (ArrayUtils.isEmpty(fileBytes)) {
+            throw new RuntimeException("文件大小不能为空!");
+        }
+
+        return DigestUtils.md5Hex(fileBytes);
+    }
+
+    /**
+     * 获取文件的MD5值
+     */
+    public static String getFileMD5(File file) {
+        if (file == null || !file.isFile()) {
+            return null;
+        }
+
+        try (FileInputStream fis = new FileInputStream(file);) {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+
+            int len;
+            byte[] bytes = new byte[4096];
+            while ((len = fis.read(bytes)) != -1) {
+                md.update(bytes, 0, len);
+            }
+
+            return new String(Hex.encodeHex(md.digest()));
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+}

+ 61 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/FssProperty.java

@@ -0,0 +1,61 @@
+package cn.com.qmth.examcloud.support.fss;
+
+import cn.com.qmth.examcloud.support.fss.model.FssType;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class FssProperty {
+
+    public static final String FSS_SEPARATOR = "/";
+
+    public static FssType FSS_TYPE;
+
+    public static String FSS_BUCKET;
+
+    public static String FSS_ENDPOINT;
+
+    public static String FSS_INTERNAL_ENDPOINT;
+
+    public static String FSS_ACCESS_KEY_ID;
+
+    public static String FSS_ACCESS_KEY_SECRET;
+
+    public static String FSS_URL_PREFIX;
+
+    @Value("${examcloud.fss.type:}")
+    public void fssType(FssType fssType) {
+        FSS_TYPE = fssType;
+    }
+
+    @Value("${examcloud.fss.bucket:}")
+    public void fssBucket(String fssBucket) {
+        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.accessKeyId:}")
+    public void fssAccessKeyId(String fssAccessKeyId) {
+        FSS_ACCESS_KEY_ID = fssAccessKeyId;
+    }
+
+    @Value("${examcloud.fss.accessKeySecret:}")
+    public void fssAccessKeySecret(String fssAccessKeySecret) {
+        FSS_ACCESS_KEY_SECRET = fssAccessKeySecret;
+    }
+
+    @Value("${examcloud.fss.urlPrefix:}")
+    public void fssUrlPrefix(String fssUrlPrefix) {
+        FSS_URL_PREFIX = fssUrlPrefix;
+    }
+
+}

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

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.support.fss;
+
+import cn.com.qmth.examcloud.support.fss.model.FileInfo;
+
+import java.io.File;
+
+public interface FssService {
+
+    FileInfo writeFile(String filePath, File file, String md5);
+
+    FileInfo writeFile(String filePath, byte[] bytes, String md5);
+
+    void readFile(String filePath, File toFile);
+
+    byte[] readFile(String filePath);
+
+}

+ 196 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/impl/AliyunOssService.java

@@ -0,0 +1,196 @@
+package cn.com.qmth.examcloud.support.fss.impl;
+
+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.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.GetObjectRequest;
+import com.aliyun.oss.model.OSSObject;
+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 AliyunOssService implements FssService {
+
+    private static final Logger log = LoggerFactory.getLogger(AliyunOssService.class);
+
+    /**
+     * 是否内网访问
+     */
+    private boolean internal;
+
+    public AliyunOssService setInternal(boolean internal) {
+        this.internal = internal;
+        return this;
+    }
+
+    @Override
+    public FileInfo writeFile(String filePath, File file, String md5) {
+        if (StringUtils.isEmpty(md5)) {
+            throw new StatusException("文件MD5值不能为空!");
+        }
+
+        String realMd5 = FssHelper.getFileMD5(file);
+        if (!realMd5.equals(md5)) {
+            log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
+            throw new StatusException("文件MD5值校验不通过!");
+        }
+
+        filePath = this.fixOssFilePath(filePath);
+        OSS ossClient = this.getClient();
+
+        try {
+            ossClient.putObject(FssProperty.FSS_BUCKET, filePath, file);
+        } catch (Exception e) {
+            log.error("文件上传失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件上传失败!");
+        } finally {
+            try {
+                ossClient.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) {
+        if (StringUtils.isEmpty(md5)) {
+            throw new StatusException("文件MD5值不能为空!");
+        }
+
+        String realMd5 = FssHelper.getFileMD5(bytes);
+        if (!realMd5.equals(md5)) {
+            log.warn("filePath:{} realMD5:{} MD5:{}", filePath, realMd5, md5);
+            throw new StatusException("文件MD5值校验不通过!");
+        }
+
+        filePath = this.fixOssFilePath(filePath);
+        OSS ossClient = this.getClient();
+
+        try (ByteArrayInputStream is = new ByteArrayInputStream(bytes);) {
+            ossClient.putObject(FssProperty.FSS_BUCKET, filePath, is);
+        } catch (Exception e) {
+            log.error("文件上传失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件上传失败!");
+        } finally {
+            try {
+                ossClient.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.fixOssFilePath(filePath);
+        OSS ossClient = this.getClient();
+
+        try {
+            GetObjectRequest request = new GetObjectRequest(FssProperty.FSS_BUCKET, filePath);
+
+            FssHelper.makeDirs(toFile.getParent());
+            ossClient.getObject(request, toFile);
+        } catch (Exception e) {
+            log.error("文件下载失败!filePath:{} err:{}", filePath, e.getMessage());
+            throw new StatusException("文件下载失败!");
+        } finally {
+            try {
+                ossClient.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
+
+    @Override
+    public byte[] readFile(String filePath) {
+        filePath = this.fixOssFilePath(filePath);
+        OSS ossClient = this.getClient();
+
+        InputStream is = null;
+        ByteArrayOutputStream bos = null;
+        try {
+            OSSObject ossObject = ossClient.getObject(FssProperty.FSS_BUCKET, filePath);
+            is = ossObject.getObjectContent();
+            bos = new ByteArrayOutputStream();
+
+            int len;
+            byte[] bytes = new byte[1024];
+            while ((len = is.read(bytes)) != -1) {
+                bos.write(bytes, 0, len);
+            }
+            bos.flush();
+            ossObject.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();
+                }
+
+                ossClient.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
+
+    private OSS getClient() {
+        try {
+            // ClientBuilderConfiguration configuration = new ClientBuilderConfiguration();
+            OSS ossClient = new OSSClientBuilder().build(
+                    internal ? FssProperty.FSS_INTERNAL_ENDPOINT : FssProperty.FSS_ENDPOINT,
+                    FssProperty.FSS_ACCESS_KEY_ID, FssProperty.FSS_ACCESS_KEY_SECRET);
+            // ossClient.setBucketTransferAcceleration(FssProperty.FSS_BUCKET, internal);
+            return ossClient;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("OSS客户端初始化失败!");
+        }
+    }
+
+    private String fixOssFilePath(String ossFilePath) {
+        if (StringUtils.isEmpty(ossFilePath)) {
+            throw new StatusException("文件存储路径不能为空!");
+        }
+
+        // OSS存储路径不允许以 / 开头,需要去掉 /
+        if (ossFilePath.startsWith("/")) {
+            return ossFilePath.substring(1);
+        }
+
+        return ossFilePath;
+    }
+
+}

+ 34 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/impl/TencentCosService.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.support.fss.impl;
+
+import cn.com.qmth.examcloud.support.fss.model.FileInfo;
+import cn.com.qmth.examcloud.support.fss.FssService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class TencentCosService implements FssService {
+
+    private static final Logger log = LoggerFactory.getLogger(TencentCosService.class);
+
+    @Override
+    public FileInfo writeFile(String filePath, File file, String md5) {
+        return null;
+    }
+
+    @Override
+    public FileInfo writeFile(String filePath, byte[] bytes, String md5) {
+        return null;
+    }
+
+    @Override
+    public void readFile(String filePath, File toFile) {
+
+    }
+
+    @Override
+    public byte[] readFile(String filePath) {
+        return new byte[0];
+    }
+
+}

+ 49 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/model/FileInfo.java

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.support.fss.model;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class FileInfo {
+
+    @ApiModelProperty(value = "文件名")
+    private String fileName;
+
+    // @ApiModelProperty(value = "文件后缀名")
+    // private String fileSuffix;
+
+    // @ApiModelProperty(value = "文件MD5")
+    // private String fileMd5;
+
+    // @ApiModelProperty(value = "文件大小KB")
+    // private Long fileSize;
+
+    @ApiModelProperty(value = "文件存储路径")
+    private String filePath;
+
+    @ApiModelProperty(value = "文件访问地址")
+    private String fileUrl;
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+}

+ 20 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/fss/model/FssType.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.support.fss.model;
+
+public enum FssType {
+
+    /**
+     * 本地存储
+     */
+    LOCAL,
+
+    /**
+     * 阿里云OSS
+     */
+    ALIYUN_OSS,
+
+    /**
+     * 腾讯云COS
+     */
+    TENCENT_COS;
+
+}

+ 40 - 25
examcloud-support/src/test/java/cn/com/qmth/examcloud/support/test/OssClientTest.java

@@ -1,14 +1,19 @@
 package cn.com.qmth.examcloud.support.test;
 
 import cn.com.qmth.examcloud.commons.util.FileUtil;
-import cn.com.qmth.examcloud.web.aliyun.OssClient;
-import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.support.fss.FssFactory;
+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 cn.com.qmth.examcloud.support.fss.model.FssType;
+import org.apache.commons.io.IOUtils;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.config.Configurator;
 import org.junit.Before;
 import org.junit.Test;
 
 import java.io.File;
+import java.nio.file.Files;
 
 public class OssClientTest {
 
@@ -18,29 +23,39 @@ public class OssClientTest {
     }
 
     @Test
-    public void demoTest() {
-        SystemProperties properties = new SystemProperties();
-        properties.setAliyunBucketName("examcloud-test");
-        // properties.setAliyunOssEndpoint("https://oss-cn-shenzhen-internal.aliyuncs.com");
-        properties.setAliyunOssEndpoint("https://oss-cn-shenzhen.aliyuncs.com");
-        properties.setAliyunDomain("https://ecs-test-static.qmth.com.cn");
-        properties.setAccessKeyId("LTAI4FboXLCJzrjVo5dUoXaU");
-        properties.setAccessKeySecret("O0my6eSAl1Ic62WvxEf3WlMXox1LNX");
-
-        OssClient ossClient = new OssClient();
-        ossClient.setProperties(properties);
-
-        final String dir = "D:/home/test";
-        File file = new File(dir + "/abc.png");
-
-        final String ossFilePath = "/test/abc.png";
-        // String fileUrl = ossClient.upload(file, ossFilePath, false);
-        // System.out.println(fileUrl);
-
-        File toFile = new File(dir + "/abc-new.png");
-        // ossClient.download(ossFilePath, toFile);
-
-        byte[] bytes = ossClient.download(ossFilePath);
+    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_ACCESS_KEY_ID = "LTAI4FboXLCJzrjVo5dUoXaU";
+        FssProperty.FSS_ACCESS_KEY_SECRET = "xxx";
+        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_ACCESS_KEY_ID = "AKID51lO89AEFNRNA90jGZiw9E5kCQh7djpn";
+        // FssProperty.FSS_ACCESS_KEY_SECRET = "xxx";
+        // FssProperty.FSS_URL_PREFIX = "https://examcloud-1252178304.cos.ap-guangzhou.myqcloud.com";
+
+        final String testDir = "D:/home/test";
+        File testFile = new File(testDir + "/abc.png");
+
+        final String filePath = "/test/abc.png";
+        final String fileMd5 = "80bf42bd4d12b2a38eff3b32249b17fd";
+
+        FssService fssService = FssFactory.getInstance(false);
+        // FileInfo result = fssService.writeFile(filePath, testFile, fileMd5);
+        byte[] testBytes = IOUtils.toByteArray(Files.newInputStream(testFile.toPath()));
+        FileInfo result = fssService.writeFile(filePath, testBytes, fileMd5);
+        System.out.println(result.getFileUrl());
+
+        File toFile = new File(testDir + "/ddd/abc-new.png");
+        // fssService.readFile(filePath, toFile);
+
+        byte[] bytes = fssService.readFile(filePath);
         FileUtil.saveToFile(bytes, toFile);
     }