package com.hotent.file.attachmentService; import cn.hutool.core.io.IoUtil; import cn.hutool.json.JSONUtil; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.internal.OSSHeaders; import com.aliyun.oss.model.*; import com.hotent.base.attachment.Attachment; import com.hotent.base.attachment.AttachmentService; import com.hotent.base.attachment.MultipartFileParam; import com.hotent.base.attachment.UploadShardResult; import com.hotent.base.cache.annotation.*; import com.hotent.base.constants.CacheKeyConst; import com.hotent.base.util.*; import com.hotent.file.model.AliyunOssObject; import com.hotent.file.model.AliyunOssSettings; import com.hotent.file.model.UploadProperties; import com.hotent.file.service.FlowUploadPropertiesService; import com.hotent.file.util.AppFileUtil; import io.jsonwebtoken.lang.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; import javax.annotation.Resource; import java.io.*; import java.net.URLEncoder; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; @Service public class AliyunOssAttachmentServiceImpl implements AttachmentService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final String aliyunOssTempDir=File.separator+"aliyunOssTempDir"; @Resource FlowUploadPropertiesService flowUploadPropertiesService; @Resource AliyunOssAttachmentServiceImpl aliyunOssAttachmentService; private AliyunOssObject initOssObject(Attachment attachment, String propertiesId) { AliyunOssObject aliyunOssObject=new AliyunOssObject(); AliyunOssSettings ossSettings = AppUtil.getBean(AliyunOssSettings.class); aliyunOssObject.setAliyunOssSettings(ossSettings); setObjectKeyAndFilePath(attachment, aliyunOssObject); if (StringUtil.isNotEmpty(propertiesId)) { UploadProperties uploadProperties = flowUploadPropertiesService.getUploadProperties(propertiesId, ossSettings); attachment.setEntryptName(uploadProperties.getEncryptName()== 0 ? false : true); setObjectKeyAndFilePath(attachment, aliyunOssObject); } return aliyunOssObject; } // 设置 ossSettings 的 objectKey和 attachment 的 filePath private void setObjectKeyAndFilePath(Attachment attachment, AliyunOssObject ossObject) { String fileParentPath = ""; String filePath = attachment.getFilePath(); if (StringUtil.isNotEmpty(filePath)) { fileParentPath = Paths.get(filePath).getParent().toString(); fileParentPath = fileParentPath.replaceAll("\\\\", "/"); if (fileParentPath.startsWith("/")) { fileParentPath = fileParentPath.substring(1); } } if (attachment.getEntryptName()) { filePath = fileParentPath + "/" + attachment.getId() + "." + attachment.getExtensionName(); } else { filePath = fileParentPath + "/" + attachment.getId() + "_" + attachment.getFileName() + "." + attachment.getExtensionName(); } ossObject.setObjectKey(filePath); attachment.setFilePath(filePath); } private String getFilePath(Attachment attachment){ String fileParentPath = ""; String filePath = attachment.getFilePath(); if (StringUtil.isNotEmpty(filePath)) { fileParentPath = Paths.get(filePath).getParent().toString(); fileParentPath = fileParentPath.replaceAll("\\\\", "/"); if (fileParentPath.startsWith("/")) { fileParentPath = fileParentPath.substring(1); } } if (attachment.getEntryptName()) { filePath = fileParentPath + "/" + attachment.getId() + "." + attachment.getExtensionName(); } else { filePath = fileParentPath + "/" + attachment.getId() + "_" + attachment.getFileName() + "." + attachment.getExtensionName(); } return filePath; } @Override public void remove(Attachment attachment, String propertiesId) throws Exception { AliyunOssObject ossObject = initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=ossObject.getAliyunOssSettings(); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); try { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } ossClient.deleteObject(ossSettings.getBucketName(), ossObject.getObjectKey()); } finally { ossClient.shutdown(); } } @Override public void upload(Attachment attachment, InputStream inputStream, String propertiesId) throws Exception { AliyunOssObject ossObject = initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=ossObject.getAliyunOssSettings(); //setObjectKeyAndFilePath(attachment, ossSettings); String objectKey = this.getFilePath(attachment); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); try { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } if(BeanUtils.isEmpty(inputStream)) { ossClient.putObject(ossSettings.getBucketName(), objectKey, new ByteArrayInputStream(attachment.getBytes())); }else{ ossClient.putObject(ossSettings.getBucketName(), objectKey, inputStream); } } finally { inputStream.close(); ossClient.shutdown(); removeTemp(attachment); } } @Override public void download(Attachment attachment, OutputStream outStream, String propertiesId) throws Exception { AliyunOssObject aliyunOssObject = initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); try (OSSObject ossObject = ossClient.getObject(ossSettings.getBucketName(), aliyunOssObject.getObjectKey())) { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } IoUtil.copy(ossObject.getObjectContent(), outStream); } finally { outStream.close(); ossClient.shutdown(); } } @Override public String getStoreType() { return "aliyunOss"; } @Override public boolean chekckFile(Attachment attachment, String propertiesId) throws Exception { AliyunOssObject aliyunOssObject = initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); try { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } boolean found = ossClient.doesObjectExist(ossSettings.getBucketName(), aliyunOssObject.getObjectKey()); return found; } finally { ossClient.shutdown(); } } public String getLocalTempFilePath(Attachment attachment){ String localFilePath=attachment.getFilePath(); localFilePath = localFilePath.replace("/", File.separator); if(!attachment.getFilePath().startsWith(File.separator)){ localFilePath=File.separator+localFilePath; } localFilePath=aliyunOssTempDir+localFilePath; return localFilePath; } @Override public byte[] getFileBytes(Attachment attachment) throws Exception { String localFilePath=getLocalTempFilePath(attachment); FileUtil.createFolderFile(localFilePath); File file=new File(localFilePath); if(!file.exists()){ String propertiesId = BeanUtils.isNotEmpty(attachment)?attachment.getProp6():""; AliyunOssObject aliyunOssObject = this.initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); try { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } ossClient.getObject(new GetObjectRequest(ossSettings.getBucketName(), attachment.getFilePath()), file); } finally { ossClient.shutdown(); } } try(FileInputStream fileInputStream=new FileInputStream(file); ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ) { int read; final byte[] bytes = new byte[1024]; while ((read = fileInputStream.read(bytes)) != -1) { byteArrayOutputStream.write(bytes, 0, read); } byteArrayOutputStream.flush(); return byteArrayOutputStream.toByteArray(); } } /** * 分片上传 * @param multipartFileParam * @param attachment * @param propertiesId * @return * @throws Exception */ @Override public UploadShardResult uploadByShard(MultipartFileParam multipartFileParam, Attachment attachment, String propertiesId) throws Exception { multipartFileParam.setUniqueKey(multipartFileParam.getIdentifier()+"_"+multipartFileParam.getUniqueIdCard()); String uploadId=aliyunOssAttachmentService.getUploadIdFromCache(multipartFileParam.getUniqueKey()); String fileId= aliyunOssAttachmentService.getIdentifierIdFromCache(multipartFileParam.getUniqueKey()); OSS ossClient=null; try { if(StringUtil.isEmpty(fileId)){ fileId=UniqueIdUtil.getSuid(); aliyunOssAttachmentService.putIdentifierIdCache(multipartFileParam.getUniqueKey(),fileId); } attachment.setId(fileId); AliyunOssObject aliyunOssObject = initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } //如果uploadId为空就是初始上传,需要初始化uploadId if(StringUtil.isEmpty(uploadId)){ uploadId=initUploadId(multipartFileParam,attachment,aliyunOssObject,ossClient); } multipartFileParam.setAliyunOssUploadId(uploadId); //进行分片上传 UploadShardResult uploadShardResult=partUpload(multipartFileParam,attachment,aliyunOssObject,ossClient); return uploadShardResult; }catch(Exception e){ aliyunOssAttachmentService.delUploadIdCache(multipartFileParam.getUniqueKey()); aliyunOssAttachmentService.delPartTagListCache(multipartFileParam.getUniqueKey()); aliyunOssAttachmentService.delIdentifierIdCache(multipartFileParam.getUniqueKey()); throw e; } finally { if(ossClient!=null) { ossClient.shutdown(); } } } /** * 调用阿里云初始化上传id * @param multipartFileParam * @param attachment * @param aliyunOssObject * @return */ public String initUploadId(MultipartFileParam multipartFileParam,Attachment attachment,AliyunOssObject aliyunOssObject,OSS ossClient) { AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); // 文件名称 ObjectMetadata metadata = new ObjectMetadata(); metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString()); metadata.setCacheControl("no-cache"); //设置文件为私有 metadata.setObjectAcl(CannedAccessControlList.Private); //指定该Object被下载时的名称。 metadata.setContentDisposition("attachment;filename=" + attachment.getFileName()); // 创建InitiateMultipartUploadRequest对象。 InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(ossSettings.getBucketName(), aliyunOssObject.getObjectKey()); // 初始化分片。 InitiateMultipartUploadResult upResult = ossClient.initiateMultipartUpload(request); String uploadId=upResult.getUploadId(); aliyunOssAttachmentService.putUploadIdCache(multipartFileParam.getUniqueKey(),uploadId); aliyunOssAttachmentService.putPartETagListCache(multipartFileParam.getUniqueKey(),new ArrayList<>()); return uploadId; } /** * 调用阿里云进行分片上传 * @param multipartFileParam * @param attachment * @param aliyunOssObject * @param ossClient * @return * @throws Exception */ public UploadShardResult partUpload(MultipartFileParam multipartFileParam,Attachment attachment,AliyunOssObject aliyunOssObject,OSS ossClient) throws Exception { AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); String uploadId = multipartFileParam.getAliyunOssUploadId(); StopWatch st = new StopWatch(); st.start(); //封装分片上传请求 UploadPartRequest uploadPartRequest = new UploadPartRequest(); uploadPartRequest.setUploadId(uploadId); //part大小 1-10000 uploadPartRequest.setPartNumber(multipartFileParam.getChunkNumber()); uploadPartRequest.setPartSize(multipartFileParam.getCurrentChunkSize()); uploadPartRequest.setBucketName(ossSettings.getBucketName()); uploadPartRequest.setKey(aliyunOssObject.getObjectKey()); // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。 UploadPartResult uploadPartResult = null; try(ByteArrayInputStream inputStream=new ByteArrayInputStream(multipartFileParam.getFile().getBytes())){ uploadPartRequest.setInputStream(inputStream); uploadPartResult = ossClient.uploadPart(uploadPartRequest); } catch (OSSException e) { e.printStackTrace(); throw new RuntimeException("上传失败:" + e.getMessage()); } catch (com.aliyun.oss.ClientException e) { e.printStackTrace(); throw new RuntimeException("上传失败,客户端错误:" + e.getMessage()); } st.stop(); logger.info("{}文件第 {} 片上传成功,上传结果:{},耗时:{}", uploadId, uploadPartRequest.getPartNumber(), JSONUtil.toJsonStr(uploadPartResult), st.getTotalTimeMillis()); List partETagList=aliyunOssAttachmentService.getPartTagListFromCache(multipartFileParam.getUniqueKey()); Assert.notNull(partETagList,"获取缓存分片历史失败!"); partETagList.add(uploadPartResult.getPartETag()); aliyunOssAttachmentService.putPartETagListCache(multipartFileParam.getUniqueKey(),partETagList); if (multipartFileParam.getChunkNumber() == multipartFileParam.getTotalChunks()) { //上传成功 // 创建CompleteMultipartUploadRequest对象。 // 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。 // 当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。 mergeMultipart(partETagList,uploadId,aliyunOssObject,ossClient); aliyunOssAttachmentService.delUploadIdCache(multipartFileParam.getUniqueKey()); aliyunOssAttachmentService.delPartTagListCache(multipartFileParam.getUniqueKey()); aliyunOssAttachmentService.delIdentifierIdCache(multipartFileParam.getUniqueKey()); return new UploadShardResult(true); } return new UploadShardResult(false); } /*** * 当分片上传为最后一个分片时,进行合并分片操作 * @param uploadId * @param aliyunOssObject * @param ossClient * @return */ private CompleteMultipartUploadResult mergeMultipart(List partTagList,String uploadId,AliyunOssObject aliyunOssObject,OSS ossClient) throws Exception { AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); try { StopWatch st = new StopWatch(); st.start(); CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(ossSettings.getBucketName(), aliyunOssObject.getObjectKey(), uploadId, partTagList); logger.info("{}文件上传完成,开始合并,partList:{}", uploadId, JSONUtil.toJsonStr(partTagList)); // 完成分片上传。 CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest); st.stop(); logger.info("{}文件上传完成,上传结果:{},耗时:{}", uploadId, JSONUtil.toJsonStr(completeMultipartUploadResult), st.getTotalTimeMillis()); return completeMultipartUploadResult; } catch (OSSException e) { logger.error("合并失败",e); throw new RuntimeException("合并失败:" + e.getMessage()); } catch (com.aliyun.oss.ClientException e) { logger.error("合并失败,客户端异常",e); throw new RuntimeException("合并失败,客户端异常:" + e.getMessage()); } } /** * 缓存阿里云上传id * @param key * @return */ @Cacheable(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_ID, key="#key", firstCache = @FirstCache(expireTime = 1, timeUnit = TimeUnit.DAYS),secondaryCache = @SecondaryCache(expireTime = 1, timeUnit = TimeUnit.DAYS)) public String getUploadIdFromCache(String key) { return null; } @CachePut(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_ID, key="#key", firstCache = @FirstCache(expireTime = 1, timeUnit = TimeUnit.DAYS),secondaryCache = @SecondaryCache(expireTime = 1, timeUnit = TimeUnit.DAYS)) public String putUploadIdCache(String key,String uploadId) { return uploadId; } @CacheEvict(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_ID, key="#key") public void delUploadIdCache(String key) {} /** * 缓存阿里云上传分片记录 * @param key * @return */ @Cacheable(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_PARTLIST, key="#key", firstCache = @FirstCache(expireTime = 1, timeUnit = TimeUnit.DAYS),secondaryCache = @SecondaryCache(expireTime = 1, timeUnit = TimeUnit.DAYS)) public List getPartTagListFromCache(String key) { return null; } @CachePut(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_PARTLIST, key="#key", firstCache = @FirstCache(expireTime = 1, timeUnit = TimeUnit.DAYS),secondaryCache = @SecondaryCache(expireTime = 1, timeUnit = TimeUnit.DAYS)) public List putPartETagListCache(String key, List partETagList) { return partETagList; } @CacheEvict(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_PARTLIST, key="#key") public void delPartTagListCache(String key) {} /** * 缓存分片上传文件id * @param key * @return */ @Cacheable(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_FILE_ID, key="#key", firstCache = @FirstCache(expireTime = 1, timeUnit = TimeUnit.DAYS),secondaryCache = @SecondaryCache(expireTime = 1, timeUnit = TimeUnit.DAYS)) public String getIdentifierIdFromCache(String key) { return null; } @CachePut(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_FILE_ID, key="#key", firstCache = @FirstCache(expireTime = 1, timeUnit = TimeUnit.DAYS),secondaryCache = @SecondaryCache(expireTime = 1, timeUnit = TimeUnit.DAYS)) public String putIdentifierIdCache(String key,String id) { return id; } @CacheEvict(value = CacheKeyConst.EIP_UPLOAD_ALIYUN_FILE_ID, key="#key") public void delIdentifierIdCache(String key) {} @Override public Map generatePolicy(Attachment attachment, String propertiesId){ AliyunOssObject aliyunOssObject = initOssObject(attachment, propertiesId); AliyunOssSettings ossSettings=aliyunOssObject.getAliyunOssSettings(); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); String host = "https://" + ossSettings.getBucketName() + "." + ossSettings.getEndpoint(); // host的格式为 bucketname.endpoint // callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。 // String callbackUrl = "http://88.88.88.88:8888"; String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); String dir = attachment.getProp1(); //文件夹路径 Map respMap = null; try { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } long expireTime = 60*60*24; long expireEndTime = System.currentTimeMillis() + expireTime * 1000; Date expiration = new Date(expireEndTime); // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。 PolicyConditions policyConds = new PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 1, 1048576000); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir); String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); byte[] binaryData = postPolicy.getBytes("utf-8"); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); respMap = new LinkedHashMap<>(); respMap.put("accessid", ossSettings.getAccessKeyId()); respMap.put("policy", encodedPolicy); respMap.put("signature", postSignature); respMap.put("dir", dir); respMap.put("host", host); respMap.put("expire", String.valueOf(expireEndTime / 1000)); // respMap.put("expire", formatISO8601Date(expiration)); } catch (Exception e) { // Assert.fail(e.getMessage()); System.out.println(e.getMessage()); } finally { ossClient.shutdown(); } return respMap; } @Override public String getUrl(Attachment attachment, String propertiesId) throws UnsupportedEncodingException { Map map = this.generatePolicy(attachment, ""); String host = BeanUtils.isEmpty(map.get("host")) ? "" : map.get("host").toString(); String dir = BeanUtils.isEmpty(map.get("dir")) ? "" : map.get("dir").toString(); String accessid = BeanUtils.isEmpty(map.get("accessid")) ? "" : map.get("accessid").toString(); String signature = BeanUtils.isEmpty(map.get("signature")) ? "" : map.get("signature").toString(); String Expires = BeanUtils.isEmpty(map.get("Expires")) ? "" : map.get("Expires").toString(); String s = "OSSAccessKeyId=" + accessid + "&Signature=" + signature + "&Expires=" + Expires; return host + "/" + dir + "?" + URLEncoder.encode(s, "utf-8"); } @Override public InputStream getFileInputStream(Attachment attachment) throws FileNotFoundException { String localFilePath=getLocalTempFilePath(attachment); FileUtil.createFolderFile(localFilePath); File file=new File(localFilePath); if(!file.exists()){ AliyunOssSettings ossSettings = AppUtil.getBean(AliyunOssSettings.class); OSS ossClient = new OSSClientBuilder().build(ossSettings.getEndpoint(), ossSettings.getAccessKeyId(), ossSettings.getAccessKeySecret()); try { if (!ossClient.doesBucketExist(ossSettings.getBucketName())) { ossClient.createBucket(ossSettings.getBucketName()); } ossClient.getObject(new GetObjectRequest(ossSettings.getBucketName(), attachment.getFilePath()), file); } finally { ossClient.shutdown(); } } return new FileInputStream(file); } @Override public String getFilePath(String account,String fileName) { if(StringUtil.isEmpty(account)){ account="_unknow"; } return AppFileUtil.getAliOssForderPath(account)+fileName; } public void removeTemp(Attachment attachment) { String localFilePath=getLocalTempFilePath(attachment); FileUtil.createFolderFile(localFilePath); File file=new File(localFilePath); if(file.exists()){ file.delete(); } } }