千家信息网

Angular/Spring Boot Rest API下载Word文档

发表于:2025-02-06 作者:千家信息网编辑
千家信息网最后更新 2025年02月06日,POI生成Word文档使用POI XWPF生成Word文档,引入POI: org.apache.poi poi-ooxml 4.1.0项目中经常从Word模板生成文档,下面示例演示了
千家信息网最后更新 2025年02月06日Angular/Spring Boot Rest API下载Word文档

POI生成Word文档

使用POI XWPF生成Word文档,引入POI:

    org.apache.poi    poi-ooxml    4.1.0

项目中经常从Word模板生成文档,下面示例演示了替换文档内容的方法。模版中要替换的内容以${}标识,调用XWPFRun.setText()方法更新文档。

import org.apache.poi.xwpf.usermodel.XWPFDocument;import org.apache.poi.xwpf.usermodel.XWPFParagraph;import org.apache.poi.xwpf.usermodel.XWPFRun;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.IOException;import java.util.List;import java.util.Map;public final class XWPFDocumentUtils {    private XWPFDocumentUtils() {    }    public static byte[] replaceDocument(String path, Map fields) throws IOException {        try (XWPFDocument doc = new XWPFDocument(new FileInputStream(path))) {            for (XWPFParagraph paragraph : doc.getParagraphs()) {                if (!paragraph.getText().contains("${")) {                    continue;                }                replaceParagraph(paragraph, fields);            }            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {                doc.write(out);                return out.toByteArray();            }        }    }    private static void replaceParagraph(XWPFParagraph paragraph, Map fields) {        for (Map.Entry field : fields.entrySet()) {            String find = "${" + field.getKey() + "}";            if (!paragraph.getText().contains(find)) {                continue;            }            replaceText(paragraph, find, field.getValue());        }    }    private static void replaceText(XWPFParagraph paragraph, String key, String value) {        List runs = paragraph.getRuns();        for (int i = 0; i < runs.size(); i++) {            XWPFRun run = runs.get(i);            String text = run.text();            if (text.contains("${") || (text.contains("$") && runs.get(i + 1).text().startsWith("{"))) {                StringBuilder builder = new StringBuilder(text);                while (!text.contains("}")) {                    text = runs.get(i + 1).text();                    builder.append(text);                    paragraph.removeRun(i + 1);                }                text = builder.toString();                run.setText(text.contains(key) ? text.replace(key, value) : text, 0);            }        }    }}

Spring Boot Rest API

Rest API

调用replaceDocument()方法生成word文档,如要在Rest API中定义文件名称,使用ResponseEntity并增加header,否则可以直接返回byte[]。

@GetMapping("/api/doc/{heroName}")public ResponseEntity getDocument(@PathVariable String heroName) {    try {        Map fields = new HashMap<>();        fields.put("hero_name", heroName);        fields.put("create_date", "2019年6月");        byte[] bytes = XWPFDocumentUtil.replaceDocument("template/hero.docx", fields);        HttpHeaders headers = new HttpHeaders();        headers.add("Content-Disposition", "attachment;filename=hero.docx");        return ResponseEntity.ok().headers(headers).body(bytes);    } catch (Exception e) {        throw new XWPFDocumentException(e.getMessage());    }}

CORS

配置CORS的ExposedHeaders,否则前台不能读取"Content-Disposition":

@BeanCorsConfigurationSource corsConfigurationSource() {    CorsConfiguration configuration = new CorsConfiguration();    SecurityProperties.Cors cors = config.getCors();    configuration.setAllowedMethods(Arrays.asList("*"));    configuration.setAllowedHeaders(Arrays.asList("Accept","Accept-Encoding","Accept-Language","Authorization","Connection","Content-Type","Host","Origin","Referer","User-Agent","X-Requested-With"));    configuration.setExposedHeaders(Arrays.asList("Content-Disposition"));    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();    source.registerCorsConfiguration("/**", configuration);    return source;}

Test

测试使用exchange方法,设置header APPLICATION_OCTET_STREAM:

import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.web.client.TestRestTemplate;import org.springframework.http.*;import org.springframework.test.context.junit4.SpringRunner;import java.util.Arrays;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)public class HeroesApplicationTests {    @Autowired    private TestRestTemplate restTemplate;    @Test    public void getDocumentSuccess() {        HttpHeaders headers = new HttpHeaders();        headers.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));        HttpEntity entity = new HttpEntity<>(headers);        ResponseEntity response = restTemplate.exchange("/api/doc/jason", HttpMethod.GET, entity, byte[].class);        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);    }}

Angular下载文档

可以使用链接直接访问REST URL下载文档,若项目启用了JWT Token验证,则必须使用HttpClient的get方法。
本文使用了FileSaver.js保存文档,开始之前先安装:

npm install --save file-saver

然后在tsconfig.json中添加:

"paths": {  "file-saver": [    "node_modules/file-saver/dist/FileSaver.js"  ]}

下载方法:

import * as fs from 'file-saver';downloadDocument() {  this.httpClient.get('yourUrl', {observe: 'response', responseType: 'blob'}).subscribe(response => {    fs.saveAs(response.body, this.getFilename(response.headers));  });}private getFilename(headers: HttpHeaders): string {  const disposition = headers.get('Content-Disposition');  if (!disposition || disposition.indexOf('filename=') < 0) {    return '';  }  return disposition.substr(disposition.indexOf('filename=') + 9);}

downloadDocument() {  this.httpClient.get('yourUrl', {responseType: 'blob'}).subscribe(data => {    fs.saveAs(data, 'yourFilename');  });}

参考文档

Excel File - Download from SpringBoot RestAPI + Apache POI + MySQL
Apache POI Word Tutorial

0