Java通过FreeMarker作为模板导出Excel

Posted Java_宇宁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java通过FreeMarker作为模板导出Excel相关的知识,希望对你有一定的参考价值。

导言

本文主要借助FreeMarker作为模板导出Excel

页面点击导出只需传入模板Id(templateId),即可实现导出组件

1. 定义Excel模板

img

2. 另存为xml文件

img

3. 项目下创建ftl文件

img

4. 格式化xml内容

img
img

5. 拷贝至ftl文件

img

6. 表设计(简化版)

CREATE TABLE `es_templates` (
  `template_id` bigint(20) NOT NULL COMMENT '主键',
  `template_name` varchar(255) DEFAULT NULL COMMENT '模板名',
  `template_usage` varchar(255) DEFAULT NULL COMMENT '模板用途',
  PRIMARY KEY (`template_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='导出模板(es_templates)';

模拟已插入一条模板数据

img

7. 导出条件实体类

简化版:假设条件就时间区间

import java.util.Date;

/**
 * @program: choice-goods
 * @description: 导出模板条件
 * @packagename: com.meritlink.choice.goods.bean.vo
 * @author: cxy
 * @date: 2021-07-14 11:02:50
 **/
public class TemplateExportVO {

    /**
     * 开始时间
     */
    private Date startTime;

    /**
     * 结束时间
     */
    private Date endTime;

    public TemplateExportVO() {
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }
}

8. 控制层

templateExport: 存在条件筛选导出,此处入参添加条件实体类(见第7步骤)

templateId: 假设前序工作已经添加模板,数据库(第6步骤)存在模板数据

此时页面触发导出,传入导出数据的条件以及选择的模板id

import com.meritlink.choice.goods.bean.vo.TemplateExportVO;
import com.meritlink.choice.goods.service.goods.ExportManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @program: choice-goods
 * @description: 导出控制层
 * @packagename: com.meritlink.choice.goods.api.manager.goods
 * @author: cxy
 * @date: 2021-07-12 10:28:42
 **/
@RestController
@RequestMapping("/admin/goods/export")
public class ExportManagerController {

    @Autowired
    private ExportManager exportManager;

    @PostMapping("/{templateId}")
    public void export(HttpServletRequest request,
                       HttpServletResponse response,
                       @RequestBody TemplateExportVO templateExport,
                       @PathVariable Long templateId) throws IOException {
        this.exportManager.createFreeMarker(request, response, templateExport, templateId);
    }
}

9. 接口

import com.meritlink.choice.goods.bean.vo.TemplateExportVO;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @program: choice-goods
 * @description: 导出
 * @packagename: com.meritlink.choice.goods.service.goods
 * @author: cxy
 * @date: 2021-07-12 10:41:14
 **/
public interface ExportManager {

    /**
     * 导出
     *
     * @param request
     * @param response
     * @param templateExport
     * @param templateId
     * @return
     */
    void createFreeMarker(HttpServletRequest request, HttpServletResponse response, TemplateExportVO templateExport, Long templateId) throws IOException;
}

10. 实现类

逻辑:

10-1: 根据templateId获取模板信息(此处省略查询数据库方法)

10-2: 获取templateName(后续会根据该参数获取ftl模板)以及templateUsage(后续会根据该参数获取渲染数据)

10-3: 获取数据

10-3-1: 通过工厂模式 + 策略模式获取数据

10-4: 创建excel

10-4-1: 根据模板名获取模板信息

10-4-2: 渲染数据

10-4-3: 将文件输出到response,返回给客户端

10-4-5: 关闭流以及删除临时文件
import com.meritlink.choice.framework.exception.ServiceException;
import com.meritlink.choice.goods.bean.dos.TemplatesDO;
import com.meritlink.choice.goods.bean.vo.TemplateExportVO;
import com.meritlink.choice.goods.common.code.GoodsErrorCode;
import com.meritlink.choice.goods.service.goods.ExportManager;
import com.meritlink.choice.goods.service.goods.TemplateManager;
import com.meritlink.choice.goods.service.strategy.templates.ITemplateStrategy;
import com.meritlink.choice.goods.service.strategy.templates.TemplateStrategyFactory;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang.StringUtils;
import org.apache.http.util.Asserts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: choice-goods
 * @description: 导出
 * @packagename: com.meritlink.choice.goods.service.goods.impl
 * @author: cxy
 * @date: 2021-07-12 10:41:37
 **/
@Service
public class ExportManagerImpl implements ExportManager {

    @Autowired
    private TemplateManager templateManager;

    @Autowired
    private TemplateStrategyFactory templateStrategyFactory;

    @Override
    public void createFreeMarker(HttpServletRequest request, HttpServletResponse response, TemplateExportVO templateExport, Long templateId) {

        try {
            // 根据模板id获取模板
            TemplatesDO template = getTemplateById(templateId);
            
            String templateName = template.getTemplateName();
            String templateUsage = template.getTemplateUsage();

            if (StringUtils.isBlank(templateName)) {
                throw new ServiceException(GoodsErrorCode.E301.code(), "模板名为空");
            }
            if (StringUtils.isBlank(templateUsage)) {
                throw new ServiceException(GoodsErrorCode.E301.code(), "模板用途为空");
            }

            Object templateData = templateStrategyFactory.getStrategy(templateUsage).getTemplateData(templateExport);

            Map<String, Object> map = new HashMap<>(16);
            map.put("items", templateData);

            // 调用创建excel
            this.createExcel(map, templateName, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private TemplatesDO getTemplateById(Long templateId) {
        TemplatesDO template = templateManager.getTemplateNameById(templateId);

        if (template == null) {
            throw new ServiceException(GoodsErrorCode.E301.code(), "模板信息异常,不存在");
        }

        return template;
    }

    /**
     * 创建excel
     *
     * @param dataMap
     * @param templateName
     * @param response
     * @return
     */
    private void createExcel(Map dataMap, String templateName, HttpServletResponse response) throws IOException {

        FileInputStream inputStream = null;
        ServletOutputStream outputStream = null;
        File file = null;
        try {
            // Excel名
            String excelName = templateName.split("[.]")[0] + System.currentTimeMillis() + ".xls";

            response.reset();
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(excelName, "UTF8"));
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/vnd.ms-excel;charset=utf-8");

            // 创建配置对象
            Configuration configuration = new Configuration(Configuration.getVersion());
            configuration.setDefaultEncoding("utf-8");
            // 设置模板路径
            configuration.setClassForTemplateLoading(this.getClass(), "/templates");
            // 构造输出流
            Template template = configuration.getTemplate(templateName, "UTF-8");

            file = new File(excelName);
            FileWriter out = new FileWriter(excelName);
            // 变量替换
            template.process(dataMap, out);

            // 将文件输出到response,返回给客户端
            inputStream = new FileInputStream(file);
            byte[] buffer = new byte[inputStream.available()];
            inputStream.read(buffer);
            inputStream.close();

            outputStream = response.getOutputStream();
            outputStream.write(buffer);
            outputStream.flush();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null)
                inputStream.close();
            if (outputStream != null)
                outputStream.close();
            if (file != null)
                // 删除临时文件
                file.delete();
        }
    }
}

10-1. 根据templateId获取模板信息

此处省略查询数据库方法,可以根据自己项目获取数据的方式。

10-2. 获取templateName和templateUsage

templateUsage是枚举类的getCode值,后续添加模板对应添加枚举值

/**
 * @program: choice-goods
 * @description: 模板用途
 * @packagename: com.meritlink.choice.goods.common.enums
 * @author: cxy
 * @date: 2021-07-14 10:25:46
 **/
public enum TemplateUsageEnum {

    GOODS("GOODS", "商品");

    private String code;
    private String desc;

    TemplateUsageEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

10-3. 获取渲染数据

我这边通过工厂模式 + 策略模式获取模板的渲染数据,后续就可以不用再去修改该类以及工厂,只需要添加策略即可完成模板的添加

10-3-1. 通过工厂模式 + 策略模式获取数据

工厂

重写 setApplicationContext() 方法获取 ITemplateStrategy 接口所有的实现类的 value.getStrategyKey() 方法动态将所有的策略加入 templatesMap

通过 getStrategy() 方法获取策略

import com.meritlink.choice.framework.exception.ResourceNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: choice-goods
 * @description: 导出模板工厂
 * @packagename: com.meritlink.choice.goods.service.strategy.templates
 * @author: cxy
 * @date: 2021-07-14 11:12:30
 **/
@Component
@Slf4j
public class TemplateStrategyFactory implements ApplicationContextAware {

    private static final Map<String, ITemplateStrategy> templatesMap = new HashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, ITemplateStrategy> map = applicationContext.getBeansOfType(ITemplateStrategy.class);
        map.forEach((key, value) -> templatesMap.put(value.getStrategyKey(), value));
    }

    public <T extends ITemplateStrategy> T getStrategy(String strategyKey) {
        ITemplateStrategy strategy = templatesMap.get(ITemplateStrategy.EXPORT_TEMPLATE_STRATEGY_KEY_PREFIX + strategyKey);
        if (strategy == null) {
            log.error("TemplateStrategyFactory.getStrategy is error, strategyKey is {}", strategyKey);
            throw new ResourceNotFoundException("TemplateStrategyFactory.getStrategy is error, strategyKey is " + strategyKey);
        }
        return (T) strategy;
    }
}
策略
  1. 定义通用方法,即获取策略的key
/**
 * @program: choice-goods
 * @description: 策略基础接口
 * @packagename: com.meritlink.choice.goods.service.strategy
 * @author: cxy
 * @date: 2021-07-14 10:48:27
 **/
public interface BaseStrategy {

    public final static String KEY_CONCAT = "_";

    String getStrategyKey();

}
  1. 定义策略接口
import com.meritlink.choice.goods.bean.vo.TemplateExportVO;
import com.meritlink.choice.goods.service.strategy.BaseStrategy;

/**
 * @program: choice-goods
 * @description: 导出模板策略
 * @packagename: com.meritlink.choice.goods.service.strategy.templates
 * @author: cxy
 * @date: 2021-07-14 10:53:21
 **/
public interface ITemplateStrategy extends BaseStrategy {


    public final static String EXPORT_TEMPLATE_STRATEGY_KEY_PREFIX = ITemplateStrategy.class.getSimpleName() + KEY_CONCAT;

    /**
     * 获取模板数据
     *
     * @param templateExport
     * @return
     */
    Object getTemplateData(TemplateExportVO templateExport);
}
  1. 添加策略的实现
import com.meritlink.choice.goods.bean.dos.GoodsDO;
import com.meritlink.choice.goods.bean.vo.TemplateExportVO;
import com.meritlink.choice.goods.common.enums.TemplateUsageEnum;
import com.meritlink.choice以上是关于Java通过FreeMarker作为模板导出Excel的主要内容,如果未能解决你的问题,请参考以下文章

java freemarker 通过ftl模板导出word文档

java freemarker模板 实现word文件导出

java freemarker模板 实现word文件导出

java freemarker 实现word文件导出

java freemarker 实现word文件导出

Java用freemarker导出word