SpringBoot Poi导出word,浏览器下载

SpringBoot Poi导出word,浏览器下载

核心问题:

问题就是axios发的请求,浏览器不能直接下载文件

关键点以标红

先看下导出的结果:

image-20211020140828713

非常简单的样式,下面开始介绍如何使用:

我这个例子是,问题+答案的word

1.引依赖:

<!--导出word-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.17</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.17</version>
</dependency>

2.写代码(生成本地word):

//创建一个word对象
XWPFDocument document = new XWPFDocument();
//创建一个段落(标题)
XWPFParagraph titleParagraph = document.createParagraph();
//将该段落居中显示
titleParagraph.setAlignment(ParagraphAlignment.CENTER);
//XWPFRun是文本对象!!!!!!
XWPFRun title = titleParagraph.createRun();
//设置标题
title.setText("test");
//设置标题加粗
title.setBold(true);
//标题字体大小
title.setFontSize(20);
//换行,同样创建一个段落,再为这个段落创建文本对象,其内容为换行符
XWPFParagraph paragraph3 = document.createParagraph();
XWPFRun paragraphRun3 = paragraph3.createRun();
paragraphRun3.setText("\r");
//创建一个段落,即问题行
XWPFParagraph questionParagraph = document.createParagraph();
XWPFRun questionRun = questionParagraph.createRun();
//设置问题的内容
questionRun.setText((++num)+"."+question.getQuestion());
//问题字体加粗
questionRun.setBold(true);
//问题字体大小
questionRun.setFontSize(12);
//创建一个答案段
XWPFParagraph answerParagraph = document.createParagraph();
//答案我们需要首航缩进,传一个int,480大概就中文两个字,
//578还是多少,就等于1cm,大家可自行调整
//下面注释的那个方法是一样的,setFirstLineIndent会调用setIndentationFirstLine
answerParagraph.setIndentationFirstLine(480);
//answerParagraph.setFirstLineIndent(400);
XWPFRun answerRun = answerParagraph.createRun();
answerRun.setText(question.getAnswer());
answerRun.setFontSize(12);
//一问一答后,换行
XWPFParagraph paragraph2 = document.createParagraph();
XWPFRun paragraphRun2 = paragraph2.createRun();
paragraphRun2.setText("\r");
//将问题和答案段落的创建放进循环
//用数据库的内容填充setText即可

//现在生成word到本地
//先创建一个文件对象,把你要放的路劲丢进去
//文件夹不存在就创建该目录
File file = new File("D:/word");
if (!file.exists()){
    file.mkdirs();
}

//创建文件输出流,异常自行捕获、上抛一下,这里就省去
//需要传入你要生成的文件完整路径
FileOutputStream out = new FileOutputStream("D:/word/test.docx");
//用word对象的write方法写入目标文件
document.write(out);
out.close();
document.close();

3.返回给浏览器下载

需要提前告知的是,以下出现的问题及解决方案,均是我在

vue+axios+springboot,前后端分离环境中发生的

问题就是axios发的请求,浏览器不能直接下载文件

ok,开始。

🌮taco🌮taco🌮taco🌮taco:

1.如何返回给浏览器让它下载

我们需要在响应头中添加一些东西,

下面贴一下,我疯狂测试后比较行的,两句:

response.setHeader("Content-Disposition", "attachment; filename="+userId+".docx");

这个Content-Disposition是干什么的呢?

使文件直接在浏览器上显示或者在访问时弹出文件下载对话框(我们这就是让浏览器下载)

image-20211020144249150

response.setHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8");


response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8");

这两句都是等价的,设置content-type就是告诉浏览器这是啥玩意儿,

application/vnd.openxmlformats-officedocument.wordprocessingml.document

这一句是docx后缀的word,

application/msword,这是doc后缀的word

根据你要返回的文档类型不同,你需要设置的MIME类型就不同,这是从别人家找到的常用的:

.doc      application/msword
.dot      application/msword
 
.docx     application/vnd.openxmlformats-officedocument.wordprocessingml.document
.dotx     application/vnd.openxmlformats-officedocument.wordprocessingml.template
.docm     application/vnd.ms-word.document.macroEnabled.12
.dotm     application/vnd.ms-word.template.macroEnabled.12
 
.xls      application/vnd.ms-excel
.xlt      application/vnd.ms-excel
.xla      application/vnd.ms-excel
 
.xlsx     application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xltx     application/vnd.openxmlformats-officedocument.spreadsheetml.template
.xlsm     application/vnd.ms-excel.sheet.macroEnabled.12
.xltm     application/vnd.ms-excel.template.macroEnabled.12
.xlam     application/vnd.ms-excel.addin.macroEnabled.12
.xlsb     application/vnd.ms-excel.sheet.binary.macroEnabled.12
 
.ppt      application/vnd.ms-powerpoint
.pot      application/vnd.ms-powerpoint
.pps      application/vnd.ms-powerpoint
.ppa      application/vnd.ms-powerpoint
 
.pptx     application/vnd.openxmlformats-officedocument.presentationml.presentation
.potx     application/vnd.openxmlformats-officedocument.presentationml.template
.ppsx     application/vnd.openxmlformats-officedocument.presentationml.slideshow
.ppam     application/vnd.ms-powerpoint.addin.macroEnabled.12
.pptm     application/vnd.ms-powerpoint.presentation.macroEnabled.12
.potm     application/vnd.ms-powerpoint.template.macroEnabled.12
.ppsm     application/vnd.ms-powerpoint.slideshow.macroEnabled.12
 
