问题修改

This commit is contained in:
jiang 2025-12-11 09:09:40 +08:00
parent ad27e10673
commit 84a03fcf49
8 changed files with 663 additions and 77 deletions

View File

@ -20,6 +20,7 @@ import com.bonus.material.device.domain.vo.DevInfoVo;
import com.bonus.material.device.domain.vo.DevMergeVo;
import com.bonus.material.device.service.DevInfoService;
import com.bonus.material.device.service.DevMergeService;
import com.bonus.material.utils.FolderZipUtil;
import com.bonus.material.utils.ReflectUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -290,6 +291,30 @@ public class DevMergeController extends BaseController {
}
/**
* 下载指定空文件夹结构的 ZIP
* 目标结构
* business/
* report/
* data/
* temp/
* 访问路径http://localhost:8080/download/zip
*/
@PostMapping("/zip")
public void downloadEmptyFolderZip(HttpServletResponse response, String orderId) {
service.downloadEmptyFolderZip(response, orderId);
}
/**
* 上传包含多个一级目录的ZIP包
* 无需传设备ID自动识别ZIP内的Apply-xxx目录
*/
@PostMapping("/upload-multi")
public AjaxResult uploadMultiDeviceZip(@RequestParam MultipartFile file, String orderId) {
return service.uploadAndUnzipMultiDeviceZip(file, orderId);
}
/**
* 下载导入模板
*/

View File

