easyExcel中使用String类型去接收Date日期类型问题


在easyExcel导出表格过程中,错误的使用了String类型去接收日期类型,最后导致日期值转换错误.

1 问题复现

1 导出类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Phone {
    
    @Excel(name = "商品id", width = 15, orderNum = "10")
    private int id;

    @Excel(name = "更新时间", format = "yyyy-MM-dd HH:mm:ss", width = 25, orderNum = "20")
    private Date updateTime;

    // 本应该使用Date,此处错误使用String
    @Excel(name = "创建时间", format = "yyyy-MM-dd HH:mm:ss" , width = 25, orderNum = "30")
    private String createTime;
}

2 导出工具类

public class ExcelUtils {
    /**
     * excel 导出
     *
     * @param list      数据
     * @param title     标题
     * @param sheetName sheet名称
     * @param pojoClass pojo类型
     * @param fileName  文件名称
     * @param response
     */
    public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) throws IOException {
        defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF));
    }

    /**
     * 默认的 excel 导出
     *
     * @param list         数据
     * @param pojoClass    pojo类型
     * @param fileName     文件名称
     * @param response
     * @param exportParams 导出参数
     */
    private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException {
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
        downLoadExcel(fileName, response, workbook);
    }

    /**
     * 下载
     *
     * @param fileName 文件名称
     * @param response
     * @param workbook excel数据
     */
    private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
        try {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("content-Type", "application/vnd.ms-excel");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + "." + ExcelTypeEnum.XLSX.getValue(), "UTF-8"));
            workbook.write(response.getOutputStream());
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Excel 类型枚举
     */
    enum ExcelTypeEnum {
        XLS("xls"), XLSX("xlsx");
        private String value;

        ExcelTypeEnum(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

}

3 导出控制类

    @RequestMapping(value = "/export")
    public void exportExcel(HttpServletResponse response) throws IOException {
        // 获取当前日期
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(date);

        // 模拟数据,真实场景从数据库获取
        List<Phone> phoneDTOList = new ArrayList<>();
        phoneDTOList.add(new Phone(6, date, format));
        phoneDTOList.add(new Phone(10, date, format));
        phoneDTOList.add(new Phone(20, date, format));

		// 导出数据
        ExcelUtils.exportExcel(phoneDTOList, "手机信息表", "手机信息", Phone.class, "手机信息", response);
    }

4 导出结果

商品id更新时间创建时间
62023-06-12 23:27:482022-12-05 23:02:23
102023-06-12 23:27:482022-12-05 23:02:23
202023-06-12 23:27:482022-12-05 23:02:23

从上面的导出结果可以得知,在Excel生成过程中,日期类型,被有规律的修改过,导致创建时间和更新时间不相等.

2 问题排查

从上述分析可知,easyExcel生成表格过程中,单元格的赋值转换过程中,一定不同的判断.OK, 我们跟着代码一起冲!

1 入口代码, 即我们调用工具类,并传入集合数据.

        ExcelUtils.exportExcel(phoneDTOList, "手机信息表", "手机信息", Phone.class, "手机信息", response);

2 在下面方法时,传入了集合数据,并未返回,可知进入easyExcel源码里面.

    private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException {
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
        downLoadExcel(fileName, response, workbook);
    }

3 ExcelExportUtil类中该方法,可知第二步,创建sheet过程中传入了集合值.

    /**
     * @param entity    表格标题属性
     * @param pojoClass Excel对象Class
     * @param dataSet   Excel对象数据List
     */
    public static Workbook exportExcel(ExportParams entity, Class<?> pojoClass,
                                       Collection<?> dataSet) {
        Workbook workbook = getWorkbook(entity.getType(), dataSet.size());
        new ExcelExportService().createSheet(workbook, entity, pojoClass, dataSet);
        return workbook;
    }

4 ExcelExportService类中该方法,可以看到是调用createSheetForMap方法时,传入了集合值.

    public void createSheet(Workbook workbook, ExportParams entity, Class<?> pojoClass,
                            Collection<?> dataSet) {
			// 省略...
        try {
            List<ExcelExportEntity> excelParams = new ArrayList<ExcelExportEntity>();
            // 得到所有字段
            Field[]     fileds   = PoiPublicUtil.getClassFields(pojoClass);
            ExcelTarget etarget  = pojoClass.getAnnotation(ExcelTarget.class);
            String      targetId = etarget == null ? null : etarget.value();
            getAllExcelField(entity.getExclusions(), targetId, fileds, excelParams, pojoClass,
                    null, null);
            //获取所有参数后,后面的逻辑判断就一致了
            createSheetForMap(workbook, entity, excelParams, dataSet);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e.getCause());
        }
    }