.mdb      application/vnd.ms-access

大家对号入座吧,

顺便说一说我在网上看到的一些其它的请求头设置是干嘛的

//通用的MIME类型,重点----通用
//二进制流
response.setHeader("content-type", "application/octet-stream");

然后用响应流将word对象写回浏览器:

OutputStream outputStream = response.getOutputStream();
document.write(outputStream);

完整的返回代码:(有点长,但就是前面的内容,加上填充的数据)

String userId = (String) dataMap.get("userId");
List<Integer> typeIds = (List<Integer>) dataMap.get("typeIds");
//创建一个word对象
XWPFDocument document = new XWPFDocument();

for (Integer typeId : typeIds) {
    int num = 0;
    List<QuestionVo> questions = exportMapper.getExportQuestionsByType(userId, typeId);
    Type one = new LambdaQueryChainWrapper<>(typeService.getBaseMapper())
        .select(Type::getTypeName)
        .eq(Type::getId, typeId)
        .one();

    //创建一个段落
    XWPFParagraph titleParagraph = document.createParagraph();
    //将其居中成为标题段
    titleParagraph.setAlignment(ParagraphAlignment.CENTER);
    //创建一个文本对象
    XWPFRun title = titleParagraph.createRun();
    //设置标题
    title.setText(one.getTypeName());
    //设置标题加粗
    title.setBold(true);
    //标题字体大小
    title.setFontSize(20);
    //换行
    XWPFParagraph paragraph3 = document.createParagraph();
    XWPFRun paragraphRun3 = paragraph3.createRun();
    paragraphRun3.setText("\r");

    for (QuestionVo question : questions) {
        XWPFParagraph questionParagraph = document.createParagraph();
        XWPFRun questionRun = questionParagraph.createRun();
        //设置问题段
        questionRun.setText((++num)+"."+question.getQuestion());
        //问题加粗
        questionRun.setBold(true);
        //标题字体大小
        questionRun.setFontSize(12);


        //设置答案段
        XWPFParagraph answerParagraph = document.createParagraph();
        answerParagraph.setIndentationFirstLine(480);
        //answerParagraph.setFirstLineIndent(400);
        XWPFRun answerRun = answerParagraph.createRun();
        answerRun.setText(question.getAnswer());
        answerRun.setFontSize(12);


        //换行
        XWPFParagraph paragraph2 = document.createParagraph();
        XWPFRun paragraphRun2 = paragraph2.createRun();
        paragraphRun2.setText("\r");
    }

}
File file = new File("D:/word");
if (!file.exists()){
    file.mkdirs();
}
try {
    //禁用缓存
    response.setHeader(HttpHeaders.PRAGMA, "No-cache");
    response.setHeader(HttpHeaders.CACHE_CONTROL, "No-cache");
    response.setHeader("Content-Disposition", "attachment; filename="+userId+".docx");
    response.setHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8");
    //response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8");
    OutputStream outputStream = response.getOutputStream();
    document.write(outputStream);
    outputStream.close();
    document.close();
}catch (Exception e){
    e.printStackTrace();
}

下面就可以返回给前端了(也是我最大的问题所在)

页面点击导出浏览器无动于衷,来来回回试了很多修改方案都不行,

我是真的想多长几根头发,折磨~~~~ 🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫🐫

后面我就写了一个测试controller,关闭拦截器,在浏览器直接访问该接口

image-20211020150445214

响应头的效果如下(火狐浏览器):

image-20211020150619238

image-20211020150551281

完全没问题,一气呵成,差点没跳到房顶上去

接着把前端导出的请求改为/test,理所当然,浏览器毫无响应

axios的问题?应该是的

4.解决方案

苦思无果,我决定采用另一种办法,js模拟下载,就是axios拿到响应的数据

链接如下:

https://blog.csdn.net/xiaobing_hope/article/details/103363513

大概长这样:

image-20211020151207843

这并不是乱码!

响应的数据在res.data,

我们只需要这样设置axios请求:

image-20211020151606808

添加响应类型,设置为 blob

然后响应回来后,取到word数据,生成下载链接,js模拟点击下载即可

模拟点击下载代码:

const data = res.data
let url = window.URL.createObjectURL(data)   // 将二进制文件转化为可访问的url
var a = document.createElement('a')  
document.body.appendChild(a)
a.href = url
a.download = sessionStorage.getItem("userName")+'.docx'   //文件名
a.click()   // 模拟点击下载
window.URL.revokeObjectURL(url)    //清除

ok!

image-20211020152741505

5.为什么没使用easypoi 模板导出word

最开始使用的就是这个,但是我发现,循环输出数据库的内容到word里面,最后只显示最后一条数据,因为之前的都被覆盖了。

之后看视频找到了一个解决方案:就是将循环的内容拼接起来,传给一个模板占位符。拼接的时候加上换行符即可。但是我一想,我还需要设置首行缩进、设置一些格式,这样做只会更难,而且这样简单的word使用poi也不难

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了我的架构师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值