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

前端代码 (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年是否已普及电子身份证?该如何申领使用?
除了实体证件,2026年是否已普及电子身份证?该如何申领使用?
如果更换了新的手机号码,是否会影响原有电子身份证的正常使用和有效性?
如果更换了新的手机号码,是否会影响原有电子身份证的正常使用和有效性?
除了干燥和磕碰,还有哪些容易被忽略的因素会引发鼻子流血?
除了干燥和磕碰,还有哪些容易被忽略的因素会引发鼻子流血?
工伤认定与后续的劳动能力鉴定,两者在时限规定上有何联系与区别?
工伤认定与后续的劳动能力鉴定,两者在时限规定上有何联系与区别?
汛期户外作业人员需要注意哪些安全事项以防范突发风险?
汛期户外作业人员需要注意哪些安全事项以防范突发风险?
男职工缴纳的生育保险,在配偶生育时可以享受哪些报销或津贴待遇?
男职工缴纳的生育保险,在配偶生育时可以享受哪些报销或津贴待遇?
未来的智慧高速“大脑”,是怎样通过数据协同来缓解节假日大拥堵的?
未来的智慧高速“大脑”,是怎样通过数据协同来缓解节假日大拥堵的?
智能家居系统在高层住宅中的应用,为居住安全与便利性带来了哪些提升?
智能家居系统在高层住宅中的应用,为居住安全与便利性带来了哪些提升?
商业性质公寓或小产权房的交易记录,是否纳入住房套数计算范围?
商业性质公寓或小产权房的交易记录,是否纳入住房套数计算范围?