保亭县中国白事服务网

前端ajax请求+后端java实现的下载zip压缩包功能示例

2026-04-08 22:49:01 浏览次数:0
详细信息

前端代码 (Vue.js + Axios)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件下载</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <div id="app">
        <h2>文件下载示例</h2>

        <!-- 方式1:直接下载按钮 -->
        <div style="margin: 20px 0;">
            <button @click="downloadZip">下载ZIP文件</button>
            <span v-if="downloadMessage" style="margin-left: 10px; color: green;">
                {{ downloadMessage }}
            </span>
        </div>

        <!-- 方式2:带参数下载 -->
        <div style="margin: 20px 0;">
            <label>文件名:</label>
            <input v-model="fileName" placeholder="输入文件名" />
            <button @click="downloadWithParams">带参数下载</button>
        </div>

        <!-- 方式3:显示进度 -->
        <div style="margin: 20px 0;">
            <button @click="downloadWithProgress">下载并显示进度</button>
            <div v-if="progress > 0" style="margin-top: 10px;">
                下载进度:{{ progress }}%
                <div style="width: 300px; height: 20px; border: 1px solid #ccc;">
                    <div :style="{ width: progress + '%', height: '100%', backgroundColor: '#4CAF50' }"></div>
                </div>
            </div>
        </div>
    </div>

    <script>
        const { createApp, ref } = Vue;

        createApp({
            setup() {
                const downloadMessage = ref('');
                const fileName = ref('example.zip');
                const progress = ref(0);

                // 方法1:基本下载
                const downloadZip = async () => {
                    try {
                        const response = await axios({
                            method: 'GET',
                            url: 'http://localhost:8080/api/download/zip',
                            responseType: 'blob'
                        });

                        // 创建下载链接
                        const url = window.URL.createObjectURL(new Blob([response.data]));
                        const link = document.createElement('a');
                        link.href = url;
                        link.setAttribute('download', 'download.zip');
                        document.body.appendChild(link);
                        link.click();
                        link.remove();

                        downloadMessage.value = '下载成功!';
                        setTimeout(() => {
                            downloadMessage.value = '';
                        }, 3000);
                    } catch (error) {
                        console.error('下载失败:', error);
                        downloadMessage.value = '下载失败!';
                    }
                };

                // 方法2:带参数下载
                const downloadWithParams = async () => {
                    try {
                        const response = await axios({
                            method: 'POST',
                            url: 'http://localhost:8080/api/download/zip-with-params',
                            data: {
                                filename: fileName.value,
                                files: ['file1.txt', 'file2.txt']
                            },
                            responseType: 'blob'
                        });

                        // 从响应头获取文件名
                        const contentDisposition = response.headers['content-disposition'];
                        let filename = fileName.value;
                        if (contentDisposition) {
                            const filenameMatch = contentDisposition.match(/filename="(.+)"/);
                            if (filenameMatch && filenameMatch[1]) {
                                filename = filenameMatch[1];
                            }
                        }

                        const url = window.URL.createObjectURL(new Blob([response.data]));
                        const link = document.createElement('a');
                        link.href = url;
                        link.setAttribute('download', filename);
                        document.body.appendChild(link);
                        link.click();
                        link.remove();
                    } catch (error) {
                        console.error('下载失败:', error);
                        alert('下载失败:' + error.message);
                    }
                };

                // 方法3:显示下载进度
                const downloadWithProgress = async () => {
                    try {
                        progress.value = 0;
                        const response = await axios({
                            method: 'GET',
                            url: 'http://localhost:8080/api/download/zip',
                            responseType: 'blob',
                            onDownloadProgress: (progressEvent) => {
                                if (progressEvent.total) {
                                    const percentCompleted = Math.round(
                                        (progressEvent.loaded * 100) / progressEvent.total
                                    );
                                    progress.value = percentCompleted;
                                }
                            }
                        });

                        const url = window.URL.createObjectURL(new Blob([response.data]));
                        const link = document.createElement('a');
                        link.href = url;
                        link.setAttribute('download', 'download.zip');
                        document.body.appendChild(link);
                        link.click();
                        link.remove();

                        setTimeout(() => {
                            progress.value = 0;
                        }, 2000);
                    } catch (error) {
                        console.error('下载失败:', error);
                        alert('下载失败!');
                    }
                };

                return {
                    downloadMessage,
                    fileName,
                    progress,
                    downloadZip,
                    downloadWithParams,
                    downloadWithProgress
                };
            }
        }).mount('#app');
    </script>