@ -26,7 +26,7 @@ public class EquipmentImportDTO {
@Excel(name = "规格型号", sort = 3, align = HorizontalAlignment.CENTER)
private String specification;
@Excel(name = "资产原值", sort = 4, align = HorizontalAlignment.CENTER)
@Excel(name = "资产原值(万元)", sort = 4, align = HorizontalAlignment.CENTER)
@DecimalMin(value = "0.00", message = "资产原值不能小于0")
private BigDecimal originalValue;

View File

@ -91,10 +91,17 @@ public interface DevMergeMapper {
List<String> listAllManufacturerNames();
// 批量查询profession对应的typeId
// 批量查询profession对应的typeId
Integer getTypeId(String professions);
Integer getManufacturer(String manufacturers);
List<String> getFileName(String orderId);
@MapKey("fileName")
Map<String, Object> getFileId(String orderId);
Integer selNum(DevMergeVo o);
}

View File

@ -77,4 +77,8 @@ public interface DevMergeService {
List<String> listAllProfessionNames();
List<String> listAllManufacturerNames();
void downloadEmptyFolderZip(HttpServletResponse response,String orderId);
AjaxResult uploadAndUnzipMultiDeviceZip(MultipartFile zipFile,String orderId);
}

View File

@ -1,51 +1,31 @@
package com.bonus.material.device.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import com.bonus.common.biz.constant.MaterialConstants;
import com.bonus.common.biz.domain.*;
import com.bonus.common.biz.enums.HttpCodeEnum;
import com.bonus.common.biz.enums.MaStatusEnum;
import com.bonus.common.core.exception.ServiceException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bonus.common.core.utils.DateUtils;
import com.bonus.common.core.utils.StringUtils;
import com.bonus.common.core.utils.bean.BeanUtils;
import com.bonus.common.core.utils.bean.BeanValidators;
import com.bonus.common.core.utils.poi.ExcelUtil;
import com.bonus.common.core.web.domain.AjaxResult;
import com.bonus.common.security.utils.SecurityUtils;
import com.bonus.material.basic.domain.BmSlideShow;
import com.bonus.material.book.domain.BookCarInfoDto;
import com.bonus.material.devchange.domain.MaDevFile;
import com.bonus.material.devchange.domain.MaDevInfo;
import com.bonus.material.devchange.domain.MaDevInfoXlsx;
import com.bonus.material.devchange.domain.MapBean;
import com.bonus.material.devchange.mapper.MaDevInfoMapper;
import com.bonus.material.device.domain.DevInfo;
import com.bonus.material.device.domain.EquipmentImportDTO;
import com.bonus.material.device.domain.MaDevQc;
import com.bonus.material.device.domain.Table;
import com.bonus.material.device.domain.dto.DevInfoImpDto;
import com.bonus.material.device.domain.dto.InfoMotionDto;
import com.bonus.material.device.domain.vo.*;
import com.bonus.material.device.mapper.BmFileInfoMapper;
import com.bonus.material.device.domain.vo.DevInfoPropertyVo;
import com.bonus.material.device.domain.vo.DevInfoVo;
import com.bonus.material.device.domain.vo.DevMergeVo;
import com.bonus.material.device.mapper.DevInfoMapper;
import com.bonus.material.device.mapper.DevMergeMapper;
import com.bonus.material.device.mapper.MaDevQcMapper;
import com.bonus.material.device.service.DevInfoService;
import com.bonus.material.device.service.DevMergeService;
import com.bonus.material.device.service.MaDevQcService;
import com.bonus.system.api.model.LoginUser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.bonus.material.utils.FolderZipUtil;
import com.bonus.system.api.RemoteFileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@ -55,23 +35,19 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import static com.bonus.common.biz.constant.MaterialConstants.ADMIN_ID;
import static com.bonus.common.biz.constant.MaterialConstants.PROVINCE_COMPANY_DEPT_ID;
import static com.bonus.common.biz.enums.MaStatusEnum.*;
/**
* 设备信息Service业务层处理
@ -82,6 +58,8 @@ import static com.bonus.common.biz.enums.MaStatusEnum.*;
@Slf4j
public class DevMergeServiceImpl implements DevMergeService {
@Resource
private RemoteFileService remoteFileService;
@Resource
private DevMergeMapper devMergeMapper;
@Resource
@ -127,6 +105,13 @@ public class DevMergeServiceImpl implements DevMergeService {
@Override
public AjaxResult submitOrder(DevMergeVo o) {
Integer num = devMergeMapper.selNum(o);
if (num == 0) {
return AjaxResult.warn("请先添加装备");
}
Integer i = devMergeMapper.submitOrder(o);
if (i > 0) {
devMergeMapper.updateDeviceStatus(o);
@ -548,6 +533,37 @@ public class DevMergeServiceImpl implements DevMergeService {
return devMergeMapper.listAllManufacturerNames();
}
/**
* @param response
*/
@Override
public void downloadEmptyFolderZip(HttpServletResponse response, String orderId) {
// 1. 从数据库查询一级文件夹名称列表
List<String> firstLevelFolders = devMergeMapper.getFileName(orderId);
if (firstLevelFolders == null || firstLevelFolders.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return; // 无文件夹时返回400
}
// 2. 定义一级文件夹下的固定子文件夹名称
List<String> subFolders = Arrays.asList(
"装备外观/",
"合格证/",
"定期检验报告/",
"采购发票/"
);
// 3. 构建所有需要生成的文件夹路径
List<String> allFolderPaths = new ArrayList<>();
for (String firstFolder : firstLevelFolders) {
// 添加一级文件夹
allFolderPaths.add(firstFolder + "/");
// 添加一级文件夹下的所有子文件夹
for (String subFolder : subFolders) {
allFolderPaths.add(firstFolder + "/" + subFolder);
}
}
// 4. 生成 ZIP 包并下载文件名为订单+文件夹模板.zip
FolderZipUtil.buildEmptyFolderZip(response, orderId + "-文件夹模板.zip", allFolderPaths);
}
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");
private static final String SEPARATOR = "-";
@ -586,4 +602,443 @@ public class DevMergeServiceImpl implements DevMergeService {
}
return String.format("%0" + SEQUENCE_LENGTH + "d", sequence);
}
// 固定子目录
private static final Set<String> REQUIRED_SUB_FOLDERS = new HashSet<>(Arrays.asList(
"装备外观", "合格证", "定期检验报告", "采购发票"
));
// 允许的文件类型
private static final Set<String> IMAGE_TYPES = new HashSet<>(Arrays.asList("jpg", "png"));
private static final Set<String> PDF_TYPE = new HashSet<>(Collections.singletonList("pdf"));
// 一级目录前缀
private static final String ROOT_FOLDER_PREFIX = "Apply-";
// 系统临时目录
private static final String SYSTEM_TEMP_DIR = System.getProperty("java.io.tmpdir");
/**
* 上传并解压多一级目录的ZIP包适配任意层级的Apply-目录
*/
@Override
public AjaxResult uploadAndUnzipMultiDeviceZip(MultipartFile zipFile, String orderId) {
Map<String, Object> map = devMergeMapper.getFileId(orderId);
if (ObjectUtil.isEmpty(map)) {
if (zipFile.isEmpty()) {
return AjaxResult.error(400, "无设备可修改");
}
}
Map<String, Object> result = new HashMap<>();
int totalRootFolder = 0;
int successFileCount = 0;
List<String> errorMsgList = new ArrayList<>();
Map<String, List<String>> rootFolderErrorMap = new HashMap<>();
// 1. 基础校验
if (zipFile.isEmpty()) {
return AjaxResult.error(400, "上传的ZIP包不能为空");
}
String zipFileName = zipFile.getOriginalFilename();
if (zipFileName == null || !zipFileName.toLowerCase().endsWith(".zip")) {
return AjaxResult.error(400, "仅支持上传ZIP格式文件");
}
// 使用更安全的临时目录名
String timestamp = String.valueOf(System.nanoTime());
String random = UUID.randomUUID().toString().substring(0, 8);
String tempSubDirName = "device_zip_" + timestamp + "_" + random;
File tempUnzipDir = new File(SYSTEM_TEMP_DIR, tempSubDirName);
try {
// 2. 创建临时目录
FileUtil.mkdir(tempUnzipDir);
// 3. 验证ZIP文件完整性并解压
boolean unzipSuccess = safeUnzip(zipFile, tempUnzipDir, errorMsgList);
if (!unzipSuccess) {
// 解压失败但可能部分文件解压成功可以选择继续处理或直接返回
if (errorMsgList.stream().anyMatch(msg -> msg.contains("损坏") || msg.contains("无效") || msg.contains("MALFORMED"))) {
return AjaxResult.error(400, "ZIP文件损坏或格式错误" + String.join("; ", errorMsgList));
}
}
// 4. 核心递归遍历所有层级找出所有Apply-开头的目录
Set<File> applyFolders = new HashSet<>();
findAllApplyFolders(tempUnzipDir, applyFolders);
// 5. 遍历所有Apply-目录并处理
for (File applyFolder : applyFolders) {
String applyFolderName = applyFolder.getName();
totalRootFolder++;
List<String> rootErrorList = new ArrayList<>();
try {
// 6. 校验子目录结构装备外观/合格证等
validateSubFolderStructure(applyFolder, rootErrorList);
// 7. 遍历子目录文件并处理
for (String subFolder : REQUIRED_SUB_FOLDERS) {
File subDir = new File(applyFolder, subFolder);
if (FileUtil.exist(subDir)) {
Collection<File> files = FileUtil.loopFiles(subDir);
for (File file : files) {
try {
validateFileType(subFolder, file, rootErrorList);
// 移动文件到业务存储目录
AjaxResult fileResult = remoteFileService.upload(convert(file, file.getName(), null));
JSONObject json = (JSONObject) JSON.toJSON(fileResult);
if (json.getInteger("code") == 200) {
JSONObject jsonObject = (JSONObject) JSON.toJSON(map);
if (jsonObject != null) {
MaDevFile item = new MaDevFile();
if ("装备外观".equals(subFolder)) {
item.setFileType(1);
} else if ("合格证".equals(subFolder)) {
item.setFileType(2);
} else if ("定期检验报告".equals(subFolder)) {
item.setFileType(3);
} else if ("采购发票".equals(subFolder)) {
item.setFileType(4);
}
item.setMaId(jsonObject.getJSONObject(applyFolderName).getInteger("id"));
JSONObject data = json.getJSONObject("data");
String url = data.getString("url");
item.setFileName(file.getName());
// 这里编写对每个 image 的处理逻辑比如打印处理等
item.setFileUrl(url);
item.setCreator(Math.toIntExact(SecurityUtils.getLoginUser().getUserid()));
devMergeMapper.interFile(item);
}
}
successFileCount++;
} catch (IllegalArgumentException e) {
rootErrorList.add(e.getMessage());
}
}
} else {
rootErrorList.add("缺失子目录:" + applyFolderName + "/" + subFolder);
}
}
if (!rootErrorList.isEmpty()) {
rootFolderErrorMap.put(applyFolderName, rootErrorList);
}
} catch (Exception e) {
rootErrorList.add("Apply目录处理失败" + e.getMessage());
rootFolderErrorMap.put(applyFolderName, rootErrorList);
}
}
// 8. 组装结果
if (totalRootFolder == 0) {
return AjaxResult.error(400, "未找到有效的Apply-开头的目录");
}
result.put("code", 200);
result.put("msg", "ZIP包解压完成识别到" + totalRootFolder + "个Apply-目录)");
result.put("totalRootFolder", totalRootFolder);
result.put("successFileCount", successFileCount);
result.put("rootFolderErrorMap", rootFolderErrorMap);
result.put("otherError", errorMsgList);
return AjaxResult.success(result);
} catch (IllegalArgumentException e) {
return AjaxResult.error(400, e.getMessage());
} catch (Exception e) {
log.error("ZIP解压/上传失败", e);
return AjaxResult.error(500, "ZIP解压/上传失败:" + e.getMessage());
} finally {
// 9. 清理临时目录
try {
if (tempUnzipDir.exists()) {
FileUtil.del(tempUnzipDir);
log.debug("清理临时目录: {}", tempUnzipDir.getAbsolutePath());
}
} catch (Exception e) {
log.warn("清理临时目录失败: {}", e.getMessage());
}
}
}
/**
* 从File对象中获取Apply-目录的纯名称核心方法
*
* @param applyFolder Apply-开头的目录File对象
* @return 文件夹名称如Apply-22099-13-123
*/
private String getApplyFolderName(File applyFolder) {
// 方式1直接获取文件名推荐最简洁
String folderName = applyFolder.getName();
// 可选额外校验确保是Apply-开头避免异常
if (!folderName.startsWith(ROOT_FOLDER_PREFIX)) {
throw new IllegalArgumentException("非合法的Apply-目录:" + folderName);
}
return folderName;
}
/**
* File 转换为 MultipartFile
*
* @param file 要转换的文件
* @param originalFilename 原始文件名可空默认为file.getName()
* @param contentType 内容类型可空默认为null
* @return MultipartFile 对象
* @throws IOException 文件读取异常
*/
public static MultipartFile convert(File file, String originalFilename, String contentType) throws IOException {
if (originalFilename == null) {
originalFilename = file.getName();
}
try (FileInputStream input = new FileInputStream(file)) {
return new MockMultipartFile(
"file", // form field name
originalFilename, // original file name
contentType, // content type
input // file content
);
}
}
/**
* 简化的转换方法
*/
public static MultipartFile convert(File file) throws IOException {
return convert(file, file.getName(), null);
}
/**
* 安全的ZIP解压方法
*/
private boolean safeUnzip(MultipartFile zipFile, File destDir, List<String> errorMsgList) {
long startTime = System.currentTimeMillis();
int extractedCount = 0;
// 先验证ZIP文件头
try (InputStream inputStream = zipFile.getInputStream()) {
byte[] header = new byte[4];
if (inputStream.read(header) != 4 ||
!(header[0] == 0x50 && header[1] == 0x4B &&
header[2] == 0x03 && header[3] == 0x04)) {
errorMsgList.add("无效的ZIP文件格式");
return false;
}
} catch (IOException e) {
errorMsgList.add("读取ZIP文件失败: " + e.getMessage());
return false;
}
// 尝试多种编码方式解压
Charset[] charsets = {
StandardCharsets.UTF_8,
Charset.forName("GBK"),
Charset.forName("GB2312"),
Charset.forName("ISO-8859-1"),
StandardCharsets.US_ASCII
};
for (Charset charset : charsets) {
try {
log.debug("尝试使用编码 {} 解压ZIP文件", charset.name());
if (unzipWithCharset(zipFile, destDir, charset)) {
extractedCount = countFiles(destDir);
log.info("使用编码 {} 成功解压 {} 个文件", charset.name(), extractedCount);
return extractedCount > 0;
}
} catch (Exception e) {
log.debug("编码 {} 解压失败: {}", charset.name(), e.getMessage());
}
}
errorMsgList.add("无法解压ZIP文件尝试所有编码都失败");
return false;
}
/**
* 使用指定编码解压ZIP文件
*/
private boolean unzipWithCharset(MultipartFile zipFile, File destDir, Charset charset) throws IOException {
try (InputStream inputStream = zipFile.getInputStream();
ZipInputStream zipIn = new ZipInputStream(inputStream, charset)) {
ZipEntry entry;
byte[] buffer = new byte[1024 * 1024]; // 1MB buffer
int fileCount = 0;
while ((entry = zipIn.getNextEntry()) != null) {
fileCount++;
String entryName = entry.getName();
// 安全检查防止目录遍历攻击
File entryFile = new File(destDir, entryName);
String canonicalDestPath = destDir.getCanonicalPath();
String canonicalEntryPath = entryFile.getCanonicalPath();
if (!canonicalEntryPath.startsWith(canonicalDestPath + File.separator)) {
throw new SecurityException("尝试访问外部目录: " + entryName);
}
if (entry.isDirectory()) {
if (!entryFile.exists() && !entryFile.mkdirs()) {
throw new IOException("创建目录失败: " + entryFile.getAbsolutePath());
}
} else {
File parentDir = entryFile.getParentFile();
if (!parentDir.exists() && !parentDir.mkdirs()) {
throw new IOException("创建父目录失败: " + parentDir.getAbsolutePath());
}
try (OutputStream out = new FileOutputStream(entryFile)) {
int len;
long totalBytes = 0;
while ((len = zipIn.read(buffer)) > 0) {
out.write(buffer, 0, len);
totalBytes += len;
// 可选限制单个文件大小
if (totalBytes > 100 * 1024 * 1024) { // 100MB
throw new IOException("文件过大: " + entryName);
}
}
}
}
zipIn.closeEntry();
// 限制解压的文件数量
if (fileCount > 10000) {
throw new IOException("ZIP包内文件数量过多可能为恶意压缩包");
}
}
return fileCount > 0;
} catch (ZipException e) {
throw new IOException("ZIP文件格式错误: " + e.getMessage(), e);
}
}
/**
* 计算目录下的文件数量
*/
private int countFiles(File dir) {
if (!dir.exists() || !dir.isDirectory()) {
return 0;
}
int count = 0;
Queue<File> queue = new LinkedList<>();
queue.add(dir);
while (!queue.isEmpty()) {
File current = queue.poll();
File[] files = current.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
queue.add(file);
} else {
count++;
}
}
}
}
return count;
}
/**
* 改进的findAllApplyFolders方法避免无限递归
*/
private void findAllApplyFolders(File rootDir, Set<File> applyFolders) {
if (rootDir == null || !rootDir.exists() || !rootDir.isDirectory()) {
return;
}
// 防止无限递归例如符号链接
try {
String canonicalPath = rootDir.getCanonicalPath();
if (visitedPaths.contains(canonicalPath)) {
return;
}
visitedPaths.add(canonicalPath);
} catch (IOException e) {
return;
}
// 深度限制
if (depth > 50) {
return;
}
depth++;
try {
File[] files = rootDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
String name = file.getName();
if (name.startsWith("Apply-") || name.startsWith("apply-")) {
applyFolders.add(file);
} else {
// 递归查找子目录
findAllApplyFolders(file, applyFolders);
}
}
}
}
} finally {
depth--;
}
}
// 添加成员变量用于防止无限递归
private Set<String> visitedPaths = new HashSet<>();
private int depth = 0;
// 以下方法与之前一致无需修改
private void validateSubFolderStructure(File rootFolder, List<String> errorList) {
Collection<File> subDirs = Arrays.asList(FileUtil.ls(rootFolder.getPath()));
Set<String> actualSubFolders = new HashSet<>();
for (File subDir : subDirs) {
if (subDir.isDirectory()) {
actualSubFolders.add(subDir.getName());
}
}
Set<String> missingFolders = new HashSet<>(REQUIRED_SUB_FOLDERS);
missingFolders.removeAll(actualSubFolders);
if (!missingFolders.isEmpty()) {
errorList.add("缺失必填子目录:" + String.join(",", missingFolders));
}
}
private void validateFileType(String subFolder, File file, List<String> errorList) {
if (file.isDirectory()) {
return;
}
String fileExt = FileUtil.extName(file).toLowerCase();
if (fileExt.isEmpty()) {
throw new IllegalArgumentException(file.getParentFile().getName() + "/" + file.getName() + ":无后缀文件不允许上传");
}
switch (subFolder) {
case "装备外观":
if (!IMAGE_TYPES.contains(fileExt)) {
throw new IllegalArgumentException(
subFolder + "/" + file.getName() + ":仅支持图片(" + String.join(",", IMAGE_TYPES) + "");
}
break;
case "合格证":
case "定期检验报告":
case "采购发票":
if (!IMAGE_TYPES.contains(fileExt) && !PDF_TYPE.contains(fileExt)) {
throw new IllegalArgumentException(
subFolder + "/" + file.getName() + ":仅支持图片+PDF" + String.join(",", IMAGE_TYPES) + ",pdf");
}
break;
default:
throw new IllegalArgumentException("不支持的子目录:" + subFolder);
}
}
}

