【spring boot 使用apache poi 生成和处理word 文档】

导入maven依赖

xml 复制代码
<!-- pom.xml -->
<properties>
    <java.version>8</java.version>
    <poi.version>5.2.4</poi.version>
</properties>

<dependencies>
    <!-- Spring Boot Starters -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Apache POI 基础包 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>${poi.version}</version>
    </dependency>
    <!-- Apache POI for Word / PPT / Excel 核心依赖-->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>${poi.version}</version>
    </dependency>

    <!-- 可选:如果需要处理旧版Word文档(.doc) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>${poi.version}</version>
    </dependency>

    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Controller

java 复制代码
package com.example.wordservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@RequestMapping("/api/word")
public class WordController {

    @Autowired
    private WordDocumentService wordService;

    @GetMapping("/download")
    public ResponseEntity<InputStreamResource> downloadWordDocument() throws IOException {
        return wordService.createSimpleDocument();
    }
}

Service

java 复制代码
package com.example.wordservice;

import org.apache.poi.xwpf.usermodel.*;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

@Service
public class WordDocumentService {

    /**
     * 创建简单的Word文档
     */
    public ResponseEntity<InputStreamResource> createSimpleDocument() throws IOException {
        // 创建Word文档//每一份word文档底层都是XML
        //XWPF的全称是:XML Word Processing Format
        XWPFDocument document = new XWPFDocument();
        // 创建段落
        XWPFParagraph title = document.createParagraph();
        title.setAlignment(ParagraphAlignment.CENTER);
        //创建段落中的文本运行单元//只有文本运行单元才会存文本
        XWPFRun titleRun = title.createRun();
        titleRun.setText("项目报告");
        //设置样式:字体大小、颜色、斜体、等
        //是对底层API的封装,封装为高级API
        titleRun.setBold(true);
        titleRun.setFontSize(16);
        
        // 创建第二个段落
        XWPFParagraph content = document.createParagraph();
        content.setAlignment(ParagraphAlignment.LEFT);
        content.setSpacingBefore(200);
        XWPFRun contentRun = content.createRun();
        contentRun.setText("这是使用Spring Boot和Apache POI生成的Word文档。");
        
        // 转换为字节流//ByteArrayOutputStream 无需关闭
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        document.write(out);
        //可以改为try-with-resource
        document.close();
        //ByteArrayInputStream 无需关闭
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        
        // 设置HTTP响应头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; filename=report.docx");
        
        return ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new InputStreamResource(in));
    }
}

复制文本运行

java 复制代码
   /**
     * 文本运行是Word文档中最基本的文本格式单位。它代表一段具有相同格式的连续文本。
     * 复制文本运行的格式(字体名称和大小、字体颜色、粗体、斜体、下划线、上下标、背景色、字符间距等等)
     * 这个方法就是专门复制一个Run的所有格式属性
     * @param sourceRun 源文本运行
     * @param targetRun 目标文本运行
     */
    private static void copyRunFormatting(XWPFRun sourceRun, XWPFRun targetRun) {
        // 直接复制整个格式属性对象【底层级别的复制】
        //CTR 即:Content Run 底层叫法,高级叫法是XWPFRun ,二者等价
        //CTRPr  即:Content Run Properties 文本运行属性,即样式
        CTRPr sourceRPr = sourceRun.getCTR().getRPr();
        if (sourceRPr != null) {
            CTR targetCTR = targetRun.getCTR();
            CTRPr targetRPr = targetCTR.isSetRPr() ? targetCTR.getRPr() : targetCTR.addNewRPr();
            targetRPr.set(sourceRPr.copy());
        }
   }     

跨文档复制时,对文本中的指定文字进行标红