</body>
</html>

后端Java代码 (Spring Boot)

1. Maven依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- 如果需要操作ZIP文件 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-compress</artifactId>
        <version>1.23.0</version>
    </dependency>
</dependencies>

2. 下载DTO类

package com.example.demo.dto;

import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;

@Data
public class DownloadRequest {
    @NotEmpty(message = "文件名不能为空")
    private String filename;

    private List<String> files;

    private String downloadType = "ZIP";
}

3. 控制器类

package com.example.demo.controller;

import com.example.demo.dto.DownloadRequest;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RestController
@RequestMapping("/api/download")
@CrossOrigin(origins = "*") // 允许跨域
@Validated
public class DownloadController {

    /**
     * 方式1:基本下载 - 返回字节流
     */
    @GetMapping("/zip")
    public void downloadZip(HttpServletResponse response) throws IOException {
        // 创建测试文件
        String content = "这是测试文件内容\nHello World!";

        // 设置响应头
        response.setContentType("application/zip");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, 
                          "attachment; filename=\"download.zip\"");

        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            // 添加第一个文件
            ZipEntry entry1 = new ZipEntry("file1.txt");
            zos.putNextEntry(entry1);
            zos.write(content.getBytes(StandardCharsets.UTF_8));
            zos.closeEntry();

            // 添加第二个文件
            ZipEntry entry2 = new ZipEntry("folder/file2.txt");
            zos.putNextEntry(entry2);
            zos.write("第二个文件内容".getBytes(StandardCharsets.UTF_8));
            zos.closeEntry();

