派生自 projectDept/qhighschool

胡仁荣
2023-07-18 885290e4d0d0c7fad3f538d901c616e49c3d6985
src/main/java/com/qxueyou/scc/teach/res/service/impl/HweiYunOBSServiceImpl.java
@@ -1,29 +1,40 @@
package com.qxueyou.scc.teach.res.service.impl;
//import com.example.study.springboot.background.service.HweiYunOBSService;
//import com.example.study.springboot.config.HweiOBSConfig;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import com.qxueyou.scc.base.util.CollectionUtils;
import com.qxueyou.scc.base.util.QFileUtils;
import com.qxueyou.scc.base.util.UUIDUtils;
import com.qxueyou.scc.base.model.FileMeta;
import com.qxueyou.scc.base.model.Result;
import com.qxueyou.scc.base.service.ICacheService;
import com.qxueyou.scc.base.service.impl.CommonAppService;
import com.qxueyou.scc.base.util.*;
import com.qxueyou.scc.config.HweiOBSConfig;
import com.qxueyou.scc.teach.res.model.Res;
import com.qxueyou.scc.teach.res.model.ResDir;
import com.qxueyou.scc.teach.res.model.ResFile;
import com.qxueyou.scc.teach.res.service.HweiYunOBSService;
import com.qxueyou.scc.teach.res.service.IFileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.core.ApplicationPart;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.util.FileUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
 * @ClassName: HweiYunOBSServiceImpl
@@ -34,11 +45,21 @@
 */
