|
|
@@ -1,11 +1,350 @@
|
|
|
package com.usky.vpp.service.impl;
|
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.ruoyi.file.domain.FilesUpload;
|
|
|
+import com.ruoyi.file.mapper.FilesMapper;
|
|
|
+import com.usky.common.core.bean.CommonPage;
|
|
|
+import com.usky.common.security.utils.SecurityUtils;
|
|
|
+import com.usky.vpp.domain.VppFileArchive;
|
|
|
+import com.usky.vpp.mapper.VppFileArchiveMapper;
|
|
|
import com.usky.vpp.service.VppArchiveService;
|
|
|
+import com.usky.vpp.service.vo.VppFileArchiveRequestVO;
|
|
|
+import com.usky.vpp.service.vo.VppFileArchiveResponseVO;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import javax.servlet.ServletOutputStream;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.zip.ZipEntry;
|
|
|
+import java.util.zip.ZipOutputStream;
|
|
|
|
|
|
/**
|
|
|
* VppArchiveService 默认实现(待按业务完善)
|
|
|
*/
|
|
|
@Service
|
|
|
public class VppArchiveServiceImpl implements VppArchiveService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FilesMapper filesMapper;
|
|
|
+ @Autowired
|
|
|
+ private VppFileArchiveMapper vppFileArchiveMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean create(VppFileArchiveRequestVO vo) {
|
|
|
+ String username = SecurityUtils.getUsername();
|
|
|
+ Integer tenantId = SecurityUtils.getTenantId();
|
|
|
+
|
|
|
+ LambdaQueryWrapper<FilesUpload> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(FilesUpload::getFilesName, vo.getName())
|
|
|
+ .eq(FilesUpload::getUrl, vo.getUrl())
|
|
|
+ //.eq(FilesUpload::getEnable, "1")
|
|
|
+ .eq(FilesUpload::getIsDelete, 0);
|
|
|
+ FilesUpload filesUpload = filesMapper.selectOne(queryWrapper);
|
|
|
+
|
|
|
+ VppFileArchive vppFileArchive = new VppFileArchive();
|
|
|
+ vppFileArchive.setArchiveName(vo.getArchiveName());
|
|
|
+ vppFileArchive.setArchiveType(vo.getArchiveType());
|
|
|
+ vppFileArchive.setFileName(filesUpload.getFilesName());
|
|
|
+ vppFileArchive.setFileUrl(filesUpload.getUrl());
|
|
|
+ vppFileArchive.setFileType(filesUpload.getType());
|
|
|
+ vppFileArchive.setFileSize(filesUpload.getSize());
|
|
|
+ vppFileArchive.setBizId(vo.getBizId() != null ? vo.getBizId() : 0L);
|
|
|
+ vppFileArchive.setBizType(StringUtils.isNotBlank(vo.getBizType()) ? vo.getBizType() : "");
|
|
|
+ vppFileArchive.setSiteId(vo.getSiteId());
|
|
|
+ vppFileArchive.setCreateTime(LocalDateTime.now());
|
|
|
+ vppFileArchive.setCreatedBy(username);
|
|
|
+ vppFileArchive.setVersion("V1.0");
|
|
|
+ vppFileArchive.setTenantId(tenantId);
|
|
|
+ vppFileArchive.setRemark(StringUtils.isNotBlank(vo.getRemark()) ? vo.getRemark() : "");
|
|
|
+ vppFileArchive.setDeleteFlag(0);
|
|
|
+ int insert = vppFileArchiveMapper.insert(vppFileArchive);
|
|
|
+
|
|
|
+ return insert > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommonPage<VppFileArchiveResponseVO> page(String archiveName, Integer archiveType, Long siteId, String bizType, Long bizId, Integer pageNum, Integer pageSize) {
|
|
|
+ IPage<VppFileArchive> page = new Page<>(pageNum, pageSize);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppFileArchive::getDeleteFlag, 0)
|
|
|
+ .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId())
|
|
|
+ .eq(siteId != null, VppFileArchive::getSiteId, siteId)
|
|
|
+ .eq(archiveType != null, VppFileArchive::getArchiveType, archiveType)
|
|
|
+ .eq(StringUtils.isNotBlank(bizType), VppFileArchive::getBizType, bizType)
|
|
|
+ .eq(bizId != null, VppFileArchive::getBizId, bizId)
|
|
|
+ .like(StringUtils.isNotBlank(archiveName), VppFileArchive::getArchiveName, archiveName)
|
|
|
+ .orderByDesc(VppFileArchive::getCreateTime);
|
|
|
+ page = vppFileArchiveMapper.selectPage(page, queryWrapper);
|
|
|
+
|
|
|
+ List<VppFileArchiveResponseVO> list = page.getRecords().stream().map(entity -> {
|
|
|
+ VppFileArchiveResponseVO responseVO = new VppFileArchiveResponseVO();
|
|
|
+ responseVO.setId(entity.getId());
|
|
|
+ responseVO.setArchiveName(entity.getArchiveName());
|
|
|
+ responseVO.setFileName(entity.getFileName());
|
|
|
+ responseVO.setFileUrl(entity.getFileUrl());
|
|
|
+ responseVO.setArchiveType(entity.getArchiveType());
|
|
|
+ responseVO.setFileType(entity.getFileType());
|
|
|
+ responseVO.setFileSize(convertFileSize(entity.getFileSize()));
|
|
|
+ responseVO.setBizType(entity.getBizType());
|
|
|
+ responseVO.setBizId(entity.getBizId());
|
|
|
+ responseVO.setSiteId(entity.getSiteId());
|
|
|
+ responseVO.setRemark(entity.getRemark());
|
|
|
+ responseVO.setCreatedBy(entity.getCreatedBy());
|
|
|
+ responseVO.setCreateTime(entity.getCreateTime());
|
|
|
+ responseVO.setUpdateTime(entity.getUpdateTime());
|
|
|
+ responseVO.setUpdatedBy(entity.getUpdatedBy());
|
|
|
+ responseVO.setVersion(entity.getVersion());
|
|
|
+ responseVO.setTenantId(entity.getTenantId());
|
|
|
+ // TODO 查询站点名称
|
|
|
+ // responseVO.setSiteName();
|
|
|
+ return responseVO;
|
|
|
+ }).collect(java.util.stream.Collectors.toList());
|
|
|
+
|
|
|
+ return new CommonPage<>(list, page.getTotal(), pageSize, pageNum);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean upload(VppFileArchiveRequestVO vo) {
|
|
|
+ if (vo.getId() == null) {
|
|
|
+ throw new RuntimeException("主键ID不能为空!");
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppFileArchive::getId, vo.getId())
|
|
|
+ .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId())
|
|
|
+ .eq(VppFileArchive::getDeleteFlag, 0);
|
|
|
+ VppFileArchive existingRecord = vppFileArchiveMapper.selectOne(queryWrapper);
|
|
|
+
|
|
|
+ if (existingRecord == null) {
|
|
|
+ throw new RuntimeException("档案记录不存在或已被删除!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据 name + url 关联 FilesUpload 表,同步更新文件信息
|
|
|
+ if (StringUtils.isNotBlank(vo.getName()) && StringUtils.isNotBlank(vo.getUrl())) {
|
|
|
+ LambdaQueryWrapper<FilesUpload> fileQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ fileQueryWrapper.eq(FilesUpload::getFilesName, vo.getName())
|
|
|
+ .eq(FilesUpload::getUrl, vo.getUrl())
|
|
|
+ .eq(FilesUpload::getIsDelete, 0);
|
|
|
+ FilesUpload filesUpload = filesMapper.selectOne(fileQueryWrapper);
|
|
|
+
|
|
|
+ if (filesUpload != null) {
|
|
|
+ existingRecord.setFileName(filesUpload.getFilesName());
|
|
|
+ existingRecord.setFileUrl(filesUpload.getUrl());
|
|
|
+ existingRecord.setFileType(filesUpload.getType());
|
|
|
+ existingRecord.setFileSize(filesUpload.getSize());
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("未找到匹配的文件记录,请确认文件名与路径是否正确!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仅更新非空字段
|
|
|
+ if (StringUtils.isNotBlank(vo.getArchiveName())) {
|
|
|
+ existingRecord.setArchiveName(vo.getArchiveName());
|
|
|
+ }
|
|
|
+ if (vo.getArchiveType() != null) {
|
|
|
+ existingRecord.setArchiveType(vo.getArchiveType());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(vo.getRemark())) {
|
|
|
+ existingRecord.setRemark(vo.getRemark());
|
|
|
+ }
|
|
|
+ if (vo.getSiteId() != null) {
|
|
|
+ existingRecord.setSiteId(vo.getSiteId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 版本号自增 +0.1
|
|
|
+ String currentVersion = existingRecord.getVersion();
|
|
|
+ if (StringUtils.isNotBlank(currentVersion) && currentVersion.startsWith("V")) {
|
|
|
+ try {
|
|
|
+ double versionNum = Double.parseDouble(currentVersion.substring(1));
|
|
|
+ versionNum += 0.1;
|
|
|
+ existingRecord.setVersion("V" + String.format("%.1f", versionNum));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ existingRecord.setVersion(currentVersion + ".1");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ existingRecord.setUpdateTime(LocalDateTime.now());
|
|
|
+ existingRecord.setUpdatedBy(SecurityUtils.getUsername());
|
|
|
+
|
|
|
+ int update = vppFileArchiveMapper.updateById(existingRecord);
|
|
|
+ return update > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Boolean delete(Long id) {
|
|
|
+ LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(VppFileArchive::getId, id)
|
|
|
+ .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId());
|
|
|
+ VppFileArchive vppFileArchive = vppFileArchiveMapper.selectOne(queryWrapper);
|
|
|
+ vppFileArchive.setDeleteFlag(1);
|
|
|
+ vppFileArchive.setDeletedAt(LocalDateTime.now());
|
|
|
+ vppFileArchive.setUpdatedBy(SecurityUtils.getUsername());
|
|
|
+ int i = vppFileArchiveMapper.updateById(vppFileArchive);
|
|
|
+ return i > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void export(List<Long> ids, HttpServletResponse response) {
|
|
|
+ // ========== 参数校验 ==========
|
|
|
+ if (ids == null || ids.isEmpty()) {
|
|
|
+ writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "请选择要导出的档案!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 查询档案记录 ==========
|
|
|
+ LambdaQueryWrapper<VppFileArchive> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.in(VppFileArchive::getId, ids)
|
|
|
+ .eq(VppFileArchive::getDeleteFlag, 0)
|
|
|
+ .eq(VppFileArchive::getTenantId, SecurityUtils.getTenantId());
|
|
|
+ List<VppFileArchive> archives = vppFileArchiveMapper.selectList(queryWrapper);
|
|
|
+
|
|
|
+ if (archives == null || archives.isEmpty()) {
|
|
|
+ writeErrorResponse(response, HttpStatus.BAD_REQUEST.value(), "没有找到有效的档案记录!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 构造 ZIP 文件名 ==========
|
|
|
+ String zipFileName = "电子档案_"
|
|
|
+ + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
|
|
|
+ + ".zip";
|
|
|
+
|
|
|
+ // ========== 设置响应头(必须在写入流之前)==========
|
|
|
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
|
|
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
|
|
|
+ "attachment; filename=" + encodeFileName(zipFileName));
|
|
|
+
|
|
|
+ // ========== 生成 ZIP 并写入响应流 ==========
|
|
|
+ RestTemplate restTemplate = new RestTemplate();
|
|
|
+ Set<String> usedNames = new HashSet<>();
|
|
|
+
|
|
|
+ try (ServletOutputStream sos = response.getOutputStream();
|
|
|
+ ZipOutputStream zos = new ZipOutputStream(sos)) {
|
|
|
+
|
|
|
+ for (VppFileArchive archive : archives) {
|
|
|
+ String fileUrl = archive.getFileUrl();
|
|
|
+ String fileName = archive.getFileName();
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(fileUrl) || StringUtils.isBlank(fileName)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String zipEntryName = resolveDuplicateName(fileName, usedNames);
|
|
|
+ usedNames.add(zipEntryName);
|
|
|
+
|
|
|
+ try {
|
|
|
+ byte[] fileBytes = restTemplate.getForObject(fileUrl, byte[].class);
|
|
|
+ if (fileBytes != null && fileBytes.length > 0) {
|
|
|
+ ZipEntry zipEntry = new ZipEntry(zipEntryName);
|
|
|
+ zos.putNextEntry(zipEntry);
|
|
|
+ zos.write(fileBytes);
|
|
|
+ zos.closeEntry();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ ZipEntry errorEntry = new ZipEntry(zipEntryName + ".error.txt");
|
|
|
+ zos.putNextEntry(errorEntry);
|
|
|
+ zos.write(("下载失败: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
|
|
|
+ zos.closeEntry();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ zos.finish();
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 如果响应头还没提交,可以返回错误信息
|
|
|
+ if (!response.isCommitted()) {
|
|
|
+ writeErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
|
|
+ "导出电子档案失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 ZIP 内重名文件,自动添加编号后缀
|
|
|
+ * @param fileName 原始文件名
|
|
|
+ * @param usedNames 已使用的文件名集合
|
|
|
+ * @return 去重后的文件名
|
|
|
+ */
|
|
|
+ private String resolveDuplicateName(String fileName, Set<String> usedNames) {
|
|
|
+ if (!usedNames.contains(fileName)) {
|
|
|
+ return fileName;
|
|
|
+ }
|
|
|
+ String baseName = fileName;
|
|
|
+ String extension = "";
|
|
|
+ int dotIndex = fileName.lastIndexOf('.');
|
|
|
+ if (dotIndex > 0) {
|
|
|
+ baseName = fileName.substring(0, dotIndex);
|
|
|
+ extension = fileName.substring(dotIndex);
|
|
|
+ }
|
|
|
+ int counter = 1;
|
|
|
+ String newName;
|
|
|
+ do {
|
|
|
+ newName = baseName + "(" + counter + ")" + extension;
|
|
|
+ counter++;
|
|
|
+ } while (usedNames.contains(newName));
|
|
|
+ return newName;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 向响应中写入 JSON 错误信息
|
|
|
+ */
|
|
|
+ private void writeErrorResponse(HttpServletResponse response, int status, String message) {
|
|
|
+ response.setStatus(status);
|
|
|
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
|
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
|
|
+ try {
|
|
|
+ response.getWriter().write("{\"code\":\"" + status + "\",\"msg\":\"" + message + "\"}");
|
|
|
+ } catch (IOException e) {
|
|
|
+ // 忽略写入异常
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 编码文件名,兼容各浏览器
|
|
|
+ */
|
|
|
+ private String encodeFileName(String fileName) {
|
|
|
+ try {
|
|
|
+ return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
+ return fileName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文件大小格式转换工具方法
|
|
|
+ * 按1024进制自动递进换算单位(KB → MB → GB → TB)
|
|
|
+ * @param size 数值型文件大小
|
|
|
+ * @return 带单位后缀的格式化字符串
|
|
|
+ */
|
|
|
+ private String convertFileSize(Double size) {
|
|
|
+ if (size == null || size <= 0) {
|
|
|
+ return "0 B";
|
|
|
+ }
|
|
|
+ final String[] units = {"B", "KB", "MB", "GB", "TB"};
|
|
|
+ int unitIndex = 0;
|
|
|
+ double result = size;
|
|
|
+ while (result >= 1024 && unitIndex < units.length - 1) {
|
|
|
+ result /= 1024;
|
|
|
+ unitIndex++;
|
|
|
+ }
|
|
|
+ return String.format("%.2f %s", result, units[unitIndex]);
|
|
|
+ }
|
|
|
}
|