JAVA cooperates with Freemark template to generate word return file stream, and vue at the front desk receives download

The idea of ​​Freemark template to generate word is similar to that of POI, but the function of Freemark is more powerful than POI.

For POI, please refer to the previous article: https://blog.csdn.net/CarryBest/article/details/94630824

One: First make a template

   Create a new word template and create the required format in it. You can directly write the variable name, and then save it as xml, open xml, and click on Baidu to format the data in xml format, and then see if your variables are messed up. If it is messed up, Change it back manually.

The most basic is this usage, you can judge the variable as Null, and other processing

If you want to loop a certain variable, you can use <#list connmlist as connms>. . . . </#list>In this type of writing, connmlist is a variable of your java background, connms. connm means that your java background data format is like this: the corresponding color helps you mark it

connmMap.put("connm", wqDataEditsData.getConnm());
connmList.add(connmMap);

resultMap.put("connmlist", connmList);

 

Two: Create a new public method DocumentHandler.java

package com.XX.XXXX.documentHandler;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Map;

/**
 * 类描述:
 *
 * @author :carry
 * @version: 1.0  CreatedDate in  2019年10月12日
 * <p>
 * 修订历史: 日期			修订者		修订描述
 */
public class DocumentHandler {
    private Configuration configuration = null;

    public DocumentHandler() {
        configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
    }

