前提:需要使用easypoi根据Excel模板导出复杂Excel表,表中有合并单元格,图片等内容。

效果图

在这里插入图片描述

maven依赖

建议就用我这个版本的,因为这个版本解决图片不显示的bug。
参考:
使用esaypoi模板导出图片不显示问题解决

<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>4.2.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-web</artifactId>
    <version>4.2.0</version>
</dependency>
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-annotation</artifactId>
    <version>4.2.0</version>
</dependency>

Excel模版图片

在这里插入图片描述
模板内的标签,参考
3.1 模板 指令介绍
模板是处理复杂Excel的简单方法,复杂的Excel样式,可以用Excel直接编辑,完美的避开了代码编写样式的雷区,同时指令的支持,也提了模板的有效性 下面列举下EasyPoi支持的指令以及作用,最主要的就是各种fe的用法

空格分割
三目运算 {{test ? obj:obj2}}
n: 表示 这个cell是数值类型 {{n:}}
le: 代表长度{{le:()}} 在if/else 运用{{le:() > 8 ? obj1 : obj2}}
fd: 格式化时间 {{fd:(obj;yyyy-MM-dd)}}
fn: 格式化数字 {{fn:(obj;###.00)}}
fe: 遍历数据,创建row
!fe: 遍历数据不创建row
$fe: 下移插入,把当前行,下面的行全部下移.size()行,然后插入
#fe: 横向遍历
v_fe: 横向遍历值
!if: 删除当前列 {{!if:(test)}}
单引号表示常量值 ‘’ 比如’1’ 那么输出的就是 1
&NULL& 空格
]] 换行符 多行遍历导出
sum: 统计数据
整体风格和el表达式类似,大家应该也比较熟悉 采用的写法是{{}}代表表达式,然后根据表达式里面的数据取值

关于样式问题 easypoi不会改变excel原有的样式,如果是遍历,easypoi会根据模板的那一行样式进行复制

模板放在resouce下

src/main/resources/template/report.xlsx
在这里插入图片描述

Java代码

//报表导出
@Override
public void exportReport(Integer taskRunId, HttpServletResponse response) throws IOException {
    if (null == taskRunId) {
        throw new BusinessException("任务id不能为空");
    }
    TaskRun taskRun = getById(taskRunId);
    List<PointResult> pointResultList = pointResultService.listByTaskRunId(taskRunId);
    // 创建导出参数
    POICacheManager.setFileLoaderOnce(new FileLoaderImpl());//自己的实现类,解决读取模板文件报错问题
    TemplateExportParams exportParams = new TemplateExportParams("template/report.xlsx");

    Map<String, Object> map = new HashMap<>();
    map.put("taskCode", taskRun.getTaskCode());
    map.put("taskName", taskRun.getTaskName());
    map.put("startTime", DateUtil.getDateStr(taskRun.getStartTime()));
    map.put("endTime", DateUtil.getDateStr(taskRun.getEndTime()));
    map.put("pointCount", taskRun.getPointCount());
    map.put("normalCount", taskRun.getNormalCount());
    map.put("failCount", taskRun.getFailCount());
    map.put("beatOffCount", taskRun.getBeatOffCount());
    map.put("beatOffSuccessCount", taskRun.getBeatOffSuccessCount());
    map.put("beatOffFailCount", taskRun.getBeatOffFailCount());
    map.put("virtualFocalCount", taskRun.getVirtualFocalCount());
    map.put("virtualFocalSuccessCount", taskRun.getVirtualFocalSuccessCount());
    map.put("virtualFocalFailCount", taskRun.getVirtualFocalFailCount());
    //封装点位结果数据
    List<Map<String, Object>> listMap = getMaps(pointResultList);
    map.put("maplist", listMap);
    Workbook workbook = ExcelExportUtil.exportExcel(exportParams, map);

    // 设置响应头
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("Content-Disposition", "attachment; filename=报告信息模板.xlsx");

    // 写入响应流
    workbook.write(response.getOutputStream());
    response.getOutputStream().flush();
    response.getOutputStream().close();


}

private List<Map<String, Object>> getMaps(List<PointResult> pointResultList) {
    List<Map<String, Object>> listMap = new ArrayList<>();
    for (int i = 0; i < pointResultList.size(); i++) {
        PointResult pointResult = pointResultList.get(i);
        Map<String, Object> lm = new HashMap<>();
        lm.put("id", i + 1 + "");
        lm.put("pointName", pointResult.getPointName());
        lm.put("meterType", pointResult.getPointType());
        lm.put("beatOff", pointResult.getBeatOff());
        lm.put("virtualFocal", pointResult.getVirtualFocal());

        String presetOnePicture = pointResult.getPresetOnePicture();
        String presetTwoPicture = pointResult.getPresetTwoPicture();
        String url1 = getImageUrl(presetOnePicture);
        String url2 = getImageUrl(presetTwoPicture);
        ImageEntity image1 = fixImageEntity(url1);
        ImageEntity image2 = fixImageEntity(url2);
        lm.put("presetOnePicture", image1);
        lm.put("presetTwoPicture", image2);
        listMap.add(lm);
    }
    return listMap;
}

private static ImageEntity fixImageEntity(String url) {
    ImageEntity image = new ImageEntity();
    image.setHeight(200);
    image.setWidth(200);
    image.setType(ImageEntity.Data);
    byte[] bytes = new byte[]{};
    File file = new File(url);
    try (FileInputStream fis = new FileInputStream(file);
         ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = fis.read(buffer)) != -1) {
            baos.write(buffer, 0, bytesRead);
        }
        bytes = baos.toByteArray();
    } catch (Exception e) {
        log.error(e.getMessage());
    }
    image.setData(bytes);
    return image;
}

