Excel工具类

本文介绍了作者在工作中如何构建一个能满足需求的Excel读写框架,支持主体数据的枚举和关联数据的灵活处理,包括主体数据的读写、ExcelConfig注解的应用以及关系数据的关联存储和读取方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在工作遇到了Excel读写,但是先有的已poi为基准的框架不能满足需求。
要求

前言

最近在工作遇到了Excel读写,但是先有的已poi为基准的框架不能满足需求,网上也没有合适的车轮子,那只能自己造了。

一、需求?如图

在这里插入图片描述
要求读写这样的数据主体和关联数据,
1)主体数据有题头,除了通常读写数据,支持枚举。
2)存在任意关联数据,每种关联数据的第一行第一列有关联的数据的类型。

二、使用步骤

1.写入代码1

代码如下(示例):

private <T> void writeSheet(Workbook workbook, List<T> itemsList, Class<T> clzz, Boolean needHead){
        // 获取T对应的sheetName
        String sheetName = "sheet";
        if(clzz.isAnnotationPresent(ExcelClass.class)){
            // 获取 "类" 上的注解
            sheetName = (clzz.getAnnotation(ExcelClass.class)).sheetName();
        }
        Sheet sheet = workbook.createSheet(sheetName);
        try {
            // 在相应的单元格进行赋值
            Integer rowIndex = 0;
            // 获取对象的上的ExcelConfig注解
            List<Field> fields = getClzzOrdeByIndex(clzz);
            // 创建文件题头
            if(needHead){
                // 创建行数据
                Row row = sheet.getRow(rowIndex);
                if (null == row) {
                    row = sheet.createRow(rowIndex);
                }
                for(int i = 0; i < fields.size(); i++){
                    if(fields.get(i).getAnnotation(ExcelConfig.class) != null){
                        // 获取表字段的名称
                        String headValue = fields.get(i).getAnnotation(ExcelConfig.class).value();
                        Cell cell = row.getCell(i);
                        if (null == cell) {
                            cell = row.createCell(i);
                        }
                        if(!StringUtils.isEmpty(headValue)){
                            cell.setCellValue(headValue);
                        }
                    }
                }
                rowIndex = rowIndex+1;
            }
            //  写入数据
            for (int i = 0; i < itemsList.size() ; i++) {
                // 在excel中写入值
                T item = itemsList.get(i);
                if (item != null) {
                    rowIndex = writeCell(sheet, item, rowIndex, clzz, null);
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

	private <T> Integer writeCell(Sheet sheet, T item, Integer rowIndex, Class clzz, String relationTypeName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 获取对象的上的ExcelConfig注解
        List<Field> fields = getClzzOrdeByIndex(clzz);
        // 创建行数据
        Row row = sheet.getRow(rowIndex);
        if (null == row) {
            row = sheet.createRow(rowIndex);
        }
        Integer dataPos = 0;
        if(relationTypeName != null){
            // 添加占位符
            Cell cell = row.getCell(dataPos);
            if (null == cell) {
                cell = row.createCell(dataPos);
            }
            cell.setCellValue(relationTypeName);
            dataPos = 1;
        }

        // 处理需要写入excel的数据
        for (int i = 0; i < fields.size(); i++) {
            Field field = fields.get(i);
            // 判断获取的字段必须要有ExcelConfig标识
            if(field != null && field.getAnnotationsByType(ExcelConfig.class) != null){
                // 获取改字段是否是需要经过枚举处理
                Class enumClass = field.getAnnotation(ExcelConfig.class).enumClass();
                Object value = null;
                // 判断是否需要枚举
                if(enumClass != EmptyEums.class){
                    // 获取枚举所需的值
                    String fieldName = field.getAnnotation(ExcelConfig.class).enumFieldName();
                    // 获取枚举的目标字段的值
                    Object needEnumKey = getFieldValueByName(fieldName, item);
                    // 获取枚举的中每个枚举类
                    Object[] objects = enumClass.getEnumConstants();
                    for (Object obj : objects) {
                        Class<?> enumClzz = obj.getClass();
                        // 每个枚举的getKey方法
                        Method getKeyMethod = enumClzz.getDeclaredMethod("getKey");
                        Object enumKey = getKeyMethod.invoke(obj);
                        // 如果枚举的key和需要处理的needEnumKey相同,则获取对应value为field字段的值
                        if(enumKey.toString().equals(needEnumKey.toString())){
                            Method getValueMethod = enumClzz.getDeclaredMethod("getValue");
                            value = getValueMethod.invoke(obj);
                            break;
                        }
                    }
                } else {
                    value = getFieldValueByName(field.getName(), item);
                }
                // 获取注解字段对应的index值
                Integer columnNum = field.getAnnotation(ExcelConfig.class).index() + dataPos;
                Cell cell = row.getCell(columnNum);
                if (null == cell) {
                    cell = row.createCell(columnNum);
                }
                // 判断数据类型
                if (value != null){
                    parseFiledValue(cell, value,null);
                }
            }

        }
        rowIndex = rowIndex+1;
        // 写入关系型数据
        // clazz 集成了relations话,说明存在关系
        if(clzz.getSuperclass() == Relations.class){
            List<Relation> relations = (List<Relation>)getFieldValueByName("relations",item);
            if(relations != null){
                for (Relation relation : relations) {
                    List relationDatas = relation.getData();
                    for (int i = 0; i < relationDatas.size(); i++) {
                        // 判断是否要添加参数名称
                        String subRelationTypeName = "";
                        // 只有在第一行添加参数名称
                        if( i == 0){
                            subRelationTypeName = relation.getRelationTypeName();
                        }
                        rowIndex = writeCell(sheet, relationDatas.get(i), rowIndex, relation.getClazz(), subRelationTypeName);
                    }
                }
            }
        }
        return rowIndex;
    }

大致思路

  1. 组装数据:
    public static List<User> createUserData(){
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 5000; i++) {
            User user1 = new User();
            user1.setId(i);
            user1.setUsername("张三");
            user1.setHead("http://qzapp.qlogo.cn/qzapp/101357640/3C94155CAB4E28517D8435BF404B52F1/100");
            user1.setSex(0);
            user1.setPhone("1880000" + i);
            user1.setAdress("1000001");
    
    
            UserFamily father = new UserFamily("父子", user1.getUsername() + "的父亲", i + 20);
            UserFamily mother = new UserFamily("母亲", user1.getUsername() + "的母亲", i + 20);
            List<UserFamily> families = new ArrayList<>();
            families.add(father);
            families.add(mother);
            Relation familyRelation = new Relation<UserFamily>(UserFamily.class, families, false, "UserFamily");
    
            UserWallet rmb = new UserWallet(Currency.RMB.getKey(), 100 + i);
            UserWallet dollar = new UserWallet(Currency.DOLLAR.getKey(), 100 + i);
            List<UserWallet> wallets = new ArrayList<>();
            wallets.add(rmb);
            wallets.add(dollar);
    
            Relation walletRelation = new Relation<>(UserWallet.class, wallets, false, "UserWallet");
    
            List<Relation> relations = new ArrayList<>();
            relations.add(familyRelation);
            relations.add(walletRelation);
            user1.setRelations(relations);
    
            list.add(user1);
        }
        return list;
    }
    
    如上图:主体数据USER,关系数据:UserWallet 、UserFamily 。主体数据和关系数据通过Relation类进行关联的,降低主体数据与关系数据的耦合,提高代码重复利用性。
    public class Relations<T>{
        // 关系数据
        private List<Relation<T>> relations;
    
        public List<Relation<T>> getRelations() {
            return relations;
        }
    
        public void setRelations(List<Relation<T>> relations) {
            this.relations = relations;
        }
    }
    public class Relation<T>{
    	    /**
    	     * 参数的数据类型
    	     */
    	    private Class clazz ;
    	    /**
    	     * 参数数据
    	     */
    	    private List<T> data;
    	    /**
    	     * 是否需要字段说明
    	     */
    	    private Boolean needHead;
    	    /**
    	     * 参数类型,用于区分类别
    	     */
    	    private String relationTypeName;
    	
    	    public Relation(){};
    	
    	    public Relation(Class<T> clazz, List<T> data, Boolean needHead, String relationTypeName) {
    	        this.clazz = clazz;
    	        this.data = data;
    	        this.needHead = needHead;
    	        if(StringUtils.isEmpty(relationTypeName)){
    	            if(clazz.isAnnotationPresent(ExcelClass.class)){
    	                this.relationTypeName = clazz.getAnnotation(ExcelClass.class).value();
    	            }
    	        } else {
    	            this.relationTypeName = relationTypeName;
    	        }
    	    }
        }
    @ExcelClass(value="User", sheetName="用户")
    	public class User extends Relations {
        @ExcelConfig(value="序号", index =0)
        private Integer id;
        @ExcelConfig(value="姓名", index =1)
        private String username;
        @ExcelConfig(value="头部", index =2)
        private String head;
        @ExcelConfig(value="性别", index =3)
        private Integer sex;
        @ExcelConfig(value="手机号", index =4)
    }
    
    @ExcelClass(value="UserFamily", sheetName="家庭")
    	public class UserFamily{
        @ExcelConfig(value="关系", index =0)
        private String relation;
        @ExcelConfig(value="姓名", index =1)
        private String name;
        @ExcelConfig(value="年龄", index =2)
        private Integer age;
    }
    
    @ExcelClass(value="UserWallet", sheetName="钱包")
    public class UserWallet{
        @ExcelConfig(value="钱包类型", index =0, enumClass= Currency.class, enumFieldName="type")
        private String typeName;
        @ExcelConfig(value="数量", index =1)
        private Integer num;
        // 钱包类型
        private Integer type;
    }
    
    @ExcelClass 注解需要操作的excel数据类,value表示数据类型,如果数据是作为关系型数据,那么第一行第一列就会是value值,如下图
    在这里插入图片描述
    @ExcelConfig字段 修饰excel需要导出的字段,value:excel上对应字段的题头,index: 对应的列数,enumClass:枚举字段类型,枚举必须是 (key value)方式,enumFieldName:枚举对应的key值,通过反射获取enumFieldName对应的属性通过枚举enumClass类获取其value值,赋值到当前被ExcelConfig注解的字段。
    	public enum Currency implements ExcelFieldEunm {
    	    RMB(1, "人命币"),
    	    DOLLAR(2, "美元");
    	
    	    @Getter
    	    private Integer key;
    	    @Getter
    	    private String value;
    	
    	    Currency(int key, String value) {
    	        this.key = key;
    	        this.value = value;
    	    }
    	}
    
  2. 将数据导入excel中
    	private <T> void writeSheet(Workbook workbook, List<T> itemsList, Class<T> clzz, Boolean needHead){
           // 获取T对应的sheetName
           String sheetName = "sheet";
           if(clzz.isAnnotationPresent(ExcelClass.class)){
               // 获取 "类" 上的注解
               sheetName = (clzz.getAnnotation(ExcelClass.class)).sheetName();
           }
           Sheet sheet = workbook.createSheet(sheetName);
           try {
               // 在相应的单元格进行赋值
               Integer rowIndex = 0;
               // 获取对象的上的ExcelConfig注解
               List<Field> fields = getClzzOrdeByIndex(clzz);
               // 创建文件题头
               if(needHead){
                   // 创建行数据
                   Row row = sheet.getRow(rowIndex);
                   if (null == row) {
                       row = sheet.createRow(rowIndex);
                   }
                   for(int i = 0; i < fields.size(); i++){
                       if(fields.get(i).getAnnotation(ExcelConfig.class) != null){
                           // 获取表字段的名称
                           String headValue = fields.get(i).getAnnotation(ExcelConfig.class).value();
                           Cell cell = row.getCell(i);
                           if (null == cell) {
                               cell = row.createCell(i);
                           }
                           if(!StringUtils.isEmpty(headValue)){
                               cell.setCellValue(headValue);
                           }
                       }
                   }
                   rowIndex = rowIndex+1;
               }
               //  写入数据
               for (int i = 0; i < itemsList.size() ; i++) {
                   // 在excel中写入值
                   T item = itemsList.get(i);
                   if (item != null) {
                       rowIndex = writeCell(sheet, item, rowIndex, clzz, null);
                   }
               }
           } catch (Exception e){
               e.printStackTrace();
           }
       }
     private <T> Integer writeCell(Sheet sheet, T item, Integer rowIndex, Class clzz, String relationTypeName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
         // 获取对象的上的ExcelConfig注解
         List<Field> fields = getClzzOrdeByIndex(clzz);
         // 创建行数据
         Row row = sheet.getRow(rowIndex);
         if (null == row) {
             row = sheet.createRow(rowIndex);
         }
         Integer dataPos = 0;
         if(relationTypeName != null){
             // 添加占位符
             Cell cell = row.getCell(dataPos);
             if (null == cell) {
                 cell = row.createCell(dataPos);
             }
             cell.setCellValue(relationTypeName);
             dataPos = 1;
         }
    
         // 处理需要写入excel的数据
         for (int i = 0; i < fields.size(); i++) {
             Field field = fields.get(i);
             // 判断获取的字段必须要有ExcelConfig标识
             if(field != null && field.getAnnotationsByType(ExcelConfig.class) != null){
                 // 获取改字段是否是需要经过枚举处理
                 Class enumClass = field.getAnnotation(ExcelConfig.class).enumClass();
                 Object value = null;
                 // 判断是否需要枚举
                 if(enumClass != EmptyEums.class){
                     // 获取枚举所需的值
                     String fieldName = field.getAnnotation(ExcelConfig.class).enumFieldName();
                     // 获取枚举的目标字段的值
                     Object needEnumKey = getFieldValueByName(fieldName, item);
                     // 获取枚举的中每个枚举类
                     Object[] objects = enumClass.getEnumConstants();
                     for (Object obj : objects) {
                         Class<?> enumClzz = obj.getClass();
                         // 每个枚举的getKey方法
                         Method getKeyMethod = enumClzz.getDeclaredMethod("getKey");
                         Object enumKey = getKeyMethod.invoke(obj);
                         // 如果枚举的key和需要处理的needEnumKey相同,则获取对应value为field字段的值
                         if(enumKey.toString().equals(needEnumKey.toString())){
                             Method getValueMethod = enumClzz.getDeclaredMethod("getValue");
                             value = getValueMethod.invoke(obj);
                             break;
                         }
                     }
                 } else {
                     value = getFieldValueByName(field.getName(), item);
                 }
                 // 获取注解字段对应的index值
                 Integer columnNum = field.getAnnotation(ExcelConfig.class).index() + dataPos;
                 Cell cell = row.getCell(columnNum);
                 if (null == cell) {
                     cell = row.createCell(columnNum);
                 }
                 // 判断数据类型
                 if (value != null){
                     parseFiledValue(cell, value,null);
                 }
             }
    
         }
         rowIndex = rowIndex+1;
         // 写入关系型数据
         // clazz 集成了relations话,说明存在关系
         if(clzz.getSuperclass() == Relations.class){
             List<Relation> relations = (List<Relation>)getFieldValueByName("relations",item);
             if(relations != null){
                 for (Relation relation : relations) {
                     List relationDatas = relation.getData();
                     for (int i = 0; i < relationDatas.size(); i++) {
                         // 判断是否要添加参数名称
                         String subRelationTypeName = "";
                         // 只有在第一行添加参数名称
                         if( i == 0){
                             subRelationTypeName = relation.getRelationTypeName();
                         }
                         rowIndex = writeCell(sheet, relationDatas.get(i), rowIndex, relation.getClazz(), subRelationTypeName);
                     }
                 }
             }
         }
         return rowIndex;
       }
    
    代码能力一般啦,大致思路:
    主体数据,通过类的注解属性获取对应的注解的数据和属性的值, 根据注解ExcelConfig数据index的值,在excel中对应的列上写入主体数据(通过反射获取数据)。
    关系数据:根据Relations字段判断该数据是否有关联数据,如果有的话,在处理完每一条主体数据后,处理关系数据,处理逻辑和主体数据一致。

2.读入数据

大致思路:获取被ExcelClass注解的类,根据主体数据的class的类上的注解,获取excel中对应的sheet,获取class上每个被excelconfig注解过的属性,根据属性上的index的位置,通过反射给属性赋值。

private <T extends Relations> List<T> readExcel() throws IOException, InvocationTargetException, NoSuchMethodException {
        //获取
        List<T> datas = new ArrayList<>();

        Workbook workbook = null;
        Sheet sheet = null;
        Row row = null;
        //读取Excel文件
        File excelFile = new File(this.fileName.trim());
        InputStream is = null;
        try {
            is = new FileInputStream(excelFile);
            //获取Excel工作薄
            if (excelFile.getName().endsWith("xlsx")) {
                workbook = new XSSFWorkbook(is);
            } else {
                workbook = new HSSFWorkbook(is);
            }
            if (workbook == null) {
                throw new ExcelFileNotFind(this.fileName+"未找到");
            }
            // 注解了ExcelClass的类,map的key为注解的valueh值
            Map<String, Class<?>> classMap = annotationExcelClassToMap();
            //获取Excel需要解析的sheet,根据注解的的sheetname获取,否则就取第一个
            if(this.clazz.isAnnotationPresent(ExcelClass.class)){
                // 获取 "类" 上的注解
                String sheetName = (this.clazz.getAnnotation(ExcelClass.class)).sheetName();
                String value = (this.clazz.getAnnotation(ExcelClass.class)).value();
                // classMap移除表格主体数据
                classMap.remove(value);
                // 根据对象的注解找到对应的sheet
                sheet = workbook.getSheet(sheetName);
            } else {
                sheet = workbook.getSheetAt(0);
            }
            // 主体数据解析
            List<Field> tableFields = getClzzOrdeByIndex(this.clazz);
            // 记录当前数据类型
            Class<?> analysisClass = null;
            // 记录上一个数据类型,用于判单当前数据类型和上一个数据类型是否一直
            Class<?> lastAnalysisClass = null;
            List<Field> analysisFields = tableFields;
            // 读取数据
            for(int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
                row = sheet.getRow(rowNum);
                // 跳过空行
                if (isRowEmpty(row)){
                    continue;
                }
                List<Field> needAnalysis = new ArrayList<>();
                String firstCellValue = getCellStringValue(row.getCell(0));
                // 第一个单元格是否为空
                if(firstCellValue != null && !StringUtils.isEmpty(firstCellValue)){
                    // 是否是关系字段
                    if(classMap.keySet().contains(firstCellValue)){
                        analysisClass = classMap.get(firstCellValue);
                        analysisFields = getClzzOrdeByIndex(analysisClass);
                    } else {
                        // 设置为主题字段
                        analysisClass = this.clazz;
                        analysisFields = tableFields;
                    }
                }
                // 获取一行
//                row = sheet.getRow(rowNum);
                // 获取对象实列
                Object instance = new Object();
                // 是否为关系对象
                if(analysisClass.equals(this.clazz)){
                    instance = this.clazz.newInstance();
                } else {
                    instance = analysisClass.newInstance();
                }
                // 根据字段读取数据
                for(int i =0; i < analysisFields.size(); i++){
                    Field field = analysisFields.get(i);
                    Short index = field.getAnnotation(ExcelConfig.class).index();
                    if(index != null && index > -1){
                        String methodName = MethodUtils.setMethodName(field.getName());
                        Method method = analysisClass.getMethod(methodName, field.getType());
                        // 获取字段对应的单元格
                        Cell cell = null;
                        // 判断是 数据本体,还是关系数据
                        if(!analysisClass.equals(this.clazz)){
                            // 关系数据,第一列为 关系名称,所以跳过
                            cell = row.getCell(index+1);
                        } else {
                            cell = row.getCell(index);
                        }
                        if(cell != null){
                            // 转为为字符串
                            cell.setCellType(Cell.CELL_TYPE_STRING);
                            // 日期字段担负处理
                            if (DateUtil.isDateFied(field)) {
                                Date date = cell.getDateCellValue();
                                if(date != null){
                                    method.invoke(instance, cell.getDateCellValue());
                                }
                            } else {
                                String value = getCellStringValue(cell);
                                method.invoke(instance, convertType(field.getType(), value.trim()));
                            }
                        }
                    } else {
                        throw new ExcelNotFiledIndexException(field.getName()+"无index值,无法解析数据");
                    }
                }
                // 如果是关系数据
                if(!analysisClass.equals(this.clazz)){
                    // 最后一个关系数据
                    List<Relation> lastDataRelations = datas.get(datas.size()-1).getRelations();
                    // 初始化 relations值
                    if(lastDataRelations == null){
                        datas.get(datas.size()-1).setRelations(new ArrayList<Relations>());
                        lastDataRelations = datas.get(datas.size()-1).getRelations();
                    }
                    // 获取最有一个本体数据的List的最后一个本体的关系数据
                    if(analysisClass.equals(lastAnalysisClass)){
                        // 获取关系中的最后一个类型,并把当前的relation添加进去
                        Relation dataRelation = lastDataRelations.get(lastDataRelations.size()-1);
                        dataRelation.getData().add(instance);
                    } else {
                        // 如果和上一个不同,说明是不同关系数据,创建
                        Object finalInstance = instance;
                        Relation relation = new Relation(analysisClass, new ArrayList<Object>(){{add(finalInstance);};}, null, null);
                        lastDataRelations.add(relation);
                    }
                } else {
                    // 本体数据
                    datas.add((T)instance);
                }
                // 保存上一个数据解析的类型
                lastAnalysisClass = analysisClass;
            }
            is.close();
        } catch (IOException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            if(is != null){
                is.close();
            }
        }
        return datas;
    }

代码比较乱,懒得整理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值