springboot之Excel导入导出组件:支持字段检验、字典翻译、计算公式、级联下拉

产品需求

       Excel导入需求说明

  • 支持字段检验:必填、非空、长度限制、数字检验、日期检验等
  • 支持字典翻译:如中文性别转数字、下拉选择中文转代码等
  • 检验失败的单元格,要加批注并能导出提示Excel文件

     Excel导出需求说明

  • 支持复杂表头:如跨行跨列、动态标题
  • 生成单元格支持自动计算公式:如求和统计
  • 自动生成下拉选择框:包括下拉单选、下拉多选、级联选择

技术方案

3e5f951011094927abb471d0ff6cd263.png

e2f4457e2e58458e85871a598fcc33f1.png

案例展示

1. 导入检验 Excel加批注

d9cfe04ca6d74b01a569aa30467ac001.png

2. 导出Excel 自动生成公式

e4c019f8b8ec451da178e364ce1492f8.png

3. 导出Excel 生成单选下拉框

9e6a0ea367d54017b4749c5177765070.png

4. 导出Excel 生成级联下拉框

afd6035791834503bfd2dc2765477e49.png

ced0aaf14cf84582a65302b45cf5c298.png

5. 导出Excel 生成多选下拉框

358f3013fb9f4f83ac37020cd0c2a709.png

代码集成

项目地址:   https://gitee.com/cloud-coder/cloud-coder-report

1. 引入依赖,添加配置

<dependency>
  <groupId>com.geline</groupId>
  <artifactId>coder-report-easyexcel</artifactId>
  <version>1.3.1</version>
