Java案例如何实现文件下载?

wen python案例 2

本文目录导读:

Java案例如何实现文件下载?

  1. Servlet方式(基础实现)
  2. Spring Boot方式(推荐)
  3. NIO方式(高性能)
  4. 前端调用方式
  5. 完整的工具类
  6. 注意事项

在Java中实现文件下载有多种方式,下面我将介绍几种常见的实现方法,包括Servlet方式、Spring Boot方式以及使用NIO的高效方式。

Servlet方式(基础实现)

@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        // 1. 获取要下载的文件路径
        String fileName = request.getParameter("fileName");
        String filePath = getServletContext().getRealPath("/uploads") + File.separator + fileName;
        File file = new File(filePath);
        if (!file.exists()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
            return;
        }
        // 2. 设置响应头
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", 
            "attachment; filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
        response.setContentLengthLong(file.length());
        // 3. 读取文件并写入响应流
        try (FileInputStream fis = new FileInputStream(file);
             BufferedInputStream bis = new BufferedInputStream(fis);
             OutputStream os = response.getOutputStream()) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.flush();
        }
    }
}

Spring Boot方式(推荐)

1 使用ResponseEntity

@RestController
@RequestMapping("/api/files")
public class FileDownloadController {
    @GetMapping("/download/{fileName}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
        try {
            // 1. 加载文件
            Path filePath = Paths.get("uploads/" + fileName);
            Resource resource = new UrlResource(filePath.toUri());
            if (!resource.exists()) {
                return ResponseEntity.notFound().build();
            }
            // 2. 设置响应头
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = "application/octet-stream";
            }
            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType(contentType))
                    .header(HttpHeaders.CONTENT_DISPOSITION, 
                        "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"")
                    .body(resource);
        } catch (IOException e) {
            return ResponseEntity.internalServerError().build();
        }
    }
}

2 使用StreamingResponseBody(适合大文件)

@GetMapping("/download/large/{fileName}")
public ResponseEntity<StreamingResponseBody> downloadLargeFile(@PathVariable String fileName) {
    StreamingResponseBody stream = outputStream -> {
        Path filePath = Paths.get("uploads/" + fileName);
        try (InputStream inputStream = Files.newInputStream(filePath)) {
            IOUtils.copy(inputStream, outputStream);
        }
    };
    return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                "attachment; filename=\"" + fileName + "\"")
            .body(stream);
}

NIO方式(高性能)

@GetMapping("/download/nio/{fileName}")
public void downloadWithNio(@PathVariable String fileName, HttpServletResponse response) 
        throws IOException {
    Path filePath = Paths.get("uploads/" + fileName);
    FileChannel fileChannel = FileChannel.open(filePath, StandardOpenOption.READ);
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", 
        "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
    response.setContentLengthLong(fileChannel.size());
    // 使用FileChannel直接传输
    try (FileChannel channel = fileChannel;
         WritableByteChannel writableChannel = Channels.newChannel(response.getOutputStream())) {
        long position = 0;
        long count = channel.size();
        while (position < count) {
            position += channel.transferTo(position, 8192, writableChannel);
        }
    }
}

前端调用方式

1 使用a标签直接下载

<a href="/api/files/download/test.txt" download>下载文件</a>

2 使用JavaScript下载

function downloadFile(fileName) {
    const link = document.createElement('a');
    link.href = `/api/files/download/${fileName}`;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

3 使用fetch API(支持进度显示)

async function downloadWithProgress(fileName) {
    const response = await fetch(`/api/files/download/${fileName}`);
    const contentLength = response.headers.get('content-length');
    const total = parseInt(contentLength, 10);
    let loaded = 0;
    const reader = response.body.getReader();
    const chunks = [];
    while(true) {
        const {done, value} = await reader.read();
        if (done) break;
        chunks.push(value);
        loaded += value.length;
        // 更新进度
        const progress = (loaded / total) * 100;
        console.log(`下载进度: ${progress.toFixed(2)}%`);
    }
    // 合并chunks并下载
    const blob = new Blob(chunks);
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    link.click();
    URL.revokeObjectURL(url);
}

完整的工具类

public class FileDownloadUtil {
    /**
     * 设置下载响应头
     */
    public static void setDownloadHeaders(HttpServletResponse response, 
                                         String fileName, long fileSize) throws UnsupportedEncodingException {
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", 
            "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
        response.setHeader("Content-Length", String.valueOf(fileSize));
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
    }
    /**
     * 判断文件类型
     */
    public static String getContentType(String fileName) {
        if (fileName.endsWith(".txt")) return "text/plain";
        if (fileName.endsWith(".pdf")) return "application/pdf";
        if (fileName.endsWith(".doc") || fileName.endsWith(".docx")) 
            return "application/msword";
        if (fileName.endsWith(".xls") || fileName.endsWith(".xlsx")) 
            return "application/vnd.ms-excel";
        if (fileName.endsWith(".png")) return "image/png";
        if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) 
            return "image/jpeg";
        return "application/octet-stream";
    }
}

注意事项

  1. 大文件处理:对于大文件(>100MB),建议使用StreamingResponseBody或NIO方式
  2. 内存管理:避免将整个文件读入内存,使用缓冲流或管道传输
  3. 安全性:防止路径遍历攻击,校验文件名
  4. 编码问题:文件名需要进行URL编码,避免中文乱码
  5. 断点续传:可以支持Range头实现断点续传功能

选择哪种方式取决于你的具体需求:简单的下载用Servlet方式,Spring Boot项目使用ResponseEntity,大文件使用NIO或StreamingResponseBody。

抱歉,评论功能暂时关闭!