java 复制代码
 /**
     * 快速标红方法
     */
    public static void quickHighlight(XWPFParagraph newPara, String text, XWPFRun sourceRun) {
        // 定义关键词和替换模式
        String[] keywords = {"中共", "党中央/国务院"};

        String processedText = text;
        for (String keyword : keywords) {
            // 用特殊标记包围关键词,便于后续处理
            processedText = processedText.replace(keyword, "§RED§" + keyword + "§END§");
        }
        // 分割处理
        String[] parts = processedText.split("§RED§|§END§");

        for (int i = 0; i < parts.length; i++) {
            if (parts[i].isEmpty()) continue;

            XWPFRun newRun = newPara.createRun();
            copyRunFormatting(sourceRun, newRun);
            // 设置基本格式
            // newRun.setFontFamily("仿宋");
            //设置字体大小为16pt
             //newRun.setFontSize(16);
            // 奇数索引是标红文本(因为分割后格式:普通文本,标红文本,普通文本, ...)
            if (i % 2 == 1) {
                //设置为红色
                newRun.setColor("FF0000");
                //newRun.setBold(true);//设置加粗
            }
            //设置文本
            if(parts[i].startsWith("(")){
                addIndentAndSetText(newRun,parts[i]);
            }else{
                newRun.setText(parts[i]);
            }

            //如果是最后一个//添加换行
            if(i==parts.length-1){
                // 设置整个段落左缩进两个字符//run没有设置缩进的方法
               // newPara.setIndentationLeft(400);  // 左缩进400单位 ≈ 两个字符

                //添加换行
                newRun.addBreak();
            }
        }
    }

📚 InputStreamResource 详解

InputStreamResource 是 Spring Framework 中的一个类,用于将 输入流(InputStream) 包装成 Spring 的 Resource 对象,便于在 Web 响应中返回文件数据。

🔍 核心概念

1. 什么是 InputStreamResource?

java 复制代码
// InputStreamResource 是 Spring 对 InputStream 的包装
public class InputStreamResource extends AbstractResource {
    private final InputStream inputStream;
    
    // 它包装了一个输入流,使其可以作为 Resource 返回
}

2. 在文件下载中的角色

java 复制代码
@GetMapping("/download")
public ResponseEntity<InputStreamResource> downloadWordDocument() throws IOException {
    // 1. 生成Word文档到字节数组
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    XWPFDocument document = createDocument();
    document.write(out);
    document.close();
    
    // 2. 创建输入流
    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
    
    // 3. 包装成 InputStreamResource
    InputStreamResource resource = new InputStreamResource(in);
    
    return ResponseEntity.ok()
            .header("Content-Disposition", "attachment; filename=document.docx")
            .body(resource);
}

🎯 为什么使用 InputStreamResource?

与传统方式的对比

传统方式
java 复制代码
// 方式1:直接写入HttpServletResponse
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
    response.setHeader("Content-Disposition", "attachment; filename=document.docx");
    
    XWPFDocument document = createDocument();
    document.write(response.getOutputStream());
    document.close();
}

// 方式2:先保存到临时文件
@GetMapping("/download")
public ResponseEntity<FileSystemResource> download() throws IOException {
    File file = File.createTempFile("document", ".docx");
    //自行使用try-with-resource
    FileOutputStream out = new FileOutputStream(file);
    
    XWPFDocument document = createDocument();
    document.write(out);
    document.close();
    out.close();
    //file.delete();
    
    return ResponseEntity.ok()
            .body(new FileSystemResource(file));
}
Spring推荐方式(使用InputStreamResource)
java 复制代码
// 方式3:使用InputStreamResource(内存操作,性能好)
@GetMapping("/download")
public ResponseEntity<InputStreamResource> download() throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    XWPFDocument document = createDocument();
    document.write(out);
    document.close();
    
    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
    InputStreamResource resource = new InputStreamResource(in);
    
    return ResponseEntity.ok()
            .header("Content-Disposition", "attachment; filename=document.docx")
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
}

🔧 详细工作流程

完整的数据流

java 复制代码
public class WordDocumentService {
    
    public ResponseEntity<InputStreamResource> generateReport() throws IOException {
        // 📝 1. 创建Word文档(内存中)
        XWPFDocument document = new XWPFDocument();
        document.createParagraph().createRun().setText("报告内容");
        
        // 💾 2. 写入字节输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        document.write(byteArrayOutputStream);
        document.close();
        
        // 🔄 3. 转换为输入流
        ByteArrayInputStream byteArrayInputStream = 
            new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        
        // 📦 4. 包装为Spring Resource
        InputStreamResource resource = new InputStreamResource(byteArrayInputStream);
        
        // 🌐 5. 构建HTTP响应
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; filename=report.docx");
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        
        return ResponseEntity.ok()
                .headers(headers)
                .contentLength(byteArrayOutputStream.size()) // 可选:设置内容长度
                .body(resource);
    }
}

📊 与其他Resource类型的对比

Resource类型 适用场景 优点 缺点
InputStreamResource 动态生成的文件 内存中的数据 无需临时文件 性能好 数据需完全加载到内存
FileSystemResource 已存在的文件 大文件下载 支持大文件 内存占用小 需要创建临时文件
ByteArrayResource 小文件 已知数据 简单直接 所有数据在内存中
ClassPathResource 资源文件 模板文件 从classpath读取 只读,不能修改