/**
 * 原始图片地址
 * @param originalUrl 原始图片地址
 * @return
 */
private String getImageUrl( String originalUrl) {
    String pathPre = "BoxImages/";
    String substring = originalUrl.substring(originalUrl.lastIndexOf(pathPre) + pathPre.length());
    String url = fileSavePath + substring;
    url = url.replace('/', File.separatorChar);
    return url;
}
自己实现IFileLoader 接口,解决读取文件失败报错问题

参考文章Easypoi教程

import cn.afterturn.easypoi.cache.manager.IFileLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 自己实现IFileLoader接口,解决easypoi读取文件报错问题,
 * 使用时手动设置POICacheManager.setFileLoaderOnce(new FileLoaderImpl());
 * @author : lpx
 * @since : 2024/9/26 星期四 19:31
 * @Description : 
 */
@Slf4j
public class FileLoaderImpl implements IFileLoader {
    @Override
    public byte[] getFile(String url) {
        InputStream fileis = null;
        ByteArrayOutputStream baos = null;
        try {

            //判断是否是网络地址
            if (url.startsWith("http")) {
                URL urlObj = new URL(url);
                URLConnection urlConnection = urlObj.openConnection();
                urlConnection.setConnectTimeout(30);
                urlConnection.setReadTimeout(60);
                urlConnection.setDoInput(true);
                fileis = urlConnection.getInputStream();
            } else {
//                //先用绝对路径查询,再查询相对路径
                Path path = Paths.get(url);
                if (Files.exists(path)) {
                    fileis = new FileInputStream(path.toFile());
                } else {
                    // 再查询相对路径
                    fileis = getClass().getClassLoader().getResourceAsStream(url);
                    if (fileis == null) {
                        throw new FileNotFoundException("File not found: " + url);
                    }
                }
            }

            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fileis.read(buffer)) > -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            IOUtils.closeQuietly(fileis);
            IOUtils.closeQuietly(baos);
        }
        log.error(fileis + "这个路径文件没有找到,请查询");
        return null;
    }


}

ps:easypoi官方文档已经不开放了,好像没人维护和更新代码了!!!

看到这里,点个关注吧。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