5 进入了createSheetForMap方法, 调用insertDataToSheet方法,并传入集合值.

    public void createSheetForMap(Workbook workbook, ExportParams entity,
                                  List<ExcelExportEntity> entityList, Collection<?> dataSet) {
        // 省略...
        insertDataToSheet(workbook, entity, entityList, dataSet, sheet);
    }

6 进入insertDataToSheet方法,对集合迭代器遍历,并调用了createCells方法,传入遍历值.

    protected void insertDataToSheet(Workbook workbook, ExportParams entity,
                                     List<ExcelExportEntity> entityList, Collection<?> dataSet,Sheet sheet) {
        try {
     		// 省略...
            Iterator<?>  its      = dataSet.iterator();
            List<Object> tempList = new ArrayList<Object>();
            while (its.hasNext()) {
                Object t = its.next();
                index += createCells(patriarch, index, t, excelParams, sheet, workbook, rowHeight, 0)[0];
                tempList.add(t);
                if (index >= MAX_NUM) {
                    break;
                }
            }
            if (entity.getFreezeCol() != 0) {
                sheet.createFreezePane(entity.getFreezeCol(), 0, entity.getFreezeCol(), 0);
            }

            mergeCells(sheet, excelParams, titleHeight);

            its = dataSet.iterator();
            for (int i = 0, le = tempList.size(); i < le; i++) {
                its.next();
                its.remove();
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("List data more than max ,data size is {}",
                        dataSet.size());
            }
            // 发现还有剩余list 继续循环创建Sheet
            if (dataSet.size() > 0) {
                createSheetForMap(workbook, entity, entityList, dataSet);
            } else {
                // 创建合计信息
                addStatisticsRow(getExcelExportStyler().getStyles(true, null), sheet);
            }

        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e.getCause());
        }
    }

7 进入 createCells方法,调用getCellValue方法,并传入了遍历值.

    public int[] createCells(Drawing patriarch, int index, Object t,
                             List<ExcelExportEntity> excelParams, Sheet sheet, Workbook workbook,
                             short rowHeight, int cellNum) {
        try {
        		   // 省略...
                    Object value = getCellValue(entity, t);

                    if (entity.getType() == BaseEntityTypeConstants.STRING_TYPE) {
                        createStringCell(row, cellNum++, value == null ? "" : value.toString(),
                                index % 2 == 0 ? getStyles(false, entity) : getStyles(true, entity),
                                entity);

                    } else if (entity.getType() == BaseEntityTypeConstants.DOUBLE_TYPE) {
                        createDoubleCell(row, cellNum++, value == null ? "" : value.toString(),
                                index % 2 == 0 ? getStyles(false, entity) : getStyles(true, entity),
                                entity);
                    } else {
                        createImageCell(patriarch, entity, row, cellNum++,
                                value == null ? "" : value.toString(), t);
                    }
                    if (entity.isHyperlink()) {
                        row.getCell(cellNum - 1)
                                .setHyperlink(dataHandler.getHyperlink(
                                        row.getSheet().getWorkbook().getCreationHelper(), t,
                                        entity.getName(), value));
                    }
                }
            }
  			
			// 省略...
            return new int[]{maxHeight, cellNum};
        } catch (Exception e) {
            LOGGER.error("excel cell export error ,data is :{}", ReflectionToStringBuilder.toString(t));
            LOGGER.error(e.getMessage(), e);
            throw new ExcelExportException(ExcelExportEnum.EXPORT_ERROR, e);
        }

    }

8 进入getCellValue方法, 因为日期类型的Format不为空,我们在实体上设置了为format = "yyyy-MM-dd HH:mm:ss", 所以进入dateFormatValue方法.

    public Object getCellValue(ExcelExportEntity entity, Object obj) throws Exception {
        Object value;
        
        // 省略...
        if (StringUtils.isNotEmpty(entity.getFormat())) {
            value = dateFormatValue(value, entity);
        }
        if (entity.getReplace() != null && entity.getReplace().length > 0) {
            value = replaceValue(entity.getReplace(), String.valueOf(value));
        }
        if (StringUtils.isNotEmpty(entity.getNumFormat())) {
            value = numFormatValue(value, entity);
        }
        if (StringUtils.isNotEmpty(entity.getDict()) && dictHandler != null) {
            value = dictHandler.toName(entity.getDict(), obj, entity.getName(), value);
        }
        if (needHandlerList != null && needHandlerList.contains(entity.getName())) {
            value = dataHandler.exportHandler(obj, entity.getName(), value);
        }
        if (StringUtils.isNotEmpty(entity.getSuffix()) && value != null) {
            value = value + entity.getSuffix();
        }
        if (value != null && StringUtils.isNotEmpty(entity.getEnumExportField())) {
            value = PoiReflectorUtil.fromCache(value.getClass()).getValue(value, entity.getEnumExportField());
        }
        return value == null ? "" : value.toString();
    }