    public void createDoc( Map<String ,Object > result,String fileName,String inPath,String outPath) throws Exception {
        configuration.setDefaultEncoding("utf-8");
        //模板方在项目的resource下的template里面
       // configuration.setClassForTemplateLoading(this.getClass(), "/template");
        //模板放在项目的盘符下
        configuration.setDirectoryForTemplateLoading(new File(inPath));
        System.out.println("文件路径"+outPath);
        Template t = null;
        try {
            t = configuration.getTemplate(fileName,"utf-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        File outFile = new File(outPath);
        Writer out = null;
        try {
              out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            t.process(result, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @方法描述:
     * path 下载的文件路径  projectname  这里意义不大 直接被vue前台替换了   suffix后缀名  这里意义不大 直接被vue前台替换了
     * @return: javax.servlet.http.HttpServletResponse
     * @Author: carry
     */
    public HttpServletResponse download(String downloadPath, HttpServletResponse response) throws Exception {
        // 以流的形式下载文件。
        InputStream fis = new BufferedInputStream(new FileInputStream(downloadPath));
        byte[] buffer = new byte[fis.available()];
        fis.read(buffer);
        fis.close();
        response.reset();
        // 设置response的Header  这里意义不大 直接被vue前台替换了 所以可以删掉
        response.addHeader("Content-Disposition", "attachment;filename="
                +"test"+ new String(("" + ".docx").getBytes("gbk"),
                "iso-8859-1"));
        OutputStream toClient = new BufferedOutputStream(
                response.getOutputStream());
        //注意这里很重要 vue的回调里根据ContentType类判断是否是下载的操作,这里不要改
        response.setContentType("application/octet-stream");
        toClient.write(buffer);
        toClient.flush();
        this.close(fis);
        this.close(toClient);
        return response;
    }


    /**
     *  关闭输入流
     * @param is
     */
    private void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *  关闭输出流
     * @param os
     */
    private void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Three: call class:

// 数据库里的数据与xml映射起来getLogsData这个方法就省略了,意思很清楚,就是你xml里有叫${name}的,那么java的map里也要响应的放一个键值对的键叫name的。
 Map<String, Object> resultMap = this.getLogsData(params);
 /**
     * @方法描述: 下载导出整编数据
     * @return: void
     * @Author: carry
     */
    @GetMapping("/exportToWordByFmk")
    public void exportToWordByFmk(HttpServletRequest request,
                                  HttpServletResponse response,
                                  @RequestParam(value = "tm", required = true) String tm) {
        try {
            Map<String, String> params = new HashMap<String, String>();
            params.put("tm", tm);

            String path = ResourceUtils.getURL("classpath:").getPath();
            //项目获取盘符
            String root =path.substring(1,3);
            //创建生成的目录
            File outPathFile = new File(root+"\\jddsFile\\docResults");
            if (!outPathFile.exists()) {
                outPathFile.mkdirs();
            }
            //创建读取模板的目录
            File inPathFile = new File(root+"\\jddsFile\\docTemplate");
            if (!inPathFile.exists()) {
                inPathFile.mkdirs();
            }
           //模板名称
            String fileName = "WQDataEditsDataTmpFmk.xml";
            //输出模板名字,java生成的文件名字,前台会被vue替换掉
            String outPath =outPathFile.getAbsolutePath()+"\\WQDataEditsData.docx";
            String inPath =inPathFile.getAbsolutePath();
           // 讲数据库里的数据与xml映射起来
            Map<String, Object> resultMap = this.getLogsData(params);

            DocumentHandler dh = new DocumentHandler();
            dh.createDoc(resultMap, fileName,inPath, outPath);
            dh.download(outPath, response);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

Four: call of vue

   downloadFiles(row) {
                let params = {};
               
                this.$api.exportToWordByFmk(params).then(res => {
                    if (res.code == "0") {
                        let data = res.result.data;
                        let blob = new Blob([data], {
                            //type   mimi类型  可以百度详细类型
                            //word文档为msword,application/pdfpdf文档为pdf  application/vnd.ms-excel
                            //image/jpeg jpg
                          //  type: `msword`
                            type: `application/msword`
                        });
                        let objectUrl = URL.createObjectURL(blob);
                        let link = document.createElement("a");
                        let fname = "测试";
                        link.href = objectUrl;
                        link.setAttribute("download", fname);
                        document.body.appendChild(link);
                        link.click();
                    } else {
                        this.$message.error(res.msg);
                    }
                });
            },

Five: the method of encapsulated vue receiving file stream

 //Here, if the backend returns in the form of a file stream, the custom return content spliced ​​on the response file stream

            let contentType=headers['content-type'];

            if(contentType=="application/octet-stream"){

                let resultData=data;

                stdRs={"code":0,"msg":"操作成功","result":{ "data":resultData}}

            }

  //核心请求函数,并做拦截处理。
    // config: {url,method,params,data,timeout,headers,message,loading}
    // resp: {data:{},status:200,statusText:'OK',headers:{},config:{},request:{}}
    request(config) {
        this.onStart(config);
        return this.http.request(config).then((res) => {
            let {data, headers, request, status, statusText} = res;
            let stdRs = null;
            //此处如果后台返回的是文件流的形式,则自定义返回内容拼接上response的文件流
            let contentType=headers['content-type'];
            if(contentType=="application/octet-stream"){
                let resultData=data;
                stdRs={"code":0,"msg":"操作成功","result":{ "data":resultData}}
            }else{
                try {
                    stdRs = JSON.parse(data);
                } catch (e) {
                    stdRs = this.stdRs(status + '', statusText, data);
                }
               
            }

            this.onComplete(config, stdRs);
            return this.resultSuccess(stdRs) ? Promise.resolve(stdRs) : Promise.reject(stdRs);
        }).catch((err) => {
            if (!err.response && !err.request) {
                return Promise.reject(err);
            }
            let {request, response} = err;
            let {status, statusText} = response;
            let stdRs = this.stdRs(status + '', statusText, null);
            this.onComplete(config, stdRs);
            return Promise.reject(stdRs);
        });
    }
 //导出文件
    export(config) {
        if (config.data) {
            config.responseType="blob";
            config.params = {...config.params, ...config.data};
            if(config.data.params != undefined){
                config.params={ ...config.params,...config.data.params}
            }

            delete config.data;
        }
        return this.request(config);
    }

 

Guess you like

Origin blog.csdn.net/CarryBest/article/details/102586653