导出来 Excel 模版自动生成标准,防止客户不断改动

一句话汇总

Excel 导出来、导进时,依据注释全自动加上表格中认证标准,防止客户因填好不正确的枚举类型字段名而不断改动 Excel

要求情况

针对 Java Web 新项目,一直难以避免的发生 Excel 导进、导出来的要求,而 Excel 导入导出时,枚举类型字段名和枚举值的投射是十分普遍的一种状况

比如:下边这张实例中的性別列

数据库表构造:

image-20210616140903264

Excel 中客户必须键入:男,女,不明

image-20210616140615962

普遍的 Excel 架构都早已遮盖了枚举类型投射的作用,比如:EasyPOI

可是这类实际操作方法针对客户而言,并并不是很便捷,设想一下:倘若客户在性別列键入了:男士,最后的結果一般便是程序流程抛出异常,客户获得提醒:性別键入不正确,暖心的开发人员很有可能会再加上:输入您 男 女 不明,做的更强一些的 很有可能在列头加上标识提醒:该列仅能键入 男 女 不明,可是这类弱限定也没法从源头上解决困难

image-20210616141205671

更强一点的解决方法是:运用 Excel 的数据验证作用,把表格中再加上标准校检,让客户只有键入恰当的枚举值,防止因一次键入不正确而不断返修,消耗客户的時间和好心态

image-20210616141620860

当客户键入了非枚举值以后,Excel 会提醒客户键入不合规管理,严禁客户储存

image-20210616141707359

那样的互动就能从根源确保客户键入恰当的值

那那么友善的设计方案,在 Java 中怎样能便捷且扩展性更强的完成呢?

要求完成

我这边的完成是根据 EasyPOI 注释(EasyPOI 变换投射关联注释重复使用) 反射面 完成的,解决了之上要求困扰的与此同时,能够达到编码一处改动,好几个作用都起效的目地

代码仓库

GayHub

dao层

@Excel 注释中的 replace 特性,该特性是 EasyPOI 用于做字段名投射的,我这里重复使用他做 Excel 认证的选择项,此外一个便是 orderNum 特性,用该值来全自动获得某一字段名在 Excel 中的列的部位

@Data
public class Human extends BaseEntity {

    private Long id;

    @Excel(name = "名字", orderNum = "1", width = 15)
    private String name;

    @Excel(name = "年纪", orderNum = "2", width = 15)
    private Integer age;

    @Excel(name = "性別", replace = {"男_1", "女_2", "不明_3"}, orderNum = "3", width = 15)
    private Integer gender;
}

获得字段名和列部位的投射

此类在复位时,必须特定当今导出来 Excel 相匹配的dao层的类种类,随后根据解析xml类中字段名的注释,转化成字段名和列排列(部位)的投射关联

public class FieldOrderMappingHelper<T> {
    /**
     * 适用的较大字段名数
     */
    private final static int MAX_LIST_SIZE = 26;

    public FieldOrderMappingHelper(Class<T> pojo) {
        this.pojo = pojo;
        initMap();
    }

    /**
     * 分析注解的 pojo 目标
     */
    private Class<T> pojo;

    /**
     * 字段名和编号的投射关联
     */
    private HashMap<String, Integer> fieldAndOrderMap;


    /**
     * 作用:复位类的字段名內容,创建字段名和编号及其字段名和 excel 字段名的投射关联
     *
     * @author kangshuai@gridsum.com
     * @date 2021/4/9 12:06
     */
    private void initMap() {
        HashMap<String, Integer> fieldAndOrderMap = new HashMap<>(16);
        HashSet<Integer> existOrderNumSet = new HashSet<>(16);

        List<FiledAndOrder> list = new ArrayList<>();
        list = initList(list, pojo);
        if (list.size() > MAX_LIST_SIZE) {
            throw new RuntimeException(pojo.getName()   "现阶段较大适用 26 个字段名,26  必须改编码");
        }

        // 排列
        list.sort(Comparator.comparing(FiledAndOrder::getOrder));

        for (int i = 0; i < list.size(); i  ) {
            if (existOrderNumSet.contains(list.get(i).getOrder())) {
                throw new RuntimeException(pojo.getName()   "类內部或与父类字段名中存有反复的 excel 排列,请改动");
            }
            existOrderNumSet.add(list.get(i).getOrder());
            fieldAndOrderMap.put(list.get(i).getFiledName(), i);
        }
        this.fieldAndOrderMap = fieldAndOrderMap;
    }

