feat: 支持下载tar格式
This commit is contained in:
@@ -19,6 +19,7 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode LICENSE_NOT_EXISTS = new ErrorCode(1_100_003_001, "License不存在");
|
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_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_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_NOT_EXISTS = new ErrorCode(1_100_004_001, "评论不存在");
|
||||||
ErrorCode COMMENT_EXITS_CHILDREN = new ErrorCode(1_100_004_002, "存在子评论,无法删除");
|
ErrorCode COMMENT_EXITS_CHILDREN = new ErrorCode(1_100_004_002, "存在子评论,无法删除");
|
||||||
|
|||||||
@@ -121,6 +121,11 @@
|
|||||||
<artifactId>agt-spring-boot-starter-monitor</artifactId>
|
<artifactId>agt-spring-boot-starter-monitor</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tika</groupId>
|
||||||
|
<artifactId>tika-core</artifactId> <!-- 文件客户端:文件类型的识别 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ public class LicenseController {
|
|||||||
})
|
})
|
||||||
@PreAuthorize("@ss.hasPermission('license:license:import')")
|
@PreAuthorize("@ss.hasPermission('license:license:import')")
|
||||||
public CommonResult<ImportRespVO> importCodeExcel(@RequestParam("file") MultipartFile file,
|
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);
|
List<LicenseCodeImportExcelVO> list = ExcelUtils.read(file, LicenseCodeImportExcelVO.class);
|
||||||
return success(licenseService.importCodeList(list, updateSupport));
|
return success(licenseService.importCodeList(list, updateSupport));
|
||||||
}
|
}
|
||||||
@@ -265,4 +265,12 @@ public class LicenseController {
|
|||||||
licenseService.updateDetailById(updateReqVO);
|
licenseService.updateDetailById(updateReqVO);
|
||||||
return success(true);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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,对于 doc,jar 等文件会有误差
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.agt.module.license.service.license;
|
package org.agt.module.license.service.license;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.agt.framework.common.pojo.PageResult;
|
import org.agt.framework.common.pojo.PageResult;
|
||||||
import org.agt.module.license.controller.admin.license.vo.ImportRespVO;
|
import org.agt.module.license.controller.admin.license.vo.ImportRespVO;
|
||||||
@@ -99,4 +100,6 @@ public interface LicenseService {
|
|||||||
ImportRespVO importCodeList(List<LicenseCodeImportExcelVO> list, Boolean updateSupport);
|
ImportRespVO importCodeList(List<LicenseCodeImportExcelVO> list, Boolean updateSupport);
|
||||||
|
|
||||||
void updateDetailById(LicenseDetailVO licenseDetail);
|
void updateDetailById(LicenseDetailVO licenseDetail);
|
||||||
|
|
||||||
|
void download(HttpServletResponse response, Long id);
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,18 @@ package org.agt.module.license.service.license;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.io.resource.ResourceUtil;
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.core.util.ZipUtil;
|
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.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.agt.framework.common.pojo.CommonResult;
|
||||||
import org.agt.framework.common.pojo.PageResult;
|
import org.agt.framework.common.pojo.PageResult;
|
||||||
import org.agt.framework.common.util.object.BeanUtils;
|
import org.agt.framework.common.util.object.BeanUtils;
|
||||||
import org.agt.framework.dict.core.DictFrameworkUtils;
|
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.license.LicenseProviderMapper;
|
||||||
import org.agt.module.license.dal.mysql.project.ProjectMapper;
|
import org.agt.module.license.dal.mysql.project.ProjectMapper;
|
||||||
import org.agt.module.license.enums.LicenseStatusEnum;
|
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.MailSendApi;
|
||||||
import org.agt.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
|
import org.agt.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
|
||||||
import org.agt.module.system.api.notify.NotifyMessageSendApi;
|
import org.agt.module.system.api.notify.NotifyMessageSendApi;
|
||||||
import org.agt.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
import org.agt.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
||||||
|
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
@@ -61,6 +70,7 @@ import java.util.Optional;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.agt.framework.common.exception.util.ServiceExceptionUtil.exception;
|
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_IMPORT_LIST_IS_EMPTY;
|
||||||
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_NOT_EXISTS;
|
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_NOT_EXISTS;
|
||||||
import static org.agt.module.license.enums.ErrorCodeConstants.LICENSE_SN_DUPLICATE;
|
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("D:/temp/temp.zip");
|
||||||
File tempFile = new File("/usr/local/licGen/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()]));
|
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");
|
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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user