package com.backendsys.modules.upload.service.impl; import cn.hutool.core.codec.Base64; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; 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.douyincloud.tos.service.DouyinTosService; 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.entity.SysFileResult; import com.backendsys.modules.upload.service.SysFileService; import com.backendsys.utils.response.PageEntity; import com.backendsys.utils.response.PageInfoResult; import com.backendsys.utils.v2.PageUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @Service public class SysFileServiceImpl implements SysFileService { @Autowired private HttpRequestUtil httpRequestUtil; @Autowired private TencentCosService tencentCosService; @Autowired private DouyinTosService douyinTosService; @Autowired private SysFileDao sysFileDao; @Autowired private SysCommonService sysCommonService; /** * 获取上传文件列表 */ @Override public PageEntity selectUploadFileList(SysFile sysFile) { PageUtils.startPage(); // 分页 List sysFileList = sysFileDao.selectUploadFileList(sysFile); Integer width = 100; Integer height = 100; String backgroundColor = "#f8f8f8"; // 不同的云环境 (target),缩略图配置也不一样 // - -1:本地: // - 1:腾讯云: https://cloud.tencent.com/document/product/436/113295 // - 2:阿里云: // - 3:抖音云: https://www.volcengine.com/docs/6349/153626 sysFileList = sysFileList.stream().map(item -> { // 腾讯云 (color值通过base64加密, #f8f8f8) if (item.getTarget() == 1) { System.out.println("base64 encode: " + Base64.encode(backgroundColor)); item.setUrl_thumb(item.getUrl() + "?imageMogr2/thumbnail/" + width + "x" + height + "/pad/1/color/" + Base64.encode(backgroundColor)); } // 抖音云 if (item.getTarget() == 3) { item.setUrl_thumb(item.getUrl() + "?x-tos-process=image/resize,w_" + width + ",h_" + height + ",m_pad,color_" + backgroundColor); } return item; }).collect(Collectors.toList()); return new PageInfoResult(sysFileList).toEntity(); } // [方法] 上传事件 private SysFile uploadEvent(MultipartFile multipartFile, Long category_id, Integer target) { try { SysFileResult uploadResult = new SysFileResult(); // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云) if (target == 1) { // [腾讯云-上传对象] uploadResult = tencentCosService.putObject(multipartFile, null); } if (target == 3) { // [抖音云-上传对象] uploadResult = douyinTosService.putObject(multipartFile, null); } // [新增] 上传文件记录 SysFile sysFileEntity = new SysFile(); sysFileEntity.setCategory_id(category_id); sysFileEntity.setRequest_id(uploadResult.getRequest_id()); sysFileEntity.setUser_id(httpRequestUtil.getUserId()); sysFileEntity.setObject_key(uploadResult.getKey()); // 文件名 // sysFileEntity.setName(FileNameUtil.getName(uploadResult.getKey())); String filename = multipartFile.getOriginalFilename(); String filename_prefix = StrUtil.subBefore(filename, '.', true); String filename_suffix = StringUtils.getFilenameExtension(filename); /* filename = 200x134.png filename_prefix = 200x134 filename_suffix = png */ // 查询如果有同名文件,则在文件中加入数量命名 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); // 构造正则表达式 (匹配同名、同名(数量)) String regex = "^" + filename_prefix + "(\\(\\d+\\))?\\." + filename_suffix + "$"; wrapper.apply("name REGEXP {0}", regex); Long count = sysFileDao.selectCount(wrapper); if (count > 0) { sysFileEntity.setName(filename_prefix + "(" + count + ")." + filename_suffix); } else { sysFileEntity.setName(filename_prefix + "." + filename_suffix); } sysFileEntity.setContent_type(multipartFile.getContentType()); sysFileEntity.setUrl(uploadResult.getDomain() + "/" + uploadResult.getKey()); sysFileEntity.setSize(multipartFile.getSize()); if (StrUtil.isNotEmpty(uploadResult.getE_tag())) { sysFileEntity.setMd5(uploadResult.getE_tag().replace("\"", "")); } sysFileEntity.setTarget(target); sysFileEntity.setCreate_time(DateUtil.now()); sysFileEntity.setUpdate_time(DateUtil.now()); sysFileDao.insert(sysFileEntity); return sysFileEntity; } catch (IOException e) { throw new RuntimeException(e); } } /** * 上传文件 (单文件大小不超过 n) * - target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云) */ @Override public SysFile uploadSmall(MultipartFile multipartFile, Long category_id) { List sysCommonList = sysCommonService.getCommonByCategory("UPLOAD"); AtomicReference UPLOAD_TARGET = new AtomicReference<>(); AtomicReference UPLOAD_MAX_SIZE_MB = new AtomicReference<>(); AtomicReference UPLOAD_MD5_DUPLICATE = new AtomicReference<>(); sysCommonList.stream().forEach(sysCommon -> { if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue())); if (sysCommon.getTag().equals("UPLOAD_MAX_SIZE_MB")) UPLOAD_MAX_SIZE_MB.set(Convert.toLong(sysCommon.getValue())); if (sysCommon.getTag().equals("UPLOAD_MD5_DUPLICATE")) UPLOAD_MD5_DUPLICATE.set(Convert.toBool(sysCommon.getValue())); }); System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET); System.out.println("[系统配置] 单文件上传大小限制(MB): " + UPLOAD_MAX_SIZE_MB); System.out.println("[系统配置] 是否启用文件MD5查重: " + UPLOAD_MD5_DUPLICATE); // 判断文件是否超过大小 Long MAX_SIZE = UPLOAD_MAX_SIZE_MB.get() * 1024 * 1024; if (multipartFile.getSize() > MAX_SIZE) { throw new CustException("上传文件不能大于 " + MAX_SIZE / 1024 / 1024 + " MB,请使用大文件上传功能"); } try { // [系统配置] 是否启用文件MD5查重 if (UPLOAD_MD5_DUPLICATE.get()) { // - 是 // 根据文件MD5 判断是否上传过文件 // - 如果上传过,则仅更新记录,不再上传文件,仅更新文件分类 // - 如果存在 String md5 = DigestUtil.md5Hex(multipartFile.getInputStream()); List sysFileEntityList = sysFileDao.selectList(new LambdaQueryWrapper().eq(SysFile::getMd5, md5)); if (sysFileEntityList != null && sysFileEntityList.size() > 1) { throw new CustException("存在 " + sysFileEntityList.size() + " 个相同Md5 (" + md5 + ") 的文件,请先排重后再上传"); } SysFile sysFileEntity = (sysFileEntityList != null && sysFileEntityList.size() > 0) ? sysFileEntityList.get(0) : null; if (sysFileEntity == null) { // [方法] 上传事件 sysFileEntity = uploadEvent(multipartFile, category_id, UPLOAD_TARGET.get()); } else { // [更新] 上传文件记录 (更换文件分类) sysFileEntity.setCategory_id(category_id); sysFileEntity.setUpdate_time(DateUtil.now()); sysFileDao.updateById(sysFileEntity); } return sysFileEntity; } else { // - 否 // [方法] 上传事件 SysFile sysFileEntity = uploadEvent(multipartFile, category_id, UPLOAD_TARGET.get()); return sysFileEntity; } } catch (IOException e) { throw new RuntimeException(e); } } /** * 删除上传文件 (包括缩略图,如果有的话) */ @Override public Map removeUploadFile(SysFile sysFile) { String object_key = sysFile.getObject_key(); // [Delete] 删除文件记录 SysFile entity = sysFileDao.selectOne(new LambdaQueryWrapper().eq(SysFile::getObject_key, object_key)); if (entity != null) { sysFileDao.delete(new LambdaQueryWrapper().eq(SysFile::getObject_key, object_key)); } // [异步任务] 创建一个 CompletableFuture 来执行异步任务 CompletableFuture.runAsync(() -> { // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云) Integer target = entity.getTarget(); if (target == 1) { // [腾讯云] 删除对象 tencentCosService.deleteObject(object_key); System.out.println("Delete tencent cos object: " + object_key); } if (target == 3) { // [抖音云] 删除对象 try { douyinTosService.deleteObject(object_key); } catch (IOException e) { throw new RuntimeException(e); } System.out.println("Delete douyin tos object: " + object_key); } }); return Map.of("object_key", object_key); } }