@Slf4j
@Service
public class HweiYunOBSServiceImpl implements HweiYunOBSService {
public class HweiYunOBSServiceImpl extends CommonAppService implements HweiYunOBSService {
    private final Logger log = LogManager.getLogger(HweiYunOBSServiceImpl.class);
    @Autowired
    private HweiOBSConfig hweiOBSConfig;
    @Autowired
    FileService fileService;
    /**
     * redis 模板
     */
    @Autowired
    ICacheService cache;
    public final static String FILE_TYPE_VIDEO = "video";
@@ -65,7 +86,7 @@
            "JPG", FILE_TYPE_IMG,"JPEG", FILE_TYPE_IMG, "GIF", FILE_TYPE_IMG, "BMP", FILE_TYPE_IMG, "PNG", FILE_TYPE_IMG
    );
    @Override
    public boolean delete(String objectKey) {
        ObsClient obsClient = null;
@@ -81,7 +102,7 @@
        }
        return true;
    }
    @Override
    public boolean delete(List<String> objectKeys) {
        ObsClient obsClient = null;
@@ -99,10 +120,13 @@
        }
        return false;
    }
    @Override
    public String fileUpload(MultipartFile uploadFile, String objectKey) {
    public List<FileMeta> fileUpload(MultipartFile uploadFile, String objectKey) {
        ObsClient obsClient = null;
        List<FileMeta> files = new ArrayList<FileMeta>(2);
        FileMeta fileMeta = null;
        try {
            String destPath = getDestPath(objectKey);
            String bucketName = hweiOBSConfig.getBucketName();
@@ -118,19 +142,29 @@
            }
            InputStream inputStream = uploadFile.getInputStream();
            long available = inputStream.available();
//            PutObjectRequest request = new PutObjectRequest(bucketName,objectKey,inputStream);
            PutObjectRequest request = new PutObjectRequest(bucketName,destPath,inputStream);
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setContentLength(available);
            request.setMetadata(objectMetadata);
//            request.
            // 设置对象访问权限为公共读
            request.setAcl(AccessControlList.REST_CANNED_PUBLIC_READ);
            PutObjectResult result = obsClient.putObject(request);
            fileMeta = new FileMeta();
            ResFile file = fileService.insertFileToDBTwo(objectKey, uploadFile.getSize(), result.getObjectUrl(), uploadFile.getContentType());
//            SetObjectMetadataRequest ObjectMetadataRequest = new SetObjectMetadataRequest(bucketName, destPath);
//            ObjectMetadataRequest.setContentDisposition("inline");
//            obsClient.setObjectMetadata(ObjectMetadataRequest);
            fileMeta.setFileId(file.getFileId());
            fileMeta.setPath(result.getObjectUrl());
            fileMeta.setFileSize(uploadFile.getSize() / 1024 + "kb");
            fileMeta.setFileType(uploadFile.getContentType());
            fileMeta.setFileName(objectKey);
            files.add(fileMeta);
            // 读取该已上传对象的URL
            log.info("已上传对象的URL" + result.getObjectUrl());
            return result.getObjectUrl();
            return files;
//            return result.getObjectUrl();
        } catch (ObsException e) {
            log.error("obs上传失败", e);
        } catch (IOException e) {
@@ -141,20 +175,129 @@
        return null;
    }
    @Override
    public List<FileMeta> fnepian(MultipartFile uploadFile, String objectKey) throws IOException {
        ObsClient obsClient = null;
        List<FileMeta> files = new ArrayList<FileMeta>(2);
        FileMeta fileMeta = null;
        // 每个分片的大小,用于计算文件有多少个分片。单位为字节。
        final long partSize = 10 * 1024 * 1024L;   //10 MB。
        String bucketName = hweiOBSConfig.getBucketName();
        obsClient = hweiOBSConfig.getInstance();
        // objectName 是路径,分片文件最终整合的路径
        String objectName = "multipartUpload/"+objectKey;
        // 创建InitiateMultipartUploadRequest对象。
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
        InitiateMultipartUploadResult prebuilt = obsClient.initiateMultipartUpload(request);
        InputStream inputStream = uploadFile.getInputStream();
        // 获取全局id
        String uploadId = prebuilt.getUploadId();
        // 如果并发量大的话 初始化线程池  20线程数量
        //  ExecutorService executorService = Executors.newFixedThreadPool(20);
        // partTags是PartEtag的集合。PartEtag由分片的ETag和分片号组成。
        List<PartEtag> partTags = new ArrayList<PartEtag>();
        File file = MultipartFileToFile(uploadFile);
        // 计算有多少个分片
        int partCount = (int) (file.length() / partSize);
        // 如果取余数不为零则追加一个
        if (file.length() % partSize != 0) {
            partCount++;
        }
        try {
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (file.length() - startPos) : partSize;
                // 跳过已经上传的分片。
                inputStream.skip(startPos);
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setObjectKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInput(inputStream);
                // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,Obs将返回InvalidArgument错误码。
                uploadPartRequest.setPartNumber( i + 1);
                // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,obs会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                // 每次上传分片之后,ObS的返回结果包含PartETag。PartETag将被保存在partTags中。
                partTags.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
            }
            // 开始线程池时候使用:等待上传完成
         /*   executorService.shutdown();
            while (!executorService.isTerminated())
            {
                try
                {
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }*/
            fileMeta = new FileMeta();
            // 合并分片段
            // 在执行完成分片上传操作时,需要提供所有有效的partETags。obs收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,obs将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest multipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partTags);
            // 完成分片上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = obsClient.completeMultipartUpload(multipartUploadRequest);
            // 返回全局id,桶名,对象名称 用于后续处理 比如取消分片上传,查询分片信息等
            fileMeta.setPath(completeMultipartUploadResult.getObjectUrl());
            fileMeta.setFileSize(uploadFile.getSize() / 1024 + "kb");
            fileMeta.setFileType(uploadFile.getContentType());
            fileMeta.setFileName(objectKey);
            files.add(fileMeta);
            return files;
        }catch (Exception e){
            log.error("分片循环上传失败!"+e.getMessage());
            return null;
        }
    }
    public static File MultipartFileToFile(MultipartFile multiFile) {
        // 获取文件名
        String fileName = multiFile.getOriginalFilename();
        // 获取文件后缀
        String prefix = fileName.substring(fileName.lastIndexOf("."));
        // 若须要防止生成的临时文件重复,能够在文件名后添加随机码
        try {
            File file = File.createTempFile(getFileNameNotPrefix(fileName), prefix);
            multiFile.transferTo(file);
            return file;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 获取文件名不带后缀
     *
     * @param fileName
     * @return
     */
    public static String getFileNameNotPrefix(String fileName) {
        String prefix = fileName.substring(fileName.indexOf("."));
        int num = prefix.length();//得到后缀名长度
        String fileOtherName = fileName.substring(0, fileName.length() - num);//得到文件名。去掉了后缀
        return fileOtherName;
    }
    private String getDestPath(String name) {
        String fileType = getFileType(name);
        StringBuffer path = new StringBuffer(128);
        path.append(fileType);
//        path.append(fileType);
        Calendar now = new GregorianCalendar();
        path.append('/');
        path.append(now.get(Calendar.YEAR));
        path.append(StringUtils.leftPad(String.valueOf(now.get(Calendar.MONTH)), 2, '0'));
        path.append('/');
        path.append(now.get(Calendar.DAY_OF_MONTH));
        path.append('/');
//        path.append('/');
//        path.append(now.get(Calendar.YEAR));
//        path.append(StringUtils.leftPad(String.valueOf(now.get(Calendar.MONTH)), 2, '0'));
//        path.append('/');
//        path.append(now.get(Calendar.DAY_OF_MONTH));
//        path.append('/');
        path.append(UUIDUtils.UUID());
        path.append('.');
        path.append(QFileUtils.getFileFormat(name));
@@ -182,4 +325,212 @@
        }
        return null;
    }
}
    /**
     * 文件分片上传实现逻辑,各分片是多线程上传
     *
     * @param input       输入流
     * @param uniqueId    资源唯一id(前端传入)
     * @param chunkNumber 分片编号
     * @param chunkSize   分片大小
     * @param totalChunk  分片总数
     * @param fileName    文件名
     * @return
     */
    public Result uploadChunk(InputStream input, String uniqueId, int chunkNumber, long chunkSize, int totalChunk,
                              String fileName) {
        HashOperations<Object, Object, Object> _cacheMap = cache.template().opsForHash();
        ObsClient obsClient = new ObsClient(hweiOBSConfig.getAccessKey(),hweiOBSConfig.getSecurityKey(),hweiOBSConfig.getEndPoint());
        //给初始化预留1.2秒钟的时间
        try {
            if (chunkNumber < 4) {
                Thread.sleep(1200l);
            }
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
        }
        //获取分片上传id(阿里云OSS初始化分片上传任务返回,和uniqueId及key一一对应),若获取不到,则10秒内循环十次直至获取到,否则返回错误
        String uploadId = getUploadId(uniqueId);
        if (uploadId == null) {
            return new Result(false);
        }
        String key = (String) _cacheMap.get(uniqueId + ClientUtils.getUserId(), "key");
        boolean exists = true;
        //上传片段
        if (!_cacheMap.hasKey(uploadId, String.valueOf(chunkNumber))) {
            //开始上传
            UploadPartRequest uploadPartRequest = new UploadPartRequest(hweiOBSConfig.getBucketName(), key);
            uploadPartRequest.setUploadId(uploadId);
            // 设置分段号,范围是1~10000
            uploadPartRequest.setPartNumber(chunkNumber);
            // 设置分段大小
            uploadPartRequest.setPartSize(chunkSize);
            uploadPartRequest.setInput(input);
            UploadPartResult partResult = obsClient.uploadPart(uploadPartRequest);
            exists = false;
            _cacheMap.put(uploadId, String.valueOf(chunkNumber), partResult.getEtag());
        }
        //全部片段上传完成,调用client complete方法完成分片上传任务,并插入数据库
        if (_cacheMap.size(uploadId) != null
                && _cacheMap.size(uploadId) >= totalChunk
                && !_cacheMap.hasKey(uniqueId + ClientUtils.getUserId(), "finish")) {
            List<PartEtag> tags = new ArrayList<>(5);
            _cacheMap.entries(uploadId).forEach((k, v) -> {
                tags.add(new PartEtag((String) v, Integer.parseInt((String) k)));
            });
            CompleteMultipartUploadResult completeMultipartUploadResult = obsClient.completeMultipartUpload(new CompleteMultipartUploadRequest(hweiOBSConfig.getBucketName(), key, uploadId,
                    tags));
            System.out.println(completeMultipartUploadResult.getObjectUrl());
            ObjectMetadata metadata = obsClient.getObjectMetadata(hweiOBSConfig.getBucketName(), key);
//            obsClient.setObjectAcl(hweiOBSConfig.getBucketName(), key, AccessControlList.REST_CANNED_PUBLIC_READ);
            //标记完成
            _cacheMap.put(uniqueId + ClientUtils.getUserId(), "finish", "true");
//            //更新数据库信息
            updateFile((String) _cacheMap.get(uniqueId + ClientUtils.getUserId(), "fileId"), metadata.getContentLength(), metadata.getContentMd5(), key, fileName);
            //销毁缓存
            cache.template().expire(uniqueId + ClientUtils.getUserId(), 1, TimeUnit.MINUTES);
        }
        return new Result(true, "hit", exists, "fileId", _cacheMap.get(uniqueId + ClientUtils.getUserId(), "fileId"), "path", _cacheMap.get(uniqueId + ClientUtils.getUserId(), "path"));
    }
    @Override
    public Result initUploadChunk(String uniqueId, String fileName, String md5) {
        String path = getDestPath(fileName);
        String key = chopPath(path);
        //清缓存
        cache.template().delete(uniqueId + ClientUtils.getUserId());
        InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(hweiOBSConfig.getBucketName(), key);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setObjectStorageClass(StorageClassEnum.STANDARD);
        initiateMultipartUploadRequest.setMetadata(new ObjectMetadata());
        //调用OSS SDK 接口返回uploadId
        ObsClient obsClient = new ObsClient(hweiOBSConfig.getAccessKey(),hweiOBSConfig.getSecurityKey(),hweiOBSConfig.getEndPoint());
        String uploadId = obsClient.initiateMultipartUpload(initiateMultipartUploadRequest).getUploadId();
        //为配合分片上传,后台先新建文件记录
        ResFile file = newFileToDB(fileName, path, getFileType(fileName), 0l, md5);
        //添加到缓存
        cache.template().opsForHash().put(uniqueId + ClientUtils.getUserId(), "uploadId", uploadId);
        cache.template().opsForHash().put(uniqueId + ClientUtils.getUserId(), "key", key);
        cache.template().opsForHash().put(uniqueId + ClientUtils.getUserId(), "path", path);
        cache.template().opsForHash().put(uniqueId + ClientUtils.getUserId(), "fileId", file.getFileId());
        //防止产生过多缓存垃圾
        cache.template().expire(uniqueId + ClientUtils.getUserId(), 1, TimeUnit.DAYS);
        ResFile file1 = new ResFile();
        file1.setUpdateId(uploadId);
        file1.setFileId(file.getFileId());
        file1.setPath(file.getPath());
        return new Result(true,"cs",file1);
    }
    /**
     * 更新文件信息到数据库
     *
     * @param fileId
     * @param fileLength
     * @param md5
     * @param path
     * @param fileName
     * @return
     */
    private ResFile updateFile(String fileId, long fileLength, String md5, String path, String fileName) {
        ResFile file = new ResFile();
        file.setFileId(fileId);
        file.setSize(fileLength);
        file.setMd5Hash(md5);
        file.setPath(path);
        file.setFileName(fileName);
        cache.template().opsForList().rightPush("BaseOssServiceUpdate", file);
        return file;
    }
    /**
     * 插入文件信息到数据库
     *
     * @param name
     * @param relativePath 相对路径
     * @param type         文件类型,非文件格式
     * @return
     */
    private ResFile newFileToDB(String name, String relativePath, String type, Long fileSize, String md5) {
        ResFile file = new ResFile();
        TraceUtils.setCreateTrace(file);
        file.setFileName(name);
        file.setFileFormat(QFileUtils.getFileFormat(name));
        file.setFileType(type);
        file.setMd5Hash(md5);
        file.setPath(relativePath);
        file.setSize(fileSize == null ? 0 : fileSize);
        file.setDeleteFlag(false);
        save(file);
        return file;
    }
    /**
     * 如果路径以 / 或 \ 开头,需要截取
     *
     * @param destPath
     * @return
     */
    public String chopPath(String destPath) {
        if (destPath.startsWith("/") || destPath.startsWith("\\")) {
            return destPath.substring(1);
        }
        return destPath;
    }
    /**
     * 获取分片上传id(阿里云OSS初始化分片上传任务返回,和uniqueId及key一一对应),若获取不到,则10秒内循环十次直至获取到,否则返回错误
     *
     * @param uniqueId
     * @return
     */
    private String getUploadId(String uniqueId) {
        HashOperations<Object, Object, Object> _cacheMap = cache.template().opsForHash();
        String uploadId = (String) _cacheMap.get(uniqueId + ClientUtils.getUserId(), "uploadId");
        int tryCount = 0;
        while (uploadId == null && tryCount < 10) {
            try {
                Thread.sleep(1000l);
                uploadId = (String) _cacheMap.get(uniqueId + ClientUtils.getUserId(), "uploadId");
                tryCount++;
            } catch (InterruptedException e) {
                log.error(e, e);
            }
        }
        return uploadId;
    }
}