            // 添加图片文件(模拟)
            ZipEntry entry3 = new ZipEntry("image/example.png");
            zos.putNextEntry(entry3);
            byte[] imageBytes = generateMockImage();
            zos.write(imageBytes);
            zos.closeEntry();
        }
    }

    /**
     * 方式2:使用ResponseEntity返回 - 更好的控制HTTP响应
     */
    @GetMapping("/zip2")
    public ResponseEntity<Resource> downloadZip2() throws IOException {
        // 创建临时ZIP文件
        Path tempFile = Files.createTempFile("download", ".zip");

        try (FileOutputStream fos = new FileOutputStream(tempFile.toFile());
             ZipOutputStream zos = new ZipOutputStream(fos)) {

            // 添加多个文件到ZIP
            for (int i = 1; i <= 5; i++) {
                ZipEntry entry = new ZipEntry("file" + i + ".txt");
                zos.putNextEntry(entry);
                String content = "这是第 " + i + " 个文件的内容\n";
                zos.write(content.getBytes(StandardCharsets.UTF_8));
                zos.closeEntry();
            }
        }

        // 准备响应
        Resource resource = new InputStreamResource(
            new FileInputStream(tempFile.toFile())
        );

        // 删除临时文件(在实际使用中可能需要延迟删除)
        tempFile.toFile().deleteOnExit();

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                       "attachment; filename=\"files.zip\"")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(tempFile.toFile().length())
                .body(resource);
    }

    /**
     * 方式3:带参数下载 - POST请求
     */
    @PostMapping("/zip-with-params")
    public ResponseEntity<byte[]> downloadWithParams(
            @Valid @RequestBody DownloadRequest request) throws IOException {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ZipOutputStream zos = new ZipOutputStream(baos)) {

            if (request.getFiles() != null && !request.getFiles().isEmpty()) {
                for (String fileName : request.getFiles()) {
                    ZipEntry entry = new ZipEntry(fileName);
                    zos.putNextEntry(entry);
                    String content = "文件: " + fileName + "\n生成时间: " + 
                                   new java.util.Date();
                    zos.write(content.getBytes(StandardCharsets.UTF_8));
                    zos.closeEntry();
                }
            } else {
                // 如果没有提供文件列表,创建默认文件
                for (int i = 1; i <= 3; i++) {
                    ZipEntry entry = new ZipEntry("default_file_" + i + ".txt");
                    zos.putNextEntry(entry);
                    String content = "默认文件 " + i + "\n请求参数: " + request.getFilename();
                    zos.write(content.getBytes(StandardCharsets.UTF_8));
                    zos.closeEntry();
                }
            }
        }

        byte[] zipBytes = baos.toByteArray();

        // 对文件名进行URL编码
        String encodedFilename = URLEncoder.encode(
            request.getFilename().endsWith(".zip") ? 
            request.getFilename() : request.getFilename() + ".zip",
            StandardCharsets.UTF_8.toString()
        ).replace("+", "%20");

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, 
                   "attachment; filename=\"" + encodedFilename + "\"");
        headers.add(HttpHeaders.CONTENT_TYPE, "application/zip");
        headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(zipBytes.length));

        return ResponseEntity.ok()
                .headers(headers)
                .body(zipBytes);
    }

    /**
     * 方式4:从文件系统下载真实文件
     */
    @GetMapping("/zip-from-filesystem")
    public ResponseEntity<Resource> downloadFromFileSystem() throws IOException {
        // 假设文件存在
        File file = new File("/path/to/your/file.zip");

        if (!file.exists()) {
            return ResponseEntity.notFound().build();
        }

        Resource resource = new InputStreamResource(new FileInputStream(file));

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                       "attachment; filename=\"" + file.getName() + "\"")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(file.length())
                .body(resource);
    }

    /**
     * 方式5:分批下载大文件(流式传输)
     */
    @GetMapping("/large-zip")
    public ResponseEntity<Resource> downloadLargeZip() throws IOException {
        // 创建大ZIP文件的逻辑(实际使用时可能从其他地方获取)
        Path tempFile = createLargeZipFile();

        Resource resource = new InputStreamResource(
            new FileInputStream(tempFile.toFile())
        );

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                       "attachment; filename=\"large_file.zip\"")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(tempFile.toFile().length())
                .body(resource);
    }

    // 生成模拟图片数据
    private byte[] generateMockImage() {
        // 创建一个简单的PNG头部(实际使用时应该读取真实图片)
        String pngHeader = "PNG_HEADER_MOCK_DATA";
        return pngHeader.getBytes(StandardCharsets.UTF_8);
    }

    // 创建大ZIP文件的方法
    private Path createLargeZipFile() throws IOException {
        Path tempFile = Files.createTempFile("large", ".zip");

        try (FileOutputStream fos = new FileOutputStream(tempFile.toFile());
             ZipOutputStream zos = new ZipOutputStream(fos)) {

            // 创建多个大文件
            for (int i = 0; i < 10; i++) {
                ZipEntry entry = new ZipEntry("large_file_" + i + ".bin");
                zos.putNextEntry(entry);

                // 写入1MB数据
                byte[] buffer = new byte[1024 * 1024]; // 1MB
                for (int j = 0; j < 1024; j++) {
                    zos.write(buffer);
                }
                zos.closeEntry();
            }
        }

        return tempFile;
    }
}

4. 配置类(跨域和文件上传大小配置)

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .exposedHeaders("Content-Disposition") // 允许前端访问Content-Disposition
                .maxAge(3600);
    }
}

5. 异常处理类

package com.example.demo.handler;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IOException.class)
    public ResponseEntity<Map<String, String>> handleIOException(IOException e) {
        Map<String, String> error = new HashMap<>();
        error.put("error", "文件操作失败");
        error.put("message", e.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
    }

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<Map<String, String>> handleMaxSizeException(
            MaxUploadSizeExceededException e) {
        Map<String, String> error = new HashMap<>();
        error.put("error", "文件太大");
        error.put("message", "文件大小超过限制");
        return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
                .body(error);
    }
}

6. 主启动类

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.util.unit.DataSize;

import javax.servlet.MultipartConfigElement;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    /**
     * 配置文件上传大小限制
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // 单个文件最大
        factory.setMaxFileSize(DataSize.ofMegabytes(100));
        // 总上传数据最大
        factory.setMaxRequestSize(DataSize.ofMegabytes(500));
        return factory.createMultipartConfig();
    }
}

主要功能说明

前端特点:

多种下载方式

文件下载处理

后端特点:

多种返回方式

支持的功能

性能优化

运行步骤:

启动Spring Boot应用 将前端HTML文件放在静态目录或使用其他HTTP服务器 访问前端页面进行测试

注意事项:

大文件下载时使用流式传输 生产环境需要添加安全验证 考虑文件存储的位置和清理策略 前端需要注意跨域问题

相关推荐