package com.backendsys.modules.upload.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import com.backendsys.exception.CustException; import com.backendsys.modules.common.config.security.utils.HttpRequestUtil; import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService; import com.backendsys.modules.system.entity.SysCommon; import com.backendsys.modules.system.service.SysCommonService; import com.backendsys.modules.upload.dao.SysFileDao; import com.backendsys.modules.upload.entity.SysFile; import com.backendsys.modules.upload.service.SysFileMultipartService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.qcloud.cos.model.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @Service public class SysFileMultipartServiceImpl implements SysFileMultipartService { @Autowired private HttpRequestUtil httpRequestUtil; @Autowired private TencentCosService tencentCosService; @Autowired private SysFileDao sysFileDao; @Autowired private SysCommonService sysCommonService; /* 查一下 upload_id 多久会过期 */ // 1.初始化分块上传 (获得 upload_id, object_key) @Override public Map multipartUploadInit(MultipartFile multipartFile, Long category_id, Integer upload_chunk_count) { if (multipartFile.isEmpty()) throw new CustException("file 上传文件不能为空"); if (upload_chunk_count == null) throw new CustException("upload_chunk_count 分块数量不能为空"); // 获得公共配置 List sysCommonList = sysCommonService.getCommonByCategory("UPLOAD"); AtomicReference UPLOAD_TARGET = new AtomicReference<>(); sysCommonList.stream().forEach(sysCommon -> { if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue())); }); System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET); String target_label = ""; String upload_id = null; String object_key = null; // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云) if (UPLOAD_TARGET.get() == 1) { target_label = "腾讯云初始化分块"; InitiateMultipartUploadResult uploadResult = tencentCosService.initiateMultipartUpload(multipartFile); upload_id = uploadResult.getUploadId(); object_key = uploadResult.getKey(); } // 如果没有获得以上参数,则抛出报错 if (StrUtil.isEmpty(upload_id) || StrUtil.isEmpty(object_key)) { throw new CustException("upload_id 或 object_key 不能为空"); } // [新增] 上传文件记录 SysFile sysFileEntity = new SysFile(); sysFileEntity.setCategory_id(category_id); sysFileEntity.setUser_id(httpRequestUtil.getUserId()); sysFileEntity.setUpload_id(upload_id); sysFileEntity.setUpload_chunk_count(upload_chunk_count); sysFileEntity.setUpload_chunk_index(0); sysFileEntity.setName(FileNameUtil.getName(object_key)); sysFileEntity.setObject_key(object_key); sysFileEntity.setTarget(UPLOAD_TARGET.get()); sysFileEntity.setContent_type(multipartFile.getContentType()); sysFileEntity.setSize(multipartFile.getSize()); sysFileDao.insert(sysFileEntity); Map resp = new LinkedHashMap<>(); resp.put("target", UPLOAD_TARGET.get()); resp.put("target_label", target_label); resp.put("upload_chunk_count", upload_chunk_count); resp.put("upload_id", upload_id); resp.put("object_key", object_key); return resp; } // 2.上传分块 @Override public Map multipartUpload(MultipartFile multipartFile, String upload_id, Integer upload_chunk_index) { if (multipartFile.isEmpty()) throw new CustException("file 上传文件不能为空"); if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空"); if (upload_chunk_index == null) throw new CustException("upload_chunk_index 不能为空"); // [Get] 获得初始化分块信息 SysFile sysFileEntity = sysFileDao.selectOne(new LambdaQueryWrapper().eq(SysFile::getUpload_id, upload_id)); if (sysFileEntity == null) throw new CustException("分块记录不存在"); String object_key = sysFileEntity.getObject_key(); Integer upload_chunk_size = sysFileEntity.getUpload_chunk_count(); if (upload_chunk_index > upload_chunk_size) throw new CustException("分块索引(index)不能大于分块数量(count)"); // 获得公共配置 List sysCommonList = sysCommonService.getCommonByCategory("UPLOAD"); AtomicReference UPLOAD_TARGET = new AtomicReference<>(); sysCommonList.stream().forEach(sysCommon -> { if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue())); }); System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET); PartETag part_etag = null; // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云) if (UPLOAD_TARGET.get() == 1) { UploadPartResult partResult = tencentCosService.uploadPart(multipartFile, upload_id, object_key, upload_chunk_index); System.out.println("partResult:"); System.out.println(partResult); part_etag = partResult.getPartETag(); } // // 查询分块上传情况 (获得下次要上传的位置) // PartListing partListing = listParts(upload_id, object_key); // Integer partNumberMarker = partListing.getPartNumberMarker() + 1; // [Update] 更新分块文件信息 sysFileEntity.setUpload_chunk_index(upload_chunk_index); sysFileDao.updateById(sysFileEntity); Map resp = new LinkedHashMap<>(); resp.put("upload_chunk_index", upload_chunk_index); resp.put("upload_id", upload_id); resp.put("object_key", object_key); resp.put("part_etag", part_etag); return resp; } // 完成分块上传 @Override public Map multipartUploadComplete(String upload_id) { if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空"); // [Get] 查询文件分块记录 SysFile sysFileEntity = sysFileDao.selectOne(new LambdaQueryWrapper().eq(SysFile::getUpload_id, upload_id)); if (sysFileEntity == null) throw new CustException("upload_id 不存在"); // 判断分块索引和分块数量是否一致 if (sysFileEntity.getUpload_chunk_index() != sysFileEntity.getUpload_chunk_count()) { throw new CustException("分块索引和分块数量不一致"); } // 查询分块集合 (etag) PartListing partListing = listParts(upload_id, sysFileEntity.getObject_key()); List partSummaryList = partListing.getParts(); List etags = partSummaryList.stream() .map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag())) .collect(Collectors.toList()); // 如果分块数量对不上,最好还是重新上传 if (sysFileEntity.getUpload_chunk_count() != etags.size()) { throw new CustException("分块索引异常,请重新上传"); } // 合并分块 tencentCosService.completeMultipartUpload(upload_id, sysFileEntity.getObject_key(), etags); return Map.of("upload_id", upload_id); } // 查询分块上传情况 @Override public PartListing listParts(String upload_id, String object_key) { return tencentCosService.listParts(upload_id, object_key); } }