</dependency>
easy-excel:
  # Excel模板文件
  template-file: excel/template/*.xlsx
  # Excel统计报表SQL配置
  xml-file: excel/xml/*.xml
  # Excel导入导出处理接口
  excel-crud-consumer: com.geline.easyexcel.mybatisplus.config.MyExcelCrudConsumer
  # 清理过期导入Excel异常记录;表参考:sp_excel_file
  expire-delete-sql: delete from sp_excel_file where DATE(created_time) <= DATE(DATE_SUB(NOW(),INTERVAL 7 day))
  # 查询字典数据,支持树形字典;表参考:sp_data_dict
  dict-select-sql: select id, parent_id, code, name, value, level from sp_data_dict where deleted=0 and code=#{code}

2. 数据表

CREATE TABLE `sp_excel_file` (
  `id` varchar(32) NOT NULL COMMENT 'id',
  `filename` varchar(200) NOT NULL DEFAULT '' COMMENT '文件名称',
  `bytes` longblob COMMENT '文件流',
  `error_info` text COMMENT '单元格错误信息json',
  `deleted` tinyint DEFAULT '0' COMMENT '删除标识(0:正常,1:删除)',
  `create_by` varchar(120) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(120) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) COMMENT='Excel文件表';
CREATE TABLE `sp_data_dict` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`parent_id` bigint DEFAULT NULL COMMENT '父id',
`code` varchar(80) DEFAULT NULL COMMENT '编码',
`value` varchar(1000) DEFAULT NULL COMMENT '字典项值',
`name` varchar(255) DEFAULT NULL COMMENT '名称',
`sort_num` int DEFAULT NULL COMMENT '排序',
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
`deleted` tinyint DEFAULT '0' COMMENT '删除标识(0:正常,1:删除)',
`create_by` varchar(120) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(120) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`path` varchar(1000) DEFAULT NULL,
`path_name` varchar(1000) DEFAULT NULL,
`level` int DEFAULT '0' COMMENT '层级',
PRIMARY KEY (`id`) USING BTREE
) COMMENT='数据字典(树形结构)';

3. 自定义核心实现类

集成MybatisPlus框架实现类

@Component
@Slf4j
public class MyExcelCrudConsumer extends MPExcelCrudConsumer {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private ExcelFileDao excelFileDao;
    @Autowired
    private ExcelFileService excelFileService;
    @Autowired
    private ExcelValidFunction validFunction;

    @Override
    public IService getIService(String entityName) {
        return excelFileService;
    }

    @Override
    public void transLabelToKey(Collection entityList) {
        //处理字典翻译
        easyTransService.transBatch(entityList);
    }

    @Override
    public void transKeyToLabel(Collection entityList) {
        //处理字典翻译
        easyTransService.transBatch(entityList);
    }

    @Override
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    @Override
    public ExcelFileDao getExcelFileDao() {
        return excelFileDao;
    }

    @Override
    public List listData(String entityName, PageQry qry, Class rowClass, boolean currentPage) {
        if (!currentPage) {
            qry.setPageIndex(1);
            qry.setPageSize(50000);
        }
        // 调用业务service分页方法
        IService service = SpringUtil.getBean(StrUtil.lowerFirst(entityName)+"ServiceImpl");
        Page<Object> page = new Page<>(qry.getPageIndex(), qry.getPageSize());
        QueryWrapper wrapper = QueryWrapperUtil.build(qry);
        IPage pageMaps = service.pageMaps(page, wrapper);

        List records = pageMaps.getRecords();
        List rowList = BeanUtil.copyToList(records, rowClass);
        transKeyToLabel(rowList);
        return rowList;
    }

    @Override
    public List handleReportData(List dataList) {
        //处理字典翻译、行政区划翻译
        transKeyToLabel(dataList);
        return dataList;
    }

    @Override
    public ExcelValidFunction getValidFunction() {
        //自定义检验类
        return validFunction;
    }
}

集成JPA框架实现类

@Component
@Slf4j
public class MyExcelCrudConsumer extends JPAExcelCrudConsumer {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private ExcelFileDao excelFileDao;
    @Autowired
    private ExcelFileRepository repository;
    @Resource
    private TransServiceFactory transServiceFactory;
    @Autowired
    private ExcelValidFunction validFunction;

    @Override
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    @Override
    public ExcelFileDao getExcelFileDao() {
        return excelFileDao;
    }

    @Override
    public JpaRepository getJpaRepository(String entityName) {
        return repository;
    }

    @Override
    public void transLabelToKey(Collection entityList) {
        //处理字典翻译、行政区划翻译
        entityList.stream().forEach(row -> transServiceFactory.apply(row).labelToKey());
    }

    @Override
    public void transKeyToLabel(Collection entityList) {
        //处理字典翻译、行政区划翻译
        entityList.stream().forEach(row -> transServiceFactory.apply(row).keyToLabel());
    }

    @Override
    public List listData(String entityName, PageQry qry, Class rowClass, boolean currentPage) {
        if (!currentPage) {
            qry.setPageIndex(1);
            qry.setPageSize(50000);
        }
        // 调用业务service分页方法
        Object executor = SpringUtil.getBean(StrUtil.lowerFirst(entityName) + "ServiceImpl");
        PageResponse pageResponse = ReflectUtil.invoke(executor, "findAll", qry);
        List content = pageResponse.getData();
        List rowList = BeanUtil.copyToList(content, rowClass);
        //处理字典翻译、行政区划翻译
        transKeyToLabel(rowList);
        return rowList;
    }

    @Override
    public List handleReportData(List dataList) {
        //处理字典翻译、行政区划翻译
        transKeyToLabel(dataList);
        return dataList;
    }

    @Override
    public ExcelValidFunction getValidFunction() {
        return validFunction;
    }
}

4. 导入检验 失败加批注

@ExcelImport(value = "pipeBuilt")
@Getter
@Setter
public class PipeBuiltImportVO implements Serializable {

    //检验非空且长度<=120
    @ExcelProperty(value = "管廊项目名称", index = 0)
    @ExcelValid(notBlank = true, length = 120)
    private String projectName;

    //检验非null且为整数
    @ExcelProperty(value = "总数", index = 1)
    @ExcelValid(notNull = true, regexEnum = RegexEnum.number)
    private Integer totalNum;

    //检验非null且为小数
    @ExcelProperty(value = "断面截面尺寸", index = 2)
    @ExcelValid(notNull = true, regexEnum = RegexEnum.money)
    private BigDecimal sectionSize;

    //检验非null且保留2位小数
    @ExcelProperty(value = "总长度", index = 3)
    @ExcelValid(notNull = true, regexEnum = RegexEnum.digital_2)
    private BigDecimal totalLength;

    //检验非空且为正确的手机号
    @ExcelProperty(value = "手机号", index = 4)
    @ExcelValid(notBlank = true, regexEnum = RegexEnum.mobile)
    private String phone;

    //检验非空且为正确的邮箱
    @ExcelProperty(value = "邮箱", index = 5)
    @ExcelValid(notBlank = true, regexEnum = RegexEnum.email)
    private String email;
}

核心API

API名称

接口地址

导入Excel

POST:/easyExcel/importExcel/{entity}

检验失败时下载Excel

GET: /easyExcel/downErrorExcel/{entity}?errorId=x

5. 分页查询 导出Excel

@ExcelExportPage(value = "pipeBuilt", title = "管廊", qryClass = PipeBuiltListQry.class)
@Getter
@Setter
public class PipeBuiltExportVO implements Serializable {

    @ExcelProperty(value = "管廊项目名称", index = 0)
    private String projectName;

    //字典代码
    @Schema(description = "投融资模式")
    @TransDict(dictType = "gallery_invest_type", source = "financingModeLabel")
    private String financingMode;

    //生成单选下拉框+字典标签翻译
    @ExcelProperty(value = "投融资模式", index = 1)
    @ExcelDropDown(index = 15, dictName = "投融资模式", dictCode = "gallery_invest_type", type = DropDownType.SELECT)
    @TransDict(dictType = "gallery_invest_type", source = "financingMode")
    private String financingModeLabel;

    //字典代码
    @Schema(description = "管廊类型")
    @TransDict(dictType = "gallery_type", source = "pipeTypeLabel")
    private String pipeType;

    //生成多选下拉框+字典标签翻译
    @ExcelProperty(value = "管廊类型", index = 2)
    @ExcelDropDown(index = 5, dictName = "管廊类型", dictCode = "gallery_type", type = DropDownType.CHECKBOX)
    @TransDict(dictType = "gallery_type", source = "pipeType")
    private String pipeTypeLabel;
}

核心API

API名称

接口地址

下载Excel模板

GET:/easyExcel/exportPageTemplate/{entity}

导出当前页数据

GET:/easyExcel/exportPageExcel/{entity}

参数:?pageIndex=1&pageSize=10¤tPage=true

导出所有数据

GET:/easyExcel/exportPageExcel/townHouse

参数:?pageIndex=1&pageSize=10¤tPage=false

导出当前页勾选项

GET:/easyExcel/exportPageExcel/townHouse

参数:?pageIndex=1&pageSize=10¤tPage=true&ids=11,22


6. 导出Excel (统计报表)

@ExcelExportList(value = "illegalStatistics", title = "全统计")
@Getter
@Setter
public class IllegalStatisticsVO implements Serializable {

    //所属地市
    @ExcelProperty(index = 0)
    private String xzqhdmLabel;
    
    //统计时间
    @ExcelProperty(index = 1)
    private String month;
    
    //治理量(万m²)
    @ExcelProperty(index = 2)
    private BigDecimal byzll;
    
    //其中拆除 (万m²)
    @ExcelProperty(index = 3)
    private BigDecimal byccl;
    
    //治理量(万m²)
    @ExcelProperty(index = 4)
    private BigDecimal dnzll;
    
    //其中拆除 (万m²)
    @ExcelProperty(index = 5)
    private BigDecimal dnccl;
    
    //年治理目标(万m²)
    @ExcelProperty(index = 6)
    private BigDecimal bnzlmb;
    
    //年治理进度,写入自动计算公式
    @ExcelProperty(index = 7, format = "100*E{rowIndex}/F{rowIndex}")
    private String bnzljd;
}

Excel 模板定义-IllegalStatistics.xlsx

e466a2a91e7b4d36b10be24f8a5f42e6.png

Excel 动态查询xml定义

<?xml version="1.0" encoding="UTF-8"?>
<select>
  <!-- 查询传入参数 -->
  <conditions>year,startMonth,endMonth,xzqhdm,curXzqhdm</conditions>
  <!-- 表头动态变量 -->
  <variableMap>{"latestMonth": "{year}年{endMonth}月", "betweenMonth": "{year}年{startMonth}月-{endMonth}月", "zlmbTitle": "{year}年治理目标(万m²)", "zljdTitle": "{year}年治理进度" }</variableMap>
  <!-- 查询SQL:支持变量 ${name} #{name};支持生成动态 where 1=1 [and name=#{name}] -->
  <dynamicSql>
    <![CDATA[
    select s.xzqhdm, s.xzqhdmLabel,
    CONCAT(${year}, '-', ${endMonth}) as month, ROUND(t2.byzll, 2) as byzll,
    ROUND(t2.xzTotal, 2) as xzTotal, ROUND(t2.clTotal, 2) as clTotal,
    from (
    select regionalization as xzqhdm, regionalization_name as xzqhdmLabel,
    trim(TRAILING '0' from regionalization) as city_code
    from sp_ad_code
    where length(trim(TRAILING '0' from regionalization))=4
    ) s
    left join jd_target_reporting t3 on t1.xzqhdm = t3.xzqhdm
    left join sp_ad_code ad on ad.deleted = 0 and ad.regionalization = t1.xzqhdm
    where 1 = 1
    [AND (#{xzqhdm} IS NULL OR t1.xzqhdm LIKE CONCAT(#{xzqhdm},'%'))]
    [AND (#{curXzqhdm} IS NOT NULL OR t1.xzqhdm LIKE CONCAT(#{curXzqhdm},'%'))]
    ]]>
  </dynamicSql>
</select>

Excel 导出数据效果图

d261a757702a4f5ba1bc42c6e062ec6f.png

核心API

API名称

接口地址

下载Excel模板

GET:easyExcel/exportListTemplate/{reportCode}

导出Excel数据

GET:easyExcel/exportListExcel/{reportCode}?name=xxx

7. 自定义检验单元格数据

@Component
public class MyExcelValidFunction extends ExcelValidFunction {

    @Override
    public List<ExcelError> apply(Integer rowIndex, Object data) {
        //检验属性@ExcelValid
        List<ExcelError> list = ExcelValidUtil.valid(rowIndex, data);

        //检验用户是否拥有此行政区划权限
        Field field = ReflectUtil.getField(data.getClass(), "pathName");
        if(field!=null){
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if(property!=null){
                int colIndex = property.index();
                //...
                list.add(new ExcelError(rowIndex, colIndex, "您无权限导入当前地市数据!"));
            }
        }
        return list;
    }
}

检验效果如下

21641a5c519741d0a3e6369ea613b05e.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星梦天河

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

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

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

打赏作者

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

抵扣说明:

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

余额充值