我想养只狗

博客园 首页 联系 订阅 管理

需求:根据某word文档,开发生成word的功能,文档内容不不固定,取自数据库,格式基本固定,提供两个接口,一个生成.docx文件,另一个生成.wps文件。

思路:springboot框架,根据原文件制作模板,使用pot-tl做数据渲染,并导出。

1、源文件、模板文件,如下

image

image

2、代码实现

pom.xml

        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.29</version>
        </dependency>
        <dependency>
            <groupId>fr.opensagres.xdocreport</groupId>
            <artifactId>fr.opensagres.xdocreport.document</artifactId>
            <version>2.0.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlbeans</groupId>
            <artifactId>xmlbeans</artifactId>
            <version>5.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

TemplateDocumentService

public class TemplateDocumentService {

    public XWPFTemplate generateDocument(ReportData data) throws IOException {
        // 1. 加载模板
        Resource templateResource = new ClassPathResource("templates/tqnmgREPORT.docx");
        XWPFTemplate template = null;
        try (InputStream inputStream = templateResource.getInputStream()) {
            template = XWPFTemplate.compile(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 2. 构建数据模型
        Map<String, Object> dataModel = BeanUtil.beanToMap(data);

        // 3. 渲染文档(保留模板样式)
        return template.render(dataModel);
    }

    // 文件下载接口(支持.docx和.wps)
    public ResponseEntity<Resource> exportDocument(String format, ReportData data) throws IOException {
        XWPFTemplate template = generateDocument(data);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        template.write(out);
        template.close();

        String filename = "拖欠农民工报告." + format;
        MediaType contentType = format.equals("docx") ?
                MediaType.APPLICATION_OCTET_STREAM :
                MediaType.parseMediaType("application/wps-office.wps");

        return ResponseEntity.ok()
                .contentType(contentType)
                .header("Content-Disposition", "attachment; filename=" + filename)
                .body(new ByteArrayResource(out.toByteArray()));
    }
}

DocumentController

@RestController
@RequestMapping("/document")
public class DocumentController {

    private final String JSONSTR = "{\n" +
            "    \"title\": \"关于分析...报告\",\n" +
            "    \"regulations\": \"按照行了汇...总分析。\",\n" +
            "    \"wageArrears\": {\n" +
            "        \"title\": \"本次成整...改。\",\n" +
            "        \"info\": \"其中,聚铁...四局。\",\n" +
            "        \"arrearsDetailList\": [\n" +
            "            {\n" +
            "                \"title\": \"(一)按建设单位及项目划分\",\n" +
            "                \"info\": \"\",\n" +
            "                \"detailList\": [\n" +
            "                       \"1.....00万元;\"," +
            "                       \"2. .....万元;\"," +
            "                       \"3. .....94万元;\"," +
            "                       \"4. .....万元;\"," +
            "                       \"5. .....元;\"," +
            "                       \"6. .....万元;\"," +
            "                       \"7. .....万元;\"," +
            "                       \"8......元。\"," +
            "                       \"9. .....万元。\"" +
            "                ]\n" +
            "            },\n" +
            "            {\n" +
            "                \"title\": \"(二)......公司划分\",\n" +
            "                \"info\": \"\",\n" +
            "                \"detailList\": [\n" +
            "                       \".......元。\"" +
            "                ]\n" +
            "            },\n" +
            "            {\n" +
            "                \"title\": \"(三)按.....分\",\n" +
            "                \"info\": \"\",\n" +
            "                \"detailList\": [\n" +
            "                       \"中国.....元。\"," +
            "                       \"中国.....万元。\"" +
            "                ]\n" +
            "            }\n" +
            "        ]\n" +
            "    },\n" +
            "    \"comparisonContent\": {\n" +
            "        \"title\": \"\",\n" +
            "        \"info\": \"\",\n" +
            "        \"detailList\": [\n" +
            "               \"一是.....4%。\"," +
            "               \"二是.....薪事件。\"," +
            "               \"三是.....现象。\"" +
            "        ]\n" +
            "    },\n" +
            "    \"causeAnalysisContent\": {\n" +
            "        \"title\": \"\",\n" +
            "        \"info\": \"主要是....原因。\",\n" +
            "        \"detailList\": [\n" +
            "               \"一是....万元。\"," +
            "               \"二是.....万元。\"," +
            "               \"三是.....万元。\"," +
            "               \"四是.....元。\"," +
            "               \"五是.....元。\"," +
            "               \"六是.....元。\"" +
            "        ]\n" +
            "    },\n" +
            "    \"measuresContent\": {\n" +
            "        \"title\": \"\",\n" +
            "        \"info\": \"\",\n" +
            "        \"detailList\": [\n" +
            "            \"1......情。\"," +
            "            \"2.....中心。 \"," +
            "            \"3.中心....关注。\"," +
            "            \"4.对于....约谈。\"" +
            "        ]\n" +
            "    },\n" +
            "    \"department\": \"工....中心\",\n" +
            "    \"reportDate\": \"2029年2月27日\"\n" +
            "}";

    @GetMapping("test")
    public String test() {
        return "test-ok";
    }

    @Autowired
    private TemplateDocumentService documentService;

    @PostMapping("/download-docx")
    public ResponseEntity<Resource> downloadDocx() throws IOException {
        //JSONSTR 数据,该数据要从库中查询
        ReportData bean = JSONUtil.toBean(JSONSTR, ReportData.class);

        return buildResponse(bean, "docx");
    }

    @PostMapping("/download-wps")
    public ResponseEntity<Resource> downloadWps() throws IOException {

        ReportData bean = JSONUtil.toBean(JSONSTR, ReportData.class);

        return buildResponse(bean, "wps");
    }

    private ResponseEntity<Resource> buildResponse(ReportData data, String format) throws IOException {
        // 生成文档
        XWPFTemplate template = documentService.generateDocument(data);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        template.write(out);
        template.close();

        // 设置中文文件名(核心修改点)
        String rawFileName = "拖欠农民工报告." + format;
        String encodedFileName = URLEncoder.encode(rawFileName, "UTF-8")
                .replaceAll("\\+", "%20");  // 替换空格编码[1](@ref)[5](@ref)

        // 设置响应头(兼容多浏览器)
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition",
                "attachment; filename*=UTF-8''" + encodedFileName);  // RFC 5987标准[6](@ref)[8](@ref)

        // 根据格式设置Content-Type
        MediaType contentType = format.equals("docx") ?
                MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document") :
                MediaType.parseMediaType("application/wps-office.wps");

        return ResponseEntity.ok()
                .headers(headers)
                .contentType(contentType)
                .body(new ByteArrayResource(out.toByteArray()));
    }
}

entity

@Data
public class DetailInfo {
    private String title;
    private String info;
    private List<String> detailList;
}
@Data
public class ReportData {
    private String title;                      // 动态标题
    private String regulations;      // 法规描述段落
    private WageArrears wageArrears; //欠薪情况
    private DetailInfo comparisonContent;           // 同期对比情况内容
    private DetailInfo causeAnalysisContent;         // 欠薪...内容
    private DetailInfo measuresContent;              // 建议...内容
    private String department;                 // 工...心(动态)
    private String reportDate;                 // 报告日期(动态)
}
@Data
public class WageArrears {
    private String title;
    private String info;
    private List<DetailInfo> arrearsDetailList;
}

代码可直接运行,导出文件内容格式与原文件一致。唯一缺陷,内容部分有些字是加粗的,没有规律,解决此问题只能在poi-tl渲染数据之前,手动设置处理个别字加粗

有问题请参照Poi-tl官方文档

[https://deepoove.com/poi-tl/#_why_poi_tl](https://deepoove.com/poi-tl/#_why_poi_tl)
posted on 2025-11-20 17:35  我想养只狗  阅读(36)  评论(0)    收藏  举报