yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -17,6 +17,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import com.google.common.util.concurrent.UncheckedExecutionException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import jakarta.validation.ValidationException; @@ -34,6 +35,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.async.AsyncRequestNotUsableException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.servlet.NoHandlerFoundException; @@ -67,6 +69,14 @@ private final ApiErrorLogCommonApi apiErrorLogApi; /** 客æ·ç«¯å·²æå¼æ¶ååºä¸å¯ååï¼æ³¨å ¥ HttpServletResponse 使 Spring MVC ç´æ¥ç»æå¤çã */ @ExceptionHandler(AsyncRequestNotUsableException.class) public void asyncRequestNotUsableExceptionHandler(HttpServletRequest request, HttpServletResponse response, AsyncRequestNotUsableException ex) { log.debug("[asyncRequestNotUsableExceptionHandler][客æ·ç«¯å·²æå¼ uri={}]", request.getRequestURI()); } /** * å¤çææå¼å¸¸ï¼ä¸»è¦æ¯æä¾ç» Filter ä½¿ç¨ * å 为 Filter ä¸èµ° SpringMVC çæµç¨ï¼ä½æ¯æä»¬åéè¦å åºå¤çå¼å¸¸ï¼æä»¥è¿éæä¾ä¸ä¸ªå ¨éçå¼å¸¸å¤çè¿ç¨ï¼ä¿æé»è¾ç»ä¸ã yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandlerClientAbortTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,40 @@ package cn.iocoder.yudao.framework.web.core.handler; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.AsyncRequestNotUsableException; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.io.IOException; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class GlobalExceptionHandlerClientAbortTest { @Test void doesNotSerializeJsonAfterZipResponseBecomesUnusable() throws Exception { MockMvcBuilders.standaloneSetup(new ZipController()) .setControllerAdvice(new GlobalExceptionHandler("test", null)) .build() .perform(get("/zip")) .andExpect(status().isOk()) .andExpect(content().bytes(new byte[0])); } @RestController static class ZipController { @GetMapping("/zip") public void download(HttpServletResponse response) throws IOException { response.setContentType("application/zip"); response.flushBuffer(); throw new AsyncRequestNotUsableException("client disconnected"); } } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/qrexport/MesProQrExportTaskServiceImpl.java
@@ -15,6 +15,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import org.springframework.web.context.request.async.AsyncRequestNotUsableException; import java.io.BufferedInputStream; import java.io.File; @@ -193,6 +194,7 @@ response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize); } response.setContentLengthLong(length); long sent = 0; try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { in.skipNBytes(start); OutputStream out = response.getOutputStream(); @@ -205,8 +207,19 @@ } out.write(buffer, 0, read); remaining -= read; sent += read; } out.flush(); } catch (AsyncRequestNotUsableException e) { // æµè§å¨åæ¶ãç½ç»æå¼æä»£çå ³éè¿æ¥æ¶ï¼ååºå·²ç»ä¸å¯åï¼ä¸è½åè¿å JSON é误ã // ZIP ä¿çå°æ¸ çæ¶é´ï¼å端å¯éè¯å½ååæ®µï¼è®°å½èå´ä¸è¿åº¦ä¾¿äºææ¥é¢ç¹æå¼ã log.warn("[download][客æ·ç«¯ä¸æ taskId={}, range={}, sent={}/{} bytes: {}]", id, range, sent, length, e.getMessage()); } catch (IOException e) { // ZIP ååºå¤´å·²ç»åå ¥ï¼ä¸è½äº¤ç»å ¨å±å¼å¸¸å¤çå¨åºåå CommonResultã // Content-Length ä¸å¹é ä¼è®©å®¢æ·ç«¯å¤å®æ¬æ®µå¤±è´¥å¹¶éè¯ã log.error("[download][ä¼ è¾å¤±è´¥ taskId={}, range={}, sent={}/{} bytes]", id, range, sent, length, e); } } finally { activeDownloads.computeIfPresent(id, (key, count) -> count > 1 ? count - 1 : null); yudao-server/src/main/resources/static/trace.html
@@ -327,7 +327,7 @@ </div> <div class="contact-item"> <span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5 12 3l9 6.5V20a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9.5 22v-6h5v6"/></svg></span> <span class="txt"><span class="lbl">å ¬å¸ç½å</span><div class="val"><a href="https://www.talimu.net" target="_blank" rel="noopener">www.talimu.net</a></div></span> <span class="txt"><span class="lbl">å ¬å¸ç½å</span><div class="val"><a href="https://www.tahezy.com" target="_blank" rel="noopener">www.tahezy.com</a></div></span> </div> <div class="contact-item"> <span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.4 8.4 0 0 1 3.8-.9h.5a8.5 8.5 0 0 1 8 8z"/></svg></span>