9 进入dateFormatValue方法, 看到第一个判断,value是否是String类型实例, 且不为空, 创建时间的类型都满足,所以进入第一个if判断,且ExcelExportEntity类中的databaseFormat数据库格式默认为yyyyMMddHHmmss, 创建时间2023-06-12 23:27:48进过了数据库默认时间格式的装换后变成的Date类型值为2022-12-05 23:02:23 , 最后一步,都经过自定义的Format,将日期类型,转换成字符串返回.

(ps: 更新时间的类型是Date,所以进入第二个if中, 直接赋值,没有进行日期的装换)

    private Object dateFormatValue(Object value, ExcelExportEntity entity) throws Exception {
        Date temp = null;
        if (value instanceof String && StringUtils.isNoneEmpty(value.toString())) {
            SimpleDateFormat format = new SimpleDateFormat(entity.getDatabaseFormat());
            temp = format.parse(value.toString());
        } else if (value instanceof Date) {
            temp = (Date) value;
        } else if (value instanceof Instant) {
            Instant instant = (Instant) value;
            temp = Date.from(instant);
        } else if (value instanceof LocalDate) {
            LocalDate localDate = (LocalDate) value;
            temp = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
        } else if (value instanceof LocalDateTime) {
            LocalDateTime localDateTime = (LocalDateTime) value;
            temp = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        } else if (value instanceof ZonedDateTime) {
            ZonedDateTime zonedDateTime = (ZonedDateTime) value;
            temp = Date.from(zonedDateTime.toInstant());
        } else if (value instanceof OffsetDateTime) {
            OffsetDateTime offsetDateTime = (OffsetDateTime) value;
            temp = Date.from(offsetDateTime.toInstant());
        } else if (value instanceof java.sql.Date) {
            temp = new Date(((java.sql.Date) value).getTime());
        } else if (value instanceof java.sql.Time) {
            temp = new Date(((java.sql.Time) value).getTime());
        } else if (value instanceof java.sql.Timestamp) {
            temp = new Date(((java.sql.Timestamp) value).getTime());
        }
        if (temp != null) {
            SimpleDateFormat format = new SimpleDateFormat(entity.getFormat());
            if (StringUtils.isNotEmpty(entity.getTimezone())) {
                format.setTimeZone(TimeZone.getTimeZone(entity.getTimezone()));
            }
            value = format.format(temp);
        }
        return value;
    }

3 问题总结

通过上述问题排查,知道了问题最后生成的原因,解决方法也很明确,即将创建时间的字符串类型更换为Date日期类型,再次导出Excel,日期数据正确.

一个类型的错误使用,引发了一个不大不小的bug. 对于代码的修改和测试,还需要认真仔细,减少bug的出现.

本文到此结束,一天一个bug系列,不希望有下次! 拜拜了您嘞

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 在使用easyExcel导入表格时,如果导入模板对应的时间列使用的是日期格式,但导入进来后却变成了数字,比如2022年7月5日导入进来之后变成了44745,可能是因为在导入过程错误地使用String类型接收日期类,导致日期值转换错误。\[1\]\[2\]在easyExcel的导出过程,如果使用String类型接收日期类,会导致日期值转换错误。在具体的排查过程,可以进入dateFormatValue方法,查看是否满足了判断条件,然后根据数据库默认时间格式和自定义的Format进行日期类的转换。\[3\]因此,为了正确接收日期格式,应该使用正确的数据类型接收日期值,例如使用Date类型接收。 #### 引用[.reference_title] - *1* [使用EasyExcel导入excel日期格式数据时获取到的却是一个数字](https://blog.csdn.net/lingyejun/article/details/127335347)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [easyExcel使用String类型接收Date日期类问题](https://blog.csdn.net/ABestRookie/article/details/131191831)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值