欢迎光临保亭县白事服务网
详情描述

前端代码 (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();
    }
}

主要功能说明

前端特点:

多种下载方式

  • 基本下载
  • 带参数下载
  • 显示下载进度

文件下载处理

  • 使用Blob处理二进制数据
  • 动态创建下载链接
  • 处理响应头中的文件名

后端特点:

多种返回方式

  • HttpServletResponse直接输出
  • ResponseEntity返回
  • 字节数组返回

支持的功能

  • 动态生成ZIP文件
  • 支持大文件下载
  • 文件名URL编码
  • 跨域支持
  • 异常处理

性能优化

  • 流式传输(避免内存溢出)
  • 临时文件清理

运行步骤:

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

注意事项:

大文件下载时使用流式传输 生产环境需要添加安全验证 考虑文件存储的位置和清理策略 前端需要注意跨域问题
相关帖子
临期食品消费在2026年成为一种新潮流,这反映了怎样的消费观念变化?
临期食品消费在2026年成为一种新潮流,这反映了怎样的消费观念变化?
非强制性的人脸识别场景,用户选择拒绝使用是否会带来不便?
非强制性的人脸识别场景,用户选择拒绝使用是否会带来不便?
我们距离拥有一个能理解复杂指令并协作的机器人伙伴还有多远?
我们距离拥有一个能理解复杂指令并协作的机器人伙伴还有多远?
2026年个人所得税APP填报专项附加扣除时,常见的信息填报错误有哪些需要避免?
2026年个人所得税APP填报专项附加扣除时,常见的信息填报错误有哪些需要避免?
长期食用含添加剂的预制菜,对身体健康可能存在哪些潜在影响?
长期食用含添加剂的预制菜,对身体健康可能存在哪些潜在影响?
身份证在有效期内但闸机读不出,是不是芯片被手机消磁了?
身份证在有效期内但闸机读不出,是不是芯片被手机消磁了?
公司经营贷额度与实缴资本,注册资本对额度影响
公司经营贷额度与实缴资本,注册资本对额度影响
菠菜和豆腐搭配真的会导致结石风险大幅增加吗?
菠菜和豆腐搭配真的会导致结石风险大幅增加吗?
在人际交往或商业合作前,如何合法合规地查询对方的信用状况?
在人际交往或商业合作前,如何合法合规地查询对方的信用状况?
补缴了城乡居民养老保险后,个人账户的累计金额会如何进行计算?
补缴了城乡居民养老保险后,个人账户的累计金额会如何进行计算?
新生儿医保和商业保险有什么区别?两者应该如何搭配互补?
新生儿医保和商业保险有什么区别?两者应该如何搭配互补?
海淀六年一学位优化后,房产学位被占用也能入学,具体会走哪种划片路径?
海淀六年一学位优化后,房产学位被占用也能入学,具体会走哪种划片路径?
个人负债、银行流水影响审批,学会自查测算综合负债风险比例
个人负债、银行流水影响审批,学会自查测算综合负债风险比例
商家写明
商家写明"不支持七天无理由"的链接,消费者下单后还能反悔吗?
共同借款人、担保人权责划分,明确经营债务分担与履约责任
共同借款人、担保人权责划分,明确经营债务分担与履约责任
针对阅读障碍人士,2026年有哪些技术或服务可以帮助他们更顺畅地获取文字信息?
针对阅读障碍人士,2026年有哪些技术或服务可以帮助他们更顺畅地获取文字信息?
高校毕业生选择基层就业,能享受哪些学费补偿和待遇保障?
高校毕业生选择基层就业,能享受哪些学费补偿和待遇保障?
如何利用大数据分析来优化一个区域的停车位供给与道路疏导策略?
如何利用大数据分析来优化一个区域的停车位供给与道路疏导策略?