lfx 5 dagar sedan
förälder
incheckning
7a72be125c
1 ändrade filer med 199 tillägg och 9 borttagningar
  1. 199 9
      src/main/java/com/tyson/controller/YiDaController.java

+ 199 - 9
src/main/java/com/tyson/controller/YiDaController.java

@@ -21,20 +21,20 @@ import org.springframework.scheduling.annotation.Async;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.NumberUtils;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.print.DocFlavor;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.io.*;
+import java.net.URLConnection;
 import java.net.URLDecoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Decription:
@@ -2626,7 +2626,7 @@ public class YiDaController {
      * @param filePathName
      * @param response
      */
-    @GetMapping("preview")
+    @GetMapping("previewOld")
     public void preview(String filePathName, HttpServletResponse response, HttpServletRequest request) {
         String queryString = request.getQueryString();
         String[] params = queryString.split("filePathName=");
@@ -2675,6 +2675,196 @@ public class YiDaController {
         }
     }
 
+    private static final int BUFFER_SIZE = 16 * 1024;
+    private static final Pattern RANGE_PATTERN = Pattern.compile("^bytes=(\\d*)-(\\d*)$");
+
+    /**
+     * 浏览器文件预览,支持图片、PDF、音视频及 HTTP Range 请求。
+     *
+     * @param filePathName 文件完整路径
+     * @param request HTTP 请求
+     * @param response HTTP 响应
+     */
+    @GetMapping("preview")
+    public void preview(@RequestParam("filePathName") String filePathName,
+                        HttpServletRequest request,
+                        HttpServletResponse response) {
+        if (filePathName == null || filePathName.trim().isEmpty()) {
+            sendError(response, HttpServletResponse.SC_BAD_REQUEST, "filePathName不能为空");
+            return;
+        }
+
+        Path filePath;
+        try {
+            filePath = Paths.get(filePathName).normalize();
+        } catch (Exception e) {
+            sendError(response, HttpServletResponse.SC_BAD_REQUEST, "文件路径格式错误");
+            return;
+        }
+
+        if (!Files.exists(filePath)) {
+            sendError(response, HttpServletResponse.SC_NOT_FOUND, "文件不存在");
+            return;
+        }
+
+        if (!Files.isRegularFile(filePath)) {
+            sendError(response, HttpServletResponse.SC_BAD_REQUEST, "目标不是普通文件");
+            return;
+        }
+
+        if (!Files.isReadable(filePath)) {
+            sendError(response, HttpServletResponse.SC_FORBIDDEN, "文件不可读取");
+            return;
+        }
+
+        try {
+            long fileLength = Files.size(filePath);
+            String contentType = Files.probeContentType(filePath);
+            if (contentType == null) {
+                contentType = URLConnection.guessContentTypeFromName(filePath.getFileName().toString());
+            }
+            if (contentType == null) {
+                contentType = "application/octet-stream";
+            }
+
+            response.reset();
+            response.setContentType(contentType);
+            response.setHeader("Content-Disposition", "inline");
+            response.setHeader("Accept-Ranges", "bytes");
+
+            String rangeHeader = request.getHeader("Range");
+            if (rangeHeader == null || rangeHeader.trim().isEmpty()) {
+                response.setStatus(HttpServletResponse.SC_OK);
+                response.setContentLengthLong(fileLength);
+                writeFile(filePath, 0, fileLength, response);
+                return;
+            }
+
+            long[] range = parseRange(rangeHeader, fileLength);
+            if (range == null) {
+                response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                response.setHeader("Content-Range", "bytes */" + fileLength);
+                return;
+            }
+
+            long start = range[0];
+            long end = range[1];
+            long contentLength = end - start + 1;
+
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+            response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
+            response.setContentLengthLong(contentLength);
+            writeFile(filePath, start, contentLength, response);
+        } catch (IOException e) {
+            sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "文件预览失败");
+        }
+    }
+
+    /**
+     * 解析单段 HTTP Range 请求。
+     *
+     * @param rangeHeader Range 请求头
+     * @param fileLength 文件总长度
+     * @return 起止字节下标;非法范围返回 null
+     */
+    private long[] parseRange(String rangeHeader, long fileLength) {
+        if (fileLength <= 0 || rangeHeader.contains(",")) {
+            return null;
+        }
+
+        Matcher matcher = RANGE_PATTERN.matcher(rangeHeader.trim());
+        if (!matcher.matches()) {
+            return null;
+        }
+
+        String startText = matcher.group(1);
+        String endText = matcher.group(2);
+        if (startText.isEmpty() && endText.isEmpty()) {
+            return null;
+        }
+
+        try {
+            long start;
+            long end;
+
+            if (startText.isEmpty()) {
+                long suffixLength = Long.parseLong(endText);
+                if (suffixLength <= 0) {
+                    return null;
+                }
+                start = Math.max(0, fileLength - suffixLength);
+                end = fileLength - 1;
+            } else {
+                start = Long.parseLong(startText);
+                end = endText.isEmpty() ? fileLength - 1 : Long.parseLong(endText);
+
+                if (start >= fileLength) {
+                    return null;
+                }
+                end = Math.min(end, fileLength - 1);
+            }
+
+            if (start < 0 || end < start) {
+                return null;
+            }
+
+            return new long[]{start, end};
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 从指定字节位置开始流式写出文件内容。
+     *
+     * @param filePath 文件路径
+     * @param start 起始字节位置
+     * @param length 输出长度
+     * @param response HTTP 响应
+     * @throws IOException 文件读取或响应写出失败
+     */
+    private void writeFile(Path filePath, long start, long length,
+                           HttpServletResponse response) throws IOException {
+        byte[] buffer = new byte[BUFFER_SIZE];
+        long remaining = length;
+
+        try (RandomAccessFile input = new RandomAccessFile(filePath.toFile(), "r")) {
+            input.seek(start);
+
+            while (remaining > 0) {
+                int bytesToRead = (int) Math.min(buffer.length, remaining);
+                int bytesRead = input.read(buffer, 0, bytesToRead);
+                if (bytesRead == -1) {
+                    break;
+                }
+
+                response.getOutputStream().write(buffer, 0, bytesRead);
+                remaining -= bytesRead;
+            }
+
+            response.getOutputStream().flush();
+        }
+    }
+
+    /**
+     * 在响应未提交前返回错误状态。
+     *
+     * @param response HTTP 响应
+     * @param status HTTP 状态码
+     * @param message 错误信息
+     */
+    private void sendError(HttpServletResponse response, int status, String message) {
+        if (!response.isCommitted()) {
+            try {
+                response.sendError(status, message);
+            } catch (IOException ignored) {
+                // 响应通道异常时无法继续向客户端写入错误信息。
+            }
+        }
+    }
+
+
+
     @Autowired
     private ReadExcel readExcel;