View File

@ -0,0 +1,54 @@
package com.bonus.material.utils;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 构建指定文件夹结构的 ZIP 工具类适配 HttpServletResponse 输出
*/
public class FolderZipUtil {
/**
* 生成仅包含指定文件夹结构的空 ZIP 直接输出到响应流
*
* @param response HTTP 响应对象
* @param zipFileName 下载的 ZIP 文件名
* @param folderPaths 目标文件夹路径列表["docs/", "docs/img/"]
*/
public static void buildEmptyFolderZip(HttpServletResponse response, String zipFileName, List<String> folderPaths) {
try {
// 1. 设置响应头解决中文文件名乱码 + 触发下载
response.setContentType("application/octet-stream");
response.setCharacterEncoding("UTF-8");
// 编码文件名适配不同浏览器
String encodedFileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8.name())
.replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
// 禁止缓存
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 2. 直接通过响应流构建 ZIP无内存缓存
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream(), StandardCharsets.UTF_8)) {
// 遍历文件夹路径创建空文件夹条目
for (String folderPath : folderPaths) {
// 确保文件夹路径以 / 结尾ZIP / 结尾表示文件夹
String normalizedPath = folderPath.endsWith("/") ? folderPath : folderPath + "/";
ZipEntry folderEntry = new ZipEntry(normalizedPath);
zipOut.putNextEntry(folderEntry);
zipOut.closeEntry(); // 空文件夹无需写入内容直接关闭条目
}
zipOut.flush(); // 刷新流确保内容输出
}
} catch (Exception e) {
// 异常时返回 500 状态码
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
e.printStackTrace();
}
}
}