    /**
     * 作用:复位类的字段名信息内容,转化成 ArrayList
     *
     * @return java.util.List<com.gridsum.ad.ooh.project.entity.FiledAndOrder>
     * @author kangshuai@gridsum.com
     * @date 2021/4/9 12:09
     */
    private List<FiledAndOrder> initList(List<FiledAndOrder> list, Class<?> pojoClass) {
        if (Object.class.equals(pojoClass)) {
            return list;
        }
        Field[] fields = pojoClass.getDeclaredFields();
        for (Field f : fields) {
            // 寻找全部加了 Excel 注释的字段名
            Excel annotation = f.getAnnotation(Excel.class);
            if (annotation == null) {
                continue;
            }
            // 过虑掩藏行
            if (annotation.isColumnHidden()) {
                continue;
            }
            FiledAndOrder filedAndOrder = new FiledAndOrder(f.getName(), Integer.parseInt(annotation.orderNum()));
            list.add(filedAndOrder);
        }
        // 递归算法搜索父类
        Class<?> superclass = pojoClass.getSuperclass();
        return initList(list, superclass);
    }

    public HashMap<String, Integer> getFieldAndOrderMap() {
        return fieldAndOrderMap;
    }
}

设定认证标准

setValidation 方式有两个主要参数,第一个是导出来 Excel 相匹配的dao层的类种类,第二个是 FieldOrderMappingHelper.getFieldAndOrderMap() 获得到的字段名和排列投射,此类根据反射面字段名上的注释,全自动为转化成的 workbook 加上认证标准

public class ExcelStyleHelper {

    /**
     * 加上列值认证的最少行
     */
    public static final int EXCEL_VALID_ROW_MIN = 1;
    /**
     * 加上列值认证的较大行
     */
    public static final int EXCEL_VALID_ROW_MAX = (2 << 15) - 1;
    /**
     * Excel 目标
     */
    private Workbook workbook;
    /**
     * Sheet 页,默认设置取第一个 sheet 页
     */
    private Sheet sheet;

    public ExcelStyleHelper(Workbook workbook) {
        this.workbook = workbook;
        this.sheet = workbook.getSheetAt(0);
    }
    /**
     * 作用:表格中加上下拉列表,仅适用 xls
     *
     * @author kangshuai@gridsum.com
     * @date 2021/4/8 18:55
     */
    public void setValidation(Class<?> pojoClass, HashMap<String, Integer> map) {
        // 递归算法到 Object 就停住
        if (Object.class.equals(pojoClass)) {
            return;
        }
        // 获得所有的字段
        Field[] fields = pojoClass.getDeclaredFields();
        for (Field field : fields) {
            Excel annotation = field.getAnnotation(Excel.class);
            if (annotation == null) {
                continue;
            }
            String[] replace = annotation.replace();
            if (replace.length == 0) {
                continue;
            }
            String[] textList = new String[replace.length];
            for (int i = 0; i < replace.length; i  ) {
                textList[i] = replace[i].split("_")[0];
            }
            // 依据列名获得他在 excel 中的行数(融合 excel 注释中的排列)
            Integer col = map.get(field.getName());
            setValid(textList, col, col);
        }
        // 递归算法父类的注释
        Class<?> superclass = pojoClass.getSuperclass();
        setValidation(superclass, map);
    }

    /**
     * 作用:设定认证区段
     *
     * @author kangshuai@gridsum.com
     * @date 2021/4/9 15:11
     */
    private void setValid(String[] textList, int firstCol, int endCol) {
        // 设定数据有效性载入在哪个表格中上,四个主要参数分别是:起止行、停止行、起止列、停止列
        CellRangeAddressList regions = new CellRangeAddressList(EXCEL_VALID_ROW_MIN, EXCEL_VALID_ROW_MAX, firstCol, endCol);
        // 载入下拉框內容
        DVConstraint constraint = DVConstraint.createExplicitListConstraint(textList);
        // 数据有效性目标
        HSSFDataValidation dataList = new HSSFDataValidation(regions, constraint);
        sheet.addValidationData(dataList);
    }
}

实例导出来编码

操纵层编码以下

@RestController
public class ExcelExportController {

    @GetMapping("excel")
    public void excelExport(HttpServletResponse response) throws Exception {
        List<Human> humanList = new ArrayList<>();

        doWriteListToResponse(humanList, Human.class, response, "检测 Sheet", "检测 Excel.xls");
    }


    /**
     * 作用:将結果载入輸出流
     *
     * @author kangshuai@gridsum.com
     * @date 2021/4/14 14:46
     */
    public <T> void doWriteListToResponse(List<T> list, Class<T> exportType, HttpServletResponse response, String sheetName, String excelName) throws IOException {
        ExportParams ex = new ExportParams(null, sheetName, ExcelType.HSSF);
        // 建立导出来目标
        Workbook workbook = ExcelExportUtil.exportExcel(ex, exportType, list);
        // 复位java工具
        HashMap<String, Integer> map = new FieldOrderMappingHelper<>(exportType).getFieldAndOrderMap();
        ExcelStyleHelper styleHelper = new ExcelStyleHelper(workbook);
        // 加上标准
        styleHelper.setValidation(exportType, map);
        // 载入輸出流,忽视这里硬编码
        response.setHeader("Content-Disposition", "attachment;filename="   new String(excelName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/x-download");
        workbook.write(response.getOutputStream());
    }
}

评论(0条)

刀客源码 游客评论