🛠️ 实际应用示例

示例1:动态Word报告

java 复制代码
@RestController
@RequestMapping("/api/reports")
public class ReportController {
    
    @Autowired
    private ReportService reportService;
    
    @GetMapping("/word")
    public ResponseEntity<InputStreamResource> generateWordReport(
            @RequestParam String reportType,
            @RequestParam String startDate,
            @RequestParam String endDate) throws IOException {
        
        // 生成报告数据
        ReportData data = reportService.getReportData(reportType, startDate, endDate);
        
        // 创建Word文档
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (XWPFDocument document = createWordReport(data)) {
            document.write(out);
        }
        
        // 创建响应
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        String filename = String.format("%s报告_%s_%s.docx", reportType, startDate, endDate);
        
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=" + filename)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new InputStreamResource(in));
    }
    
    private XWPFDocument createWordReport(ReportData data) {
        XWPFDocument document = new XWPFDocument();
        // 构建Word文档内容...
        return document;
    }
}

示例2:模板填充下载

java 复制代码
@Service
public class TemplateService {
    
    public ResponseEntity<InputStreamResource> fillTemplate(Map<String, String> data) throws IOException {
        // 1. 读取模板文件
        ClassPathResource templateResource = new ClassPathResource("templates/report-template.docx");
        
        // 2. 处理模板
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (XWPFDocument document = new XWPFDocument(templateResource.getInputStream())) {
            //todo 实现自己的填充逻辑...
            fillTemplateData(document, data);
            document.write(out);
        }
        
        // 3. 返回结果
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=filled-report.docx")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new InputStreamResource(in));
    }
}

⚠️ 注意事项

1. 资源清理

java 复制代码
// Spring会自动管理InputStreamResource的资源清理
// 但最好确保你的InputStream是可关闭的
public ResponseEntity<InputStreamResource> safeDownload() throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try (XWPFDocument document = createDocument()) { // 使用try-with-resources
        document.write(out);
    }
    
    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
    // ByteArrayInputStream不需要显式关闭,但这是个好习惯
    return ResponseEntity.ok().body(new InputStreamResource(in));
}

2. 大文件处理

java 复制代码
// 对于大文件,考虑使用FileSystemResource避免内存溢出
public ResponseEntity<Resource> downloadLargeFile() throws IOException {
    File tempFile = File.createTempFile("large-document", ".docx");
    try (FileOutputStream out = new FileOutputStream(tempFile);
         XWPFDocument document = createLargeDocument()) {
        document.write(out);
    }
    
    // 使用FileSystemResource,支持大文件
    FileSystemResource resource = new FileSystemResource(tempFile);
    
    // 设置响应完成后删除临时文件
    return ResponseEntity.ok()
            .header("Content-Disposition", "attachment; filename=large-document.docx")
            .body(resource);
}

💡 总结

返回 InputStreamResource 的含义:

  1. 包装动态数据:将内存中生成的Word文档包装成可下载的资源
  2. Spring标准做法:符合Spring的Resource抽象,便于统一处理
  3. 无需临时文件:所有操作在内存中完成,性能更好
  4. 自动资源管理:Spring框架负责关闭流和清理资源
  5. 灵活的HTTP响应:可以方便地设置文件名、Content-Type等头部信息

简单来说: InputStreamResource 让动态生成的文件数据能够以 流式方式 返回给客户端,同时享受Spring框架的资源管理便利性。

📊 两种 ContentType 的详细区别

🔍 核心概念对比

特性 APPLICATION_OCTET_STREAM WordprocessingML Document
类型 通用二进制流 特定文件类型
含义 "这是一个二进制文件,具体类型未知" "这是一个Word 2007+文档"
使用场景 通用文件下载 类型不确定的文件 明确的Word文档下载
浏览器行为 总是触发下载 可能尝试预览(如果支持)

🎯 具体区别分析

1. MediaType.APPLICATION_OCTET_STREAM

java 复制代码
// 通用二进制流类型
Content-Type: application/octet-stream

// 浏览器行为:总是下载
// 用途:当服务器不知道文件确切类型,或希望强制下载时使用

2. WordprocessingML Document

java 复制代码
// 具体的Word文档类型  
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document

// 浏览器行为:可能尝试预览(如Edge、Chrome)
// 用途:明确告诉浏览器这是Word文档

🌐 浏览器行为差异

