feat: 支持下载tar格式

This commit is contained in:
caiyuchao
2025-09-02 18:17:29 +08:00
parent 9ef2f736c3
commit 9c25c374c4
6 changed files with 157 additions and 1 deletions

View File

@@ -19,6 +19,7 @@ public interface ErrorCodeConstants {
ErrorCode LICENSE_NOT_EXISTS = new ErrorCode(1_100_003_001, "License不存在");
ErrorCode LICENSE_SN_DUPLICATE = new ErrorCode(1_100_003_002, "License SN`{}`已存在");
ErrorCode LICENSE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_100_003_003, "导入License数据不能为空");
ErrorCode LICENSE_DOWNLOAD_FAILED = new ErrorCode(1_100_003_004, "下载失败!");
ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1_100_004_001, "评论不存在");
ErrorCode COMMENT_EXITS_CHILDREN = new ErrorCode(1_100_004_002, "存在子评论,无法删除");

View File

@@ -121,6 +121,11 @@
<artifactId>agt-spring-boot-starter-monitor</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId> <!-- 文件客户端:文件类型的识别 -->
</dependency>
</dependencies>
<build>

View File

@@ -253,7 +253,7 @@ public class LicenseController {
})
@PreAuthorize("@ss.hasPermission('license:license:import')")
public CommonResult<ImportRespVO> importCodeExcel(@RequestParam("file") MultipartFile file,
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
List<LicenseCodeImportExcelVO> list = ExcelUtils.read(file, LicenseCodeImportExcelVO.class);
return success(licenseService.importCodeList(list, updateSupport));
}
@@ -265,4 +265,12 @@ public class LicenseController {
licenseService.updateDetailById(updateReqVO);
return success(true);
}
@Operation(summary = "下载")
@GetMapping("/download")
@Parameter(name = "tableId", description = "id", required = true, example = "1024")
public void download(@RequestParam("id") Long id,
HttpServletResponse response) {
licenseService.download(response, id);
}
}

View File

@@ -0,0 +1,97 @@
package org.agt.module.license.framework.util;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.agt.framework.common.util.http.HttpUtils;
import org.apache.tika.Tika;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import java.io.IOException;
/**
* 文件类型 Utils
*
* @author 千通源码
*/
@Slf4j
public class FileTypeUtils {
private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);
/**
* 获得文件的 mineType对于 docjar 等文件会有误差
*
* @param data 文件内容
* @return mineType 无法识别时会返回“application/octet-stream”
*/
@SneakyThrows
public static String getMineType(byte[] data) {
return TIKA.get().detect(data);
}
/**
* 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用 jar 文件时,通过名字更为准确
*
* @param name 文件名
* @return mineType 无法识别时会返回“application/octet-stream”
*/
public static String getMineType(String name) {
return TIKA.get().detect(name);
}
/**
* 在拥有文件和数据的情况下,最好使用此方法,最为准确
*
* @param data 文件内容
* @param name 文件名
* @return mineType 无法识别时会返回“application/octet-stream”
*/
public static String getMineType(byte[] data, String name) {
return TIKA.get().detect(data, name);
}
/**
* 根据 mineType 获得文件后缀
*
* 注意:如果获取不到,或者发生异常,都返回 null
*
* @param mineType 类型
* @return 后缀,例如说 .pdf
*/
public static String getExtension(String mineType) {
try {
return MimeTypes.getDefaultMimeTypes().forName(mineType).getExtension();
} catch (MimeTypeException e) {
log.warn("[getExtension][获取文件后缀({}) 失败]", mineType, e);
return null;
}
}
/**
* 返回附件
*
* @param response 响应
* @param filename 文件名
* @param content 附件内容
*/
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
// 设置 header 和 contentType
response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
String contentType = getMineType(content, filename);
response.setContentType(contentType);
// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
if (StrUtil.containsIgnoreCase(contentType, "video")) {
response.setHeader("Content-Length", String.valueOf(content.length - 1));
response.setHeader("Content-Range", String.valueOf(content.length - 1));
response.setHeader("Accept-Ranges", "bytes");
}
// 输出附件
IoUtil.write(response.getOutputStream(), false, content);
}
}