View File

@ -10,7 +10,7 @@
aaa.create_user AS createUser,
aaa.create_time AS createTime,
aaa.status AS status,
COUNT(bbb.dev_id) AS devCount,
SUM( CASE WHEN mdi.is_active = '1' THEN 1 ELSE 0 END ) AS devCount,
aaa.order_number AS orderNumber,
SUM(CASE WHEN mdi.entry_status = 1 THEN 1 ELSE 0 END) AS agree,
SUM(CASE WHEN mdi.entry_status = 2 THEN 1 ELSE 0 END) AS reject
@ -611,4 +611,45 @@
WHERE CONCAT_WS('/', proType, mainGx, childGx, devCategory, devSubcategory) = #{profession}
LIMIT 1
</select>
<select id="getFileName" resultType="java.lang.String">
SELECT CONCAT_WS('-', 'Apply', mdi.ma_id, mdi.device_name, mdi.item_type_model)
from ma_apply cds
LEFT JOIN ma_apply_details cdrd ON cdrd.cs_id = cds.id
LEFT JOIN ma_dev_info mdi ON cdrd.dev_id = mdi.ma_id
INNER JOIN ma_type_view mtv ON mtv.typeId = mdi.type_id
LEFT JOIN jj_sing_project jsp ON mdi.on_project = jsp.pro_code
LEFT JOIN sys_dept sd ON sd.dept_id = mdi.on_company
LEFT JOIN (SELECT max(next_check_time) next_check_time, ma_id from ma_dev_qc GROUP BY ma_id) mdq on
mdi.ma_id = mdq.ma_id
LEFT JOIN ma_supplier ms ON ms.supplier_id = mdi.supplier_id
LEFT JOIN sys_cnarea sc ON sc.area_code = mdi.province_id
WHERE mdi.is_active = '1'
and cds.id = #{orderId}
</select>
<select id="getFileId" resultType="java.util.Map">
SELECT mdi.ma_id AS id,
CONCAT_WS('-', 'Apply', mdi.ma_id, mdi.device_name, mdi.item_type_model) AS fileName
from ma_apply cds
LEFT JOIN ma_apply_details cdrd ON cdrd.cs_id = cds.id
LEFT JOIN ma_dev_info mdi ON cdrd.dev_id = mdi.ma_id
INNER JOIN ma_type_view mtv ON mtv.typeId = mdi.type_id
LEFT JOIN jj_sing_project jsp ON mdi.on_project = jsp.pro_code
LEFT JOIN sys_dept sd ON sd.dept_id = mdi.on_company
LEFT JOIN (SELECT max(next_check_time) next_check_time, ma_id from ma_dev_qc GROUP BY ma_id) mdq on
mdi.ma_id = mdq.ma_id
LEFT JOIN ma_supplier ms ON ms.supplier_id = mdi.supplier_id
LEFT JOIN sys_cnarea sc ON sc.area_code = mdi.province_id
WHERE mdi.is_active = '1'
and cds.id = #{orderId}
</select>
<select id="selNum" resultType="java.lang.Integer">
SELECT COUNT(1)
FROM ma_dev_info
where entry_status = 3
and ma_id IN (select dev_id
from ma_apply_details
where cs_id = #{id})
AND is_active = '1'
</select>
</mapper>

View File

@ -186,37 +186,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LEFT JOIN sys_user su2 ON su2.user_id = mdi.creator
left join sys_dept dept on dept.dept_id = moi.buyer_company
left join sys_dept up on up.dept_id = mdi.on_company
WHERE
mt.del_flag = '0'
<if test="keyWord!=null and keyWord!=''">
and moi.code like concat('%',#{keyWord},'%')
</if>
<if test="buyerCompany != null">
AND moi.buyer_company = #{buyerCompany}
</if>
<if test="sellerCompany != null">
AND mdi.on_company = #{sellerCompany}
</if>
<if test="deviceName != null and deviceName != ''">
AND mdi.device_name like concat('%',#{deviceName},'%')
</if>
<if test="orderStatus != null and orderStatus != ''">
AND hh.order_status = #{orderStatus}
</if>
<if test="code != null and code != ''">
AND moi.code = #{code}
</if>
<if test="startTime != null and endTime != null ">
AND ((hh.rent_begin_time BETWEEN #{startTime} AND #{endTime})
OR (hh.rent_end_time BETWEEN #{startTime} AND #{endTime})
OR (hh.rent_begin_time &lt; #{startTime} AND hh.rent_end_time > #{endTime}))
</if>
<if test="czcompanyName != null and czcompanyName != ''">
AND up.dept_name like concat('%',#{czcompanyName},'%')
</if>
<if test="companyName != null and companyName != ''">
AND dept.dept_name like concat('%',#{companyName},'%')
</if>
<where>
<if test="keyWord!=null and keyWord!=''">
and moi.code like concat('%',#{keyWord},'%')
</if>
<if test="buyerCompany != null">
AND moi.buyer_company = #{buyerCompany}
</if>
<if test="sellerCompany != null">
AND mdi.on_company = #{sellerCompany}
</if>
<if test="deviceName != null and deviceName != ''">
AND mdi.device_name like concat('%',#{deviceName},'%')
</if>
<if test="orderStatus != null and orderStatus != ''">
AND hh.order_status = #{orderStatus}
</if>
<if test="code != null and code != ''">
AND moi.code = #{code}
</if>
<if test="startTime != null and endTime != null ">
AND ((hh.rent_begin_time BETWEEN #{startTime} AND #{endTime})
OR (hh.rent_end_time BETWEEN #{startTime} AND #{endTime})
OR (hh.rent_begin_time &lt; #{startTime} AND hh.rent_end_time > #{endTime}))
</if>
<if test="czcompanyName != null and czcompanyName != ''">
AND up.dept_name like concat('%',#{czcompanyName},'%')
</if>
<if test="companyName != null and companyName != ''">
AND dept.dept_name like concat('%',#{companyName},'%')
</if>
</where>
GROUP BY
moi.buyer_company,
moi.`code`,
@ -343,7 +344,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LEFT JOIN sys_dept sd1 ON sd1.dept_id = subquery.first_ancestor
) dept ON dept.deptId = su.dept_id
WHERE
mt.del_flag = '0' and moi.order_id = #{orderId} limit 1
moi.order_id = #{orderId} limit 1
</select>
<select id="selectOrderDetailsByOrderId" resultType="com.bonus.material.order.domain.OrderDetailDto">
@ -418,7 +419,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LEFT JOIN sys_dept sdept on sdept.dept_id = mdi.on_company
LEFT JOIN sys_user su1 ON moi.buyer_id = su1.user_id AND su1.del_flag != 2
WHERE
mt.del_flag = 0 and hh.order_id = #{orderId}
hh.order_id = #{orderId}
</select>
<select id="getOrderStatusCount" resultType="com.bonus.material.order.domain.OrderInfoDto">
@ -468,8 +469,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
LEFT JOIN sys_dept dept ON dept.dept_id = moi.buyer_company
LEFT JOIN sys_dept up ON up.dept_id = mdi.on_company
WHERE
mt.del_flag = '0'
AND hh.order_id = #{orderId}
hh.order_id = #{orderId}
</select>
<select id="getRentDetails" resultType="com.bonus.material.comprehensive.entity.RentDetailDto">