测试示例

java 复制代码
@RestController
public class DownloadController {
    
    // 方式1:使用通用二进制流
    @GetMapping("/download-generic")
    public ResponseEntity<InputStreamResource> downloadGeneric() {
        // 浏览器:总是弹出下载对话框
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=document.docx")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)  // 强制下载
                .body(resource);
    }
    
    // 方式2:使用具体Word类型
    @GetMapping("/download-specific")
    public ResponseEntity<InputStreamResource> downloadSpecific() {
        // 浏览器:可能直接在线打开(如果支持)
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=document.docx")
                .contentType(MediaType.parseMediaType(
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"))  // 具体类型
                .body(resource);
    }
    
    // 方式3:使用具体类型但强制下载
    @GetMapping("/download-specific-force")
    public ResponseEntity<InputStreamResource> downloadSpecificForce() {
        // 浏览器:明确类型但仍强制下载
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=document.docx")  // attachment强制下载
                .contentType(MediaType.parseMediaType(
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"))
                .body(resource);
    }
    
    // 方式4:使用具体类型允许预览
    @GetMapping("/preview")
    public ResponseEntity<InputStreamResource> preview() {
        // 浏览器:可能尝试在线预览
        return ResponseEntity.ok()
                .header("Content-Disposition", "inline; filename=document.docx")  // inline允许预览
                .contentType(MediaType.parseMediaType(
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"))
                .body(resource);
    }
}

🔧 实际应用场景

场景1:通用文件下载服务

java 复制代码
@Service
public class FileDownloadService {
    
    /**
     * 通用文件下载 - 不确定文件类型时使用
     */
    public ResponseEntity<InputStreamResource> downloadFile(byte[] fileData, String filename) {
        // 当不知道具体文件类型,或希望总是触发下载时
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=" + filename)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)  // 通用类型
                .body(new InputStreamResource(new ByteArrayInputStream(fileData)));
    }
    
    /**
     * 特定类型文件下载 - 知道确切类型时使用
     */
    public ResponseEntity<InputStreamResource> downloadWordDocument(byte[] fileData, String filename) {
        // 明确知道这是Word文档,希望浏览器能正确识别
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=" + filename)
                .contentType(MediaType.parseMediaType(
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"))  // 具体类型
                .body(new InputStreamResource(new ByteArrayInputStream(fileData)));
    }
}

场景2:智能内容类型选择

java 复制代码
public class ContentTypeResolver {
    
    /**
     * 根据文件扩展名智能选择ContentType
     */
    public static MediaType resolveContentType(String filename) {
        if (filename == null) {
            return MediaType.APPLICATION_OCTET_STREAM;
        }
        
        String extension = getFileExtension(filename).toLowerCase();
        
        switch (extension) {
            case "docx":
                return MediaType.parseMediaType(
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            case "doc":
                return MediaType.parseMediaType("application/msword");
            case "xlsx":
                return MediaType.parseMediaType(
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            case "pdf":
                return MediaType.parseMediaType("application/pdf");
            case "txt":
                return MediaType.TEXT_PLAIN;
            default:
                return MediaType.APPLICATION_OCTET_STREAM;  // 默认通用类型
        }
    }
    
    /**
     * 创建下载响应(智能ContentType)
     */
    public static ResponseEntity<InputStreamResource> createDownloadResponse(
            byte[] data, String filename, boolean forceDownload) {
        
        MediaType contentType = resolveContentType(filename);
        String contentDisposition = forceDownload ? 
            "attachment; filename=\"" + filename + "\"" : 
            "inline; filename=\"" + filename + "\"";
        
        return ResponseEntity.ok()
                .header("Content-Disposition", contentDisposition)
                .contentType(contentType)
                .contentLength(data.length)
                .body(new InputStreamResource(new ByteArrayInputStream(data)));
    }
    
    private static String getFileExtension(String filename) {
        return filename.substring(filename.lastIndexOf(".") + 1);
    }
}

📋 完整的Office文件类型映射

java 复制代码
public class OfficeMediaTypes {
    // Word文档
    public static final MediaType WORD_DOCX = MediaType.parseMediaType(
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
    public static final MediaType WORD_DOC = MediaType.parseMediaType("application/msword");
    
    // Excel文档
    public static final MediaType EXCEL_XLSX = MediaType.parseMediaType(
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    public static final MediaType EXCEL_XLS = MediaType.parseMediaType("application/vnd.ms-excel");
    
    // PowerPoint文档
    public static final MediaType POWERPOINT_PPTX = MediaType.parseMediaType(
        "application/vnd.openxmlformats-officedocument.presentationml.presentation");
    public static final MediaType POWERPOINT_PPT = MediaType.parseMediaType("application/vnd.ms-powerpoint");
    
    /**
     * 获取推荐的ContentType配置
     */
    public static ContentTypeConfig getConfig(String filename, boolean forceDownload) {
        MediaType mediaType = resolveMediaType(filename);
        String disposition = forceDownload ? "attachment" : "inline";
        
        return new ContentTypeConfig(mediaType, disposition);
    }
    
    public static class ContentTypeConfig {
        public final MediaType mediaType;
        public final String disposition;
        
        public ContentTypeConfig(MediaType mediaType, String disposition) {
            this.mediaType = mediaType;
            this.disposition = disposition;
        }
    }
}

⚠️ 注意事项

1. 浏览器兼容性考虑

java 复制代码
public class DownloadStrategy {
    
    /**
     * 安全的内容类型策略
     */
    public MediaType getSafeContentType(String userAgent, String filename) {
        // 检测老旧浏览器
        if (isLegacyBrowser(userAgent)) {
            // 老旧浏览器可能不认识具体的Office类型,使用通用类型更安全
            return MediaType.APPLICATION_OCTET_STREAM;
        }
        
        // 现代浏览器使用具体类型
        return OfficeMediaTypes.resolveMediaType(filename);
    }
    
    /**
     * 根据场景选择最佳策略
     */
    public ResponseEntity<InputStreamResource> createOptimalResponse(
            byte[] data, String filename, DownloadContext context) {
        
        MediaType contentType;
        
        if (context.isForceDownload()) {
            // 场景:明确要求下载 → 使用通用类型确保下载
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        } else if (context.isKnownOfficeFile(filename)) {
            // 场景:已知Office文件且允许预览 → 使用具体类型
            contentType = OfficeMediaTypes.resolveMediaType(filename);
        } else {
            // 场景:未知文件类型 → 使用通用类型
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }
        
        return buildResponse(data, filename, contentType, context.isForceDownload());
    }
}

2. 实际项目推荐

java 复制代码
// 对于Word文档生成项目,推荐这样做:
@GetMapping("/download-report")
public ResponseEntity<InputStreamResource> downloadReport() {
    byte[] documentData = generateWordReport();
    
    // 最佳实践:具体类型 + 强制下载
    return ResponseEntity.ok()
            .header("Content-Disposition", "attachment; filename=report.docx")  // 强制下载
            .contentType(MediaType.parseMediaType(                              // 具体类型
                "application/vnd.openxmlformats-officedocument.wordprocessingml.document"))
            .body(new InputStreamResource(new ByteArrayInputStream(documentData)));
}

💡 总结与建议

选择策略:

使用 APPLICATION_OCTET_STREAM 当:

  • 文件类型不确定
  • 希望强制下载(不预览)
  • 兼容老旧浏览器
  • 通用文件下载服务

使用具体类型当:

  • 明确知道文件类型
  • 希望浏览器能正确识别
  • 现代浏览器环境
  • 可能希望在线预览的场景

最终推荐:

对于你的Word文档生成项目,推荐使用具体类型

java 复制代码
.contentType(MediaType.parseMediaType(
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"))

这样既能正确标识文件类型,又通过attachment确保下载行为。

相关推荐
SelectDB技术团队7 小时前
Apache Doris 4.0 版本正式发布:全面升级 AI 与搜索能力,强化离线计算
人工智能·apache
数字芯片实验室8 小时前
流片可以失败,但人心的账本不能亏空
java·开发语言
华仔啊8 小时前
为什么你的 @Transactional 不生效?一文搞懂 Spring 事务机制
java·后端
Lacrimosa&L8 小时前
OS_3 Memory、4 File、5 IO
java
爱学的小码8 小时前
JavaEE——多线程1(超详细版)
java·java-ee
tuokuac8 小时前
依赖spring-cloud-starter-gateway与spring-cloud-gateway-dependencies的区别
java·gateway
seabirdssss8 小时前
JDK 11 环境正确,端口未被占用,但是运行 Tomcat 11 仍然闪退
java·开发语言·tomcat
努力学算法的蒟蒻8 小时前
day03(11.1)——leetcode面试经典150
java·算法·leetcode
Mr YiRan9 小时前
SYN关键字辨析,各种锁优缺点分析和面试题讲解
java·开发语言