sql/mysql/mdm_unit_measure_type_bag.sql
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,5 @@ -- 计éåä½ç±»åç±âNåå âæ¹ä¸ºâNè¢âãæ°åè¡¨ç¤ºæ¯æè¢æ°ï¼ééä»ç± weight åæ®µè¡¨ç¤ºã -- å¯é夿§è¡ï¼åªä¿®æ¹ç¬¦åè¯¥æ ¼å¼çç±»åï¼ä¸æ¹åä½åç§°åééã UPDATE mdm_unit_measure SET type = CONCAT(LEFT(type, CHAR_LENGTH(type) - 2), 'è¢') WHERE type REGEXP '^[0-9]{1,5}åå $'; yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureSaveReqVO.java
@@ -18,7 +18,7 @@ @Schema(description = "åä½åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") private String name; @Schema(description = "ç±»åï¼48åå /100åå /å¨ï¼ï¼éå¿ å¡«ï¼è¢ç çæä¾èµè¯¥å段ï¼ä¸ºç©ºæ¶æ æ³çæè¢ç ", example = "48åå ") @Schema(description = "ç±»åï¼48è¢/100è¢/å¨ï¼ï¼éå¿ å¡«ï¼è¢ç çæä¾èµè¯¥å段ï¼ä¸ºç©ºæ¶æ æ³çæè¢ç ", example = "48è¢") private String type; @Schema(description = "éé(kg)", example = "25") @@ -39,4 +39,4 @@ @Schema(description = "夿³¨", example = "夿³¨ä¿¡æ¯") private String remark; } } yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/dataobject/unit/MdmUnitMeasureDO.java
@@ -42,7 +42,7 @@ /** * ç±»å * * å¯éå¼ï¼48åå ã100åå ãå¨ * å¯éå¼ï¼48è¢ã100è¢ãå¨ * * éå¿ å¡«ï¼MES è¢ç çæä¾èµè¯¥å段æ¢ç®æ¯æè¢æ°ï¼ä¸ºç©ºæ¶æ æ³çæè¢ç */ @@ -80,4 +80,4 @@ */ private String remark; } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeGenerateReqVO.java
@@ -13,7 +13,7 @@ @NotNull(message = "åç§ä¸è½ä¸ºç©º") private Long itemId; @Schema(description = "æçæ°ï¼åç§ä¸»åä½ç±»å为 48åå /100åå æ¶å¿ å¡«ï¼è¢ç æ° = æ¯æè¢æ° à æçæ°ï¼", example = "5") @Schema(description = "æçæ°ï¼åç§ä¸»åä½ç±»å为 48è¢/100è¢ æ¶å¿ å¡«ï¼è¢ç æ° = æ¯æè¢æ° à æçæ°ï¼", example = "5") @Positive(message = "æçæ°å¿ é¡»å¤§äº 0") private Integer palletCount; yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeGenerateRespVO.java
@@ -12,7 +12,7 @@ @Schema(description = "çæç» UUID") private String uuid; @Schema(description = "åç§ä¸»åä½ç±»å", example = "48åå ") @Schema(description = "åç§ä¸»åä½ç±»å", example = "48è¢") private String type; @Schema(description = "æ¯æçè¢æ°ï¼ç±»å为 å¨ æ¶ä¸º 0ï¼", example = "48") yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/qrexport/MesProQrExportTaskController.java
@@ -100,11 +100,13 @@ } @GetMapping("/task/download") @Operation(summary = "ä¸è½½äºç»´ç 导åºäº§ç©ï¼ä¸è½½å®æåç³»ç»ç«å³å é¤ä¸´æ¶æä»¶ï¼") @Operation(summary = "ä¸è½½äºç»´ç 导åºäº§ç©ï¼æ¯æ Range åæ®µä¸è½½ï¼") @Parameter(name = "id", description = "ä»»å¡ç¼å·", required = true, example = "1024") @PreAuthorize(ANY_EXPORT_PERMISSION) public void download(@RequestParam("id") Long id, HttpServletResponse response) throws IOException { taskService.download(id, response); public void download(@RequestParam("id") Long id, @RequestHeader(value = "Range", required = false) String range, HttpServletResponse response) throws IOException { taskService.download(id, range, response); } // ==================== ç§ææ¹æ³ ==================== yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -861,7 +861,7 @@ ErrorCode PRO_BAG_CODE_REPLACE_ALREADY_REPLACED = new ErrorCode(1_040_752_009, "è¢ç å·²å®ææ¿æ¢ï¼ä¸å 许é夿¿æ¢"); ErrorCode PRO_BAG_CODE_REPLACE_STATUS_INVALID = new ErrorCode(1_040_752_010, "è¢ç ç¶æä¸å è®¸ç ´ææ¿æ¢"); ErrorCode PRO_BAG_CODE_REPLACE_PALLET_INVALID = new ErrorCode(1_040_752_011, "è¢ç åæçä¸å卿已ä½åºï¼ä¸å 许继æ¿"); ErrorCode PRO_BAG_CODE_ITEM_TYPE_NOT_CONFIGURED = new ErrorCode(1_040_752_012, "åç§ä¸»è®¡éå使ªé 置类åï¼ä» æ¯æ 48åå /100åå /å¨"); ErrorCode PRO_BAG_CODE_ITEM_TYPE_NOT_CONFIGURED = new ErrorCode(1_040_752_012, "åç§ä¸»è®¡éå使ªé 置类åï¼ä» æ¯æ 48è¢/100è¢/å¨"); ErrorCode PRO_BAG_CODE_PALLET_COUNT_REQUIRED = new ErrorCode(1_040_752_013, "该åç§ææççæè¢ç ï¼æçæ°å¿ é¡»å¤§äº 0"); ErrorCode PRO_BAG_CODE_BIND_BATCH_ITEM_MISMATCH = new ErrorCode(1_040_752_014, "å½å ¥æ¹æ¬¡çè¢ç å¿ é¡»å±äºåä¸åç§"); yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtils.java
@@ -9,6 +9,9 @@ import lombok.extern.slf4j.Slf4j; import javax.imageio.ImageIO; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; @@ -17,6 +20,7 @@ import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.FilterOutputStream; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; @@ -138,8 +142,24 @@ public static void writeZipEntry(ZipOutputStream zip, QrLabelInfo label) throws IOException { BufferedImage image = createQrLabel(label.getCode(), label.getFileName()); zip.putNextEntry(new ZipEntry(label.getFileName() + ".png")); ImageIO.write(image, "png", zip); zip.closeEntry(); try { // ImageIO.write(OutputStream) å¯è½ä¸ºæ¯å¼ å¾çå建ç£çç¼åï¼å¤§æ¹éå¯¼åºæ¹ç¨å åç¼åã // å è£ æµç close ä» flushï¼ä¸è½å ³éå¤å± ZIPã ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next(); try (ImageOutputStream imageOut = new MemoryCacheImageOutputStream(new FilterOutputStream(zip) { @Override public void close() throws IOException { flush(); } })) { writer.setOutput(imageOut); writer.write(image); } finally { writer.dispose(); } } finally { zip.closeEntry(); } } // ==================== ç§ææ¹æ³ ==================== yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrexport/MesProQrExportProperties.java
@@ -72,10 +72,10 @@ /** * ä¸´æ¶ ZIP çä¿çæ¶é´ï¼åéï¼ * <p> * æ£å¸¸ä¸è½½å®æåç«å³å é¤ï¼ä» å¨ãä¸è½½ä¸æãæãçæå®æåå§ç»æªä¸è½½ãæ¶ä¿çä¸å°æ®µæ¶é´ï¼ * å°æç±æ¸ çä»»å¡å é¤ãå¦åæ¯æ¬¡ä¸æé½è¦éæ°çæä¸ä¸å¼ æ ç¾ï¼æè ä¸ GB çä¸´æ¶æä»¶ä¸ç´å çç£çã * 宿åä¿ç䏿®µæ¶é´ä¾å段ä¸è½½å失败éè¯ï¼å°æç±æ¸ çä»»å¡å é¤ã * é¿å ç½ç»ä¸æåéæ°çæä¸ä¸å¼ æ ç¾ï¼ä¹é¿å ä¸´æ¶æä»¶é¿æå ç¨ç£çã */ private int tempFileRetentionMinutes = 10; private int tempFileRetentionMinutes = 60; /** * è§£æä¸´æ¶ç®å½ï¼æªé ç½®æ¶åéå°ç³»ç»ä¸´æ¶ç®å½ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
@@ -83,9 +83,9 @@ */ private static final String UNIT_TYPE_TON = "å¨"; /** * 计éåä½ç±»åãNåå ãï¼æ¯æçè£ N 个è¢ç * 计éåä½ç±»åãNè¢ãï¼æ¯æçè£ N 个è¢ç */ private static final Pattern KG_TYPE_PATTERN = Pattern.compile("^(\\d{1,5})åå $"); private static final Pattern BAG_TYPE_PATTERN = Pattern.compile("^(\\d{1,5})è¢$"); @Resource private MesCodeRedisDAO codeRedisDao; @@ -99,9 +99,9 @@ MesMdmItemDO item = mdmItemService.validateItemExists(generateReqVO.getItemId()); String type = getUnitType(item); // 2. æç±»åæ¢ç®è¢ç æ°éï¼Nåå = æ¯æ N è¢ Ã æçæ°ï¼å¨ = ç´æ¥è¾å ¥è¢ç æ°ï¼æ æçï¼ // 2. æç±»åæ¢ç®è¢ç æ°éï¼Nè¢ = æ¯æ N è¢ Ã æçæ°ï¼å¨ = ç´æ¥è¾å ¥è¢ç æ°ï¼æ æçï¼ boolean ton = UNIT_TYPE_TON.equals(type); int perPallet = ton ? 0 : parseKgPerPallet(type); int perPallet = ton ? 0 : parseBagsPerPallet(type); int totalCount; int palletCount = 0; if (ton) { @@ -190,7 +190,7 @@ } /** * 读ååç§ä¸»è®¡éåä½çç±»åï¼48åå /100åå /å¨ï¼ï¼æªé ç½®ææ æ³è¯å«æ¶æ¥é * 读ååç§ä¸»è®¡éåä½çç±»åï¼48è¢/100è¢/å¨ï¼ï¼æªé ç½®ææ æ³è¯å«æ¶æ¥é */ private String getUnitType(MesMdmItemDO item) { String type = null; @@ -203,17 +203,17 @@ if (type == null) { throw exception(PRO_BAG_CODE_ITEM_TYPE_NOT_CONFIGURED); } if (!UNIT_TYPE_TON.equals(type) && !KG_TYPE_PATTERN.matcher(type).matches()) { if (!UNIT_TYPE_TON.equals(type) && !BAG_TYPE_PATTERN.matcher(type).matches()) { throw exception(PRO_BAG_CODE_ITEM_TYPE_NOT_CONFIGURED); } return type; } /** * è§£æãNåå ãç±»åå¾åºæ¯æçè¢æ° * è§£æãNè¢ãç±»åå¾åºæ¯æçè¢æ° */ private int parseKgPerPallet(String type) { Matcher matcher = KG_TYPE_PATTERN.matcher(type); private int parseBagsPerPallet(String type) { Matcher matcher = BAG_TYPE_PATTERN.matcher(type); if (!matcher.matches()) { throw exception(PRO_BAG_CODE_ITEM_TYPE_NOT_CONFIGURED); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/qrexport/MesProQrExportRunner.java
@@ -22,6 +22,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.Deflater; import java.util.zip.ZipOutputStream; /** @@ -111,7 +112,9 @@ int written = 0; int pageNo = 1; try (OutputStream fileOut = new FileOutputStream(target); ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(fileOut))) { ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(fileOut, 256 * 1024))) { // æ¯å¼ PNG å·²å缩ï¼åå ZIP deflate ä¼é夿¶è CPUï¼ä¿ç ZIP 容å¨åéå¼ æä»¶ç»æã zip.setLevel(Deflater.NO_COMPRESSION); while (true) { List<QrLabelInfo> page = source.fetchPage(pageNo, PAGE_SIZE); if (page.isEmpty()) { yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/qrexport/MesProQrExportTaskService.java
@@ -56,13 +56,13 @@ /** * ä¸è½½å¯¼åºäº§ç© * <p> * ä¸è½½æååç«å³å é¤ä¸´æ¶ ZIPï¼é¿å ä¸ GB çä¸´æ¶æä»¶å 积ï¼ï¼ä¸è½½ä¸æåä¿çè³æ¸ çä»»å¡å é¤ï¼ * 以便è°ç¨æ¹å¨çæ¶é´å éè¯èä¸å¿ éæ°çæã * æ¯æ Range åæ®µä¸è½½ï¼ä¸´æ¶ ZIP ä¿çè³æ¸ çä»»å¡å é¤ï¼ä»¥ä¾¿ä¼ è¾ä¸æåç»ä¼ ã * * @param id ä»»å¡ç¼å· * @param range HTTP Range 请æ±å¤´ï¼å¯ä¸ºç©º * @param response ååº */ void download(Long id, HttpServletResponse response) throws IOException; void download(Long id, String range, HttpServletResponse response) throws IOException; /** * æ¸ çè¶ æçä¸´æ¶ ZIP æä»¶ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/qrexport/MesProQrExportTaskServiceImpl.java
@@ -14,7 +14,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.util.StreamUtils; import org.springframework.validation.annotation.Validated; import java.io.BufferedInputStream; @@ -22,10 +21,13 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.io.OutputStream; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BAG_CODE_NOT_EXISTS; @@ -60,6 +62,11 @@ MesProQrExportTaskStatusEnum.CANCELED.getStatus(), MesProQrExportTaskStatusEnum.FAILED.getStatus(), MesProQrExportTaskStatusEnum.DOWNLOADED.getStatus()); private static final Pattern BYTE_RANGE = Pattern.compile("^bytes=(\\d+)-(\\d*)$"); /** 鲿¢å®æ¶æ¸ çä»»å¡å 餿£å¨ä¼ è¾ç ZIPã */ private final ConcurrentHashMap<Long, Integer> activeDownloads = new ConcurrentHashMap<>(); @Resource private MesProQrExportTaskMapper taskMapper; @@ -156,25 +163,74 @@ } @Override public void download(Long id, HttpServletResponse response) throws IOException { public void download(Long id, String range, HttpServletResponse response) throws IOException { MesProQrExportTaskDO task = getTask(id); if (!MesProQrExportTaskStatusEnum.FINISHED.getStatus().equals(task.getStatus())) { throw exception(PRO_QR_EXPORT_TASK_STATUS_INVALID); } File file = resolveTaskFile(task); response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(task.getTaskName() + ".zip")); boolean success = false; try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { StreamUtils.copy(in, response.getOutputStream()); response.getOutputStream().flush(); success = true; } finally { // ä» å¨å®æ´ä¼ è¾åå é¤ï¼ä¸ææ¶ä¿çï¼äº¤ç±æ¸ çä»»å¡å¨ä¿çæåå é¤ï¼ä¾¿äºçæ¶é´å éè¯ if (success) { deleteAfterDownload(task, file); activeDownloads.compute(id, (key, count) -> count == null ? 1 : count + 1); try { File file = resolveTaskFile(task); long fileSize = file.length(); long[] bounds = resolveRange(range, fileSize); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("X-Accel-Buffering", "no"); response.setHeader("Cache-Control", "private, no-store"); response.setHeader("Access-Control-Expose-Headers", "Content-Length, Content-Range, Accept-Ranges"); if (bounds == null) { response.setStatus(416); response.setHeader("Content-Range", "bytes */" + fileSize); return; } long start = bounds[0]; long end = bounds[1]; long length = end - start + 1; response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(task.getTaskName() + ".zip")); if (range != null && !range.isBlank()) { response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize); } response.setContentLengthLong(length); try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { in.skipNBytes(start); OutputStream out = response.getOutputStream(); byte[] buffer = new byte[64 * 1024]; long remaining = length; while (remaining > 0) { int read = in.read(buffer, 0, (int) Math.min(buffer.length, remaining)); if (read < 0) { throw new IOException("äºç»´ç å¯¼åºæä»¶å¨ä¸è½½è¿ç¨ä¸è¢«æªæ"); } out.write(buffer, 0, read); remaining -= read; } out.flush(); } } finally { activeDownloads.computeIfPresent(id, (key, count) -> count > 1 ? count - 1 : null); } } /** åªæ¯æå个éåºé´ï¼ä¸åæ³ç Range è¿å 416ï¼é¿å è¯¯ä¼ æ´å ã */ private long[] resolveRange(String range, long fileSize) { if (range == null || range.isBlank()) { return new long[]{0, fileSize - 1}; } Matcher matcher = BYTE_RANGE.matcher(range); if (!matcher.matches() || fileSize <= 0) { return null; } try { long start = Long.parseLong(matcher.group(1)); long end = matcher.group(2).isEmpty() ? fileSize - 1 : Long.parseLong(matcher.group(2)); if (start >= fileSize || end < start) { return null; } return new long[]{start, Math.min(end, fileSize - 1)}; } catch (NumberFormatException e) { return null; } } @@ -185,6 +241,9 @@ List<MesProQrExportTaskDO> list = taskMapper.selectExpiredFinalList(FINAL_STATUSES, deadline); int count = 0; for (MesProQrExportTaskDO task : list) { if (activeDownloads.containsKey(task.getId())) { continue; } if (StrUtil.isBlank(task.getFilePath())) { continue; } @@ -278,20 +337,6 @@ throw exception(PRO_QR_EXPORT_TASK_FILE_MISSING); } return file; } /** * ä¸è½½å®æå³å é¤ä¸´æ¶æä»¶ï¼å次导åºå¯è¾¾ä¸ GBï¼ççä¼è¿ éæç£çåæ»¡ */ private void deleteAfterDownload(MesProQrExportTaskDO task, File file) { try { Files.deleteIfExists(file.toPath()); } catch (IOException e) { log.warn("[deleteAfterDownload][å é¤ä¸´æ¶æä»¶å¤±è´¥ {}]", file.getAbsolutePath(), e); } taskMapper.clearFilePath(task.getId()); taskMapper.updateStatus(task.getId(), MesProQrExportTaskStatusEnum.DOWNLOADED.getStatus(), null, null, null, null); } } yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtilsZipTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,41 @@ package cn.iocoder.yudao.module.mes.framework.qrcode; import cn.hutool.extra.qrcode.QrCodeUtil; import org.junit.jupiter.api.Test; import javax.imageio.ImageIO; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; class QrCodeImageUtilsZipTest { @Test void writesMultipleReadablePngEntries() throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (ZipOutputStream zip = new ZipOutputStream(bytes)) { QrCodeImageUtils.writeZipEntry(zip, QrLabelInfo.builder() .fileName("1234567890123456").code("1234567890123456").build()); QrCodeImageUtils.writeZipEntry(zip, QrLabelInfo.builder() .fileName("1234567890123457").code("1234567890123457").build()); } try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { assertEquals("1234567890123456.png", zip.getNextEntry().getName()); var first = ImageIO.read(zip); assertNotNull(first); assertEquals("1234567890123456", QrCodeUtil.decode(first)); assertEquals("1234567890123457.png", zip.getNextEntry().getName()); var second = ImageIO.read(zip); assertNotNull(second); assertEquals("1234567890123457", QrCodeUtil.decode(second)); assertNull(zip.getNextEntry()); } } } yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/qrexport/MesProQrExportTaskDownloadTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,62 @@ package cn.iocoder.yudao.module.mes.service.pro.qrexport; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.qrexport.MesProQrExportTaskDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.qrexport.MesProQrExportTaskMapper; import cn.iocoder.yudao.module.mes.enums.pro.MesProQrExportTaskStatusEnum; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.util.ReflectionTestUtils; import java.nio.file.Files; import java.nio.file.Path; import java.lang.reflect.Proxy; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class MesProQrExportTaskDownloadTest { @TempDir Path tempDir; @Test void rangeDownloadReturnsExactBytesAndKeepsFileForRetry() throws Exception { byte[] content = "0123456789".getBytes(); Path file = tempDir.resolve("export.zip"); Files.write(file, content); MesProQrExportTaskDO task = MesProQrExportTaskDO.builder() .id(13L).taskName("test").status(MesProQrExportTaskStatusEnum.FINISHED.getStatus()) .filePath(file.toString()).build(); MesProQrExportTaskMapper mapper = (MesProQrExportTaskMapper) Proxy.newProxyInstance( getClass().getClassLoader(), new Class[]{MesProQrExportTaskMapper.class}, (proxy, method, args) -> { if (method.getName().equals("selectById")) { return task; } throw new UnsupportedOperationException(method.getName()); }); MesProQrExportTaskServiceImpl service = new MesProQrExportTaskServiceImpl(); ReflectionTestUtils.setField(service, "taskMapper", mapper); MockHttpServletResponse partial = new MockHttpServletResponse(); service.download(13L, "bytes=3-6", partial); assertEquals(206, partial.getStatus()); assertEquals("bytes 3-6/10", partial.getHeader("Content-Range")); assertEquals("4", partial.getHeader("Content-Length")); assertArrayEquals("3456".getBytes(), partial.getContentAsByteArray()); MockHttpServletResponse invalid = new MockHttpServletResponse(); service.download(13L, "bytes=10-11", invalid); assertEquals(416, invalid.getStatus()); assertEquals("bytes */10", invalid.getHeader("Content-Range")); MockHttpServletResponse full = new MockHttpServletResponse(); service.download(13L, null, full); assertEquals(200, full.getStatus()); assertArrayEquals(content, full.getContentAsByteArray()); assertTrue(Files.exists(file)); } } yudao-server/src/main/resources/application.yaml
@@ -28,8 +28,8 @@ max-concurrent-tasks: 1 # å个åå°ä»»å¡æé¿è¿è¡æ¶é´ï¼åéï¼ï¼è¶ æ¶èªå¨åæ¶å¹¶æ¸ ç max-runtime-minutes: 30 # ä¸´æ¶ ZIP ä¿çæ¶é´ï¼åéï¼ãä¸è½½å®æåç«å³å é¤ï¼æ¤å¤ä» å åºãä¸è½½ä¸æ/å§ç»æªä¸è½½ãçæ®ç temp-file-retention-minutes: 10 # ä¸´æ¶ ZIP ä¿çæ¶é´ï¼åéï¼ãåæ®µä¸è½½å䏿éè¯æé´ä¿çï¼å°æåæ¸ ç temp-file-retention-minutes: 60 # ä¸´æ¶ ZIP ç®å½ï¼ç空åä½¿ç¨ ${java.io.tmpdir}/mes-qr-export temp-dir: ''