View File

@@ -1,5 +1,6 @@
package org.agt.module.license.service.license;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.agt.framework.common.pojo.PageResult;
import org.agt.module.license.controller.admin.license.vo.ImportRespVO;
@@ -99,4 +100,6 @@ public interface LicenseService {
ImportRespVO importCodeList(List<LicenseCodeImportExcelVO> list, Boolean updateSupport);
void updateDetailById(LicenseDetailVO licenseDetail);
void download(HttpServletResponse response, Long id);
}

View File

@@ -3,13 +3,18 @@ package org.agt.module.license.service.license;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.extra.compress.CompressUtil;
import cn.hutool.extra.compress.archiver.Archiver;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.agt.framework.common.pojo.CommonResult;
import org.agt.framework.common.pojo.PageResult;
import org.agt.framework.common.util.object.BeanUtils;
import org.agt.framework.dict.core.DictFrameworkUtils;
@@ -37,17 +42,21 @@ import org.agt.module.license.dal.mysql.license.LicenseMapper;
import org.agt.module.license.dal.mysql.license.LicenseProviderMapper;
import org.agt.module.license.dal.mysql.project.ProjectMapper;
import org.agt.module.license.enums.LicenseStatusEnum;
import org.agt.module.license.framework.util.FileTypeUtils;
import org.agt.module.system.api.mail.MailSendApi;
import org.agt.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
import org.agt.module.system.api.notify.NotifyMessageSendApi;
import org.agt.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@@ -61,6 +70,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
import static org.agt.framework.common.exception.util.ServiceExceptionUtil.exception;
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_DOWNLOAD_FAILED;
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_IMPORT_LIST_IS_EMPTY;
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_NOT_EXISTS;
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_SN_DUPLICATE;
@@ -408,6 +418,7 @@ public class LicenseServiceImpl implements LicenseService {
// File tempFile = new File("D:/temp/temp.zip");
File tempFile = new File("/usr/local/licGen/temp.zip");
ZipUtil.zip(tempFile, pathList.toArray(new String[0]), inputStreamList.toArray(new ByteArrayInputStream[inputStreamList.size()]));
String fileURL = fileApi.createFile(FileUtil.readBytes(tempFile), licenseDO.getSerialNo() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + ".zip");
@@ -988,4 +999,35 @@ public class LicenseServiceImpl implements LicenseService {
}
licenseDetailMapper.updateById(licenseDetailDO);
}
public void download(HttpServletResponse response, Long id) {
try {
List<LicenseDetailDO> detailList = licenseDetailMapper.selectList(Wrappers.<LicenseDetailDO>lambdaQuery()
.eq(LicenseDetailDO::getLicenseId, id));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.TAR, outputStream);
for (LicenseDetailDO detail : detailList) {
addArchiverFile(archiver, detail.getFileUrl());
addArchiverFile(archiver, detail.getFileUrlLegacy());
}
archiver.finish().close();
FileTypeUtils.writeAttachment(response, "temp.tar", outputStream.toByteArray());
} catch (IOException e) {
throw exception(LICENSE_DOWNLOAD_FAILED);
}
}
private void addArchiverFile(Archiver archiver, String fileUrl) {
String tempFilePath = "/usr/local/licGen/";
if (StrUtil.isNotBlank(fileUrl)) {
String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1, fileUrl.lastIndexOf("_")) + ".ini";
CommonResult<byte[]> result = fileApi.getFileContent(fileUrl);
byte[] fileContent = result.getData();
archiver.add(FileUtil.writeBytes(fileContent, tempFilePath + fileName));
FileUtil.del(tempFilePath + fileName);
}
}
}