• 通用导入


    数据导入

    功能支持

    1. 读取excel、CSV
    2. 加入自定义注解校验数据防重
    3. 加入导入值跟实体类的类型检查
    4. 字典表支持(跟导出一个用法)
    5. 支持多种数据场景校验(Validation自带方法实体类校验)

    使用方法

        @ApiOperation("Excel导入")
        @PostMapping("uploadExcel")
        public void uploadExcel() throws Exception {
            //1. 读取excel
            List<Map<String, Object>> maps = ExcelCSVReadUtils.readBySax(new FileInputStream("C:\\Users\Admin\Desktop\t_sys_user.xls"), 0, 1);
            //or 读取CSV
            //List<Map<String, Object>> maps = ExcelCSVReadUtils.readCsv(new FileInputStream("C:\Users\Admin\Desktop\t_sys_user.csv"), 1, 1);
    
            //2. 转换结果(支持表头下划线转驼峰)
            Result result = MapUtil.map2Object(maps, User.class,1);
            Object data = result.getData();
            //3. 成功返回true
            if(result.isCode()) {
                //4. 获取对象
                List<User> users = MapUtil.castList(data, User.class);
                for (User user : users) {
                    //5. 脚手架自带 数据校验
                    ValidatorHelper.validateEntity(user);
                }
            }
        }

    实体模板

    实体注解介绍

    @Unique为自定义注解校验数据防重,加在实体类的字段上

    @ExcelProperty(converter = GenderConvert.class)字典表用法同导出
    不同的是,导入重写的是BaseConverter类的convertToJavaData这个方法

    @Override
        public String convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            String dictType = getDictType();
            //反向替换
            if (!StringUtils.isEmpty(cellData.getData()) ){
                //告诉我你的字典类型,先从缓存中获取
                Map<String, String> dictItemMap = dictCache.get(dictType);
                if (dictItemMap == null) {
                    //没拿到调用实现方法获取字典
                    GetDictMapping queryDictItem = ApplicationContextHolder.getBean(GetDictMapping.class);
                    if (queryDictItem != null){
                        dictItemMap = queryDictItem.getDictMappingReverse(dictType);
                        //放入缓存
                        dictCache.put(dictType,dictItemMap);
                    }
                }
                String v = dictItemMap.get(cellData.getData());
                return (v == null ? Convert.toStr(cellData.getData()) : v);
            }
            return Convert.toStr(cellData.getData());
        }
    

    转换结果

    成功返回true

    失败返回错误条件提示

    代码分析

    common-data-download分支下的upload包下,主要有两个工具类

    ExcelCSVReadUtils封装了hutool工具类来读取Excel(03、07格式)、CSV
    MapUtil支持表头下划线转驼峰为实体类对象,数据类型读取的值跟实体类的类型不匹配提醒、加入Unique唯一性校验不匹配提醒

    附代码

    /***
     * @ClassName ExcelCSVReadUtils
     * @Description: 读取大表的excel工具
     * @Author suyuan
     **/
    public class ExcelCSVReadUtils {
        private static Logger logger = LoggerFactory.getLogger(ExcelCSVReadUtils.class);
        private List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
        private List<String> headers = new ArrayList<>();
        private Map<String, String> aliasHeader;
        private int headerRow = 1;
        private boolean checkHeader = false;
    
        public ExcelCSVReadUtils(Map<String, String> aliasHeader, int headerRow) {
            this.aliasHeader = aliasHeader;
            this.headerRow = headerRow;
        }
    
        public ExcelCSVReadUtils(Map<String, String> aliasHeader, int headerRow, boolean checkHeader) {
            this.aliasHeader = aliasHeader;
            this.headerRow = headerRow;
            this.checkHeader = checkHeader;
        }
    
        /**
         * 读取大表的实现方法
         *
         * @return
         */
        private RowHandler createRowHandler() {
            return new RowHandler() {
                @Override
                public void handle(int sheetIndex, int rowIndex, List<Object> rowlist) {
                    if (isEmpty(rowlist)) {
                        return;
                    }
                    if (headerRow > rowIndex + 1) {
                        return;
                    } else if (headerRow == rowIndex + 1) {
                        rowlist.forEach(e -> headers.add(e.toString()));
                        //比对表头是否正确;
                        if (checkHeader) {
                            boolean headerHandler = checkHeaderHandler();
                            if (!headerHandler) {
                                throw new POIException("表头格式不正确!");
                            }
                        }
                    } else {
                        if (aliasHeader == null || aliasHeader.isEmpty()) {
                            rows.add(IterUtil.toMap(headers, rowlist));
                        } else {
                            rows.add(IterUtil.toMap(aliasHeader(headers, aliasHeader), rowlist));
                        }
                    }
                }
            };
        }
    
    
        public boolean isEmpty(List<Object> list) {
            if (list.isEmpty()) {
                return true;
            }
            List<Object> collect = list.stream().filter(e -> e == null || StringUtils.isBlank(e.toString())).collect(Collectors.toList());
            return collect.size() == list.size() ? true : false;
        }
    
        /**
         * @param inputStream 文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param sheet       读取的sheet  第一个sheet为 0
         * @param headerRow   表头行 第一行是 1
         * @param beanType    定义的对象 属性有excel的注解
         * @param <T>
         * @return
         */
        public static <T> List<T> readBySax(InputStream inputStream, int sheet, int headerRow, Class<T> beanType) {
            Map<String, String> aliasHeader = getFiledMap(beanType);
            List<Map<String, Object>> mapList = readBySax(inputStream, sheet, aliasHeader, headerRow, false);
            if (Map.class.isAssignableFrom(beanType)) {
                return (List<T>) mapList;
            } else {
                List<T> beanList = new ArrayList(mapList.size());
                Iterator iterator = mapList.iterator();
    
                while (iterator.hasNext()) {
                    Map<String, Object> map = (Map) iterator.next();
                    beanList.add(BeanUtil.mapToBean(map, beanType, false));
                }
                return beanList;
            }
        }
    
    
        /**
         * @param inputStream 文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param sheet       读取的sheet  第一个sheet为 0
         * @param headerRow   表头行 第一行是 1
         * @param beanType    定义的对象 属性有excel的注解
         * @param <T>
         * @param checkHeader
         * @return
         */
        public static <T> List<T> readBySax(InputStream inputStream, int sheet, int headerRow, Class<T> beanType, boolean checkHeader) {
            Map<String, String> aliasHeader = getFiledMap(beanType);
            List<Map<String, Object>> mapList = readBySax(inputStream, sheet, aliasHeader, headerRow, checkHeader);
            if (Map.class.isAssignableFrom(beanType)) {
                return (List<T>) mapList;
            } else {
                List<T> beanList = new ArrayList(mapList.size());
                Iterator iterator = mapList.iterator();
    
                while (iterator.hasNext()) {
                    Map<String, Object> map = (Map) iterator.next();
                    beanList.add(BeanUtil.mapToBean(map, beanType, false));
                }
                return beanList;
            }
        }
    
        /**
         * @param inputStream 文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param sheet       读取的sheet  第一个sheet为 0
         * @param headerRow   表头行 第一行是 1
         * @return
         */
        public static List<Map<String, Object>> readBySax(InputStream inputStream, int sheet, int headerRow) {
            return readBySax(inputStream, sheet, null, headerRow, false);
        }
    
        /**
         * @param inputStream
         * @param sheet       读取的sheet  第一个sheet为 0
         * @param headerRow   表头行 第一行是 1
         * @param checkHeader
         * @return
         */
        public static List<Map<String, Object>> readBySax(InputStream inputStream, int sheet, int headerRow, boolean checkHeader) {
            return readBySax(inputStream, sheet, null, headerRow, checkHeader);
        }
    
        /**
         * @param inputStream 文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param sheet       读取的sheet  第一个sheet为 0
         * @param aliasHeader 定义的表头转换map {"姓名":name,"年龄":age}
         * @param headerRow   表头行 第一行是 1
         * @return
         */
        public static List<Map<String, Object>> readBySax(InputStream inputStream, int sheet, Map<String, String> aliasHeader, int headerRow, boolean checkHeader) {
            long start = System.currentTimeMillis();
            ExcelCSVReadUtils excelUtils = new ExcelCSVReadUtils(aliasHeader, headerRow, checkHeader);
            ExcelUtil.readBySax(inputStream, sheet, excelUtils.createRowHandler());
            System.out.println("read  used :" + (System.currentTimeMillis() - start) + " ms");
            return excelUtils.getRows();
        }
    
        /**
         * 读取csv文件
         *
         * @param inputStream    文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param headerRowIndex 表头行
         * @param startRowIndex  数据开始行
         * @return
         */
        public static List<Map<String, Object>> readCsv(InputStream inputStream, int headerRowIndex, int startRowIndex) {
            return readCsv(inputStream, null, headerRowIndex, startRowIndex, "utf-8");
        }
    
        /**
         * 读取csv文件
         *
         * @param inputStream    文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param headerRowIndex 表头行
         * @param startRowIndex  数据开始行
         * @param charset        字符集
         * @return
         */
        public static List<Map<String, Object>> readCsv(InputStream inputStream, int headerRowIndex, int startRowIndex, String charset) {
            return readCsv(inputStream, null, headerRowIndex, startRowIndex, charset);
        }
    
        /**
         * 读取csv文件
         *
         * @param inputStream    文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param aliasHeader    转换后的表头
         * @param headerRowIndex 表头行
         * @param startRowIndex  数据开始行
         * @param charset        字符集
         * @return
         */
        public static List<Map<String, Object>> readCsv(InputStream inputStream, Map<String, String> aliasHeader, int headerRowIndex, int startRowIndex, String charset) {
            long start = System.currentTimeMillis();
            List<Map<String, Object>> resList = new ArrayList<>();
            CsvReader reader = CsvUtil.getReader();
            //从文件中读取CSV数据
            InputStreamReader is = null;
            try {
                is = new InputStreamReader(inputStream, charset);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("get inputStreamReader failed");
            }
    
            CsvData data = reader.read(is);
            List<CsvRow> rows = data.getRows();
            //空表格;
            if (rows.isEmpty() || rows.size() - headerRowIndex == 0) {
                return null;
            }
            //获取表头;
            CsvRow headerRow = rows.get(headerRowIndex - 1);
            //遍历行
            for (int i = startRowIndex - 1; i < rows.size(); i++) {
                CsvRow csvRow = rows.get(i);
                //getRawList返回一个List列表,列表的每一项为CSV中的一个单元格(既逗号分隔部分)
                List<String> rawList = csvRow.getRawList();
                //跳过表头
                if(i<=startRowIndex - 1)
                {
                    continue;
                }
                if (aliasHeader == null) {
                    Map map = IterUtil.toMap(headerRow, (Iterable) rawList);
                    resList.add(map);
                } else {
                    Map map = IterUtil.toMap(aliasHeader(headerRow, aliasHeader), (Iterable) rawList);
                    resList.add(map);
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("read  used :" + (end - start) + " ms");
            return resList;
        }
    
        /**
         * 读取csv文件 返回期望的beanType
         *
         * @param inputStream    文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param headerRowIndex 表头行
         * @param startRowIndex  数据开始行
         * @param beanType       javaBean  属性包含@excel注解
         * @param <T>
         * @return
         */
        public static <T> List<T> readCsv(InputStream inputStream, int headerRowIndex, int startRowIndex, Class<T> beanType) {
            return readCsv(inputStream, headerRowIndex, startRowIndex, beanType, "utf-8");
        }
    
        /**
         * 读取csv文件 返回期望的beanType
         *
         * @param inputStream    文件流 获取方法推荐使用 new ByteArrayInputStream(multipartFile.getBytes())
         * @param headerRowIndex 表头行
         * @param startRowIndex  数据开始行
         * @param beanType       javaBean  属性包含@excel注解
         * @param charset        字符集
         * @param <T>
         * @return
         */
        public static <T> List<T> readCsv(InputStream inputStream, int headerRowIndex, int startRowIndex, Class<T> beanType, String charset) {
            Map<String, String> aliasHeader = getFiledMap(beanType);
            List<Map<String, Object>> mapList = readCsv(inputStream, aliasHeader, headerRowIndex, startRowIndex, charset);
            if (Map.class.isAssignableFrom(beanType)) {
                return (List<T>) mapList;
            } else {
                List<T> beanList = new ArrayList(mapList.size());
                Iterator i$ = mapList.iterator();
    
                while (i$.hasNext()) {
                    Map<String, Object> map = (Map) i$.next();
                    beanList.add(BeanUtil.mapToBean(map, beanType, false));
                }
                return beanList;
            }
        }
    
    
        /**
         * 校验表头是否是我们需要的格式
         *
         * @return
         */
        public boolean checkHeaderHandler() {
            boolean flag = true;
            List<String> aliasHeaders = CollUtil.newArrayList(aliasHeader.keySet());
            if (headers.size() == 0 || aliasHeaders == null) {
                return false;
            }
            //判断长度;
            if (aliasHeaders.size() > headers.size()) {
                return false;
            }
            //比较每个元素是否相等
            List<String> collect = aliasHeaders.parallelStream().filter(e -> headers.contains(e)).collect(Collectors.toList());
            return collect.size() == aliasHeaders.size();
        }
    
        /**
         * 表头转换 将excel/csv的表头中文转换成可以入库的表字段
         *
         * @param headerList
         * @param headerAlias
         * @return
         */
        private static List<String> aliasHeader(List<String> headerList, Map<String, String> headerAlias) {
            ArrayList<String> result = new ArrayList();
            if (CollUtil.isEmpty(headerList)) {
                return result;
            } else {
                String alias = null;
    
                for (Iterator iterator = headerList.iterator(); iterator.hasNext(); result.add(alias)) {
                    Object headerObj = iterator.next();
                    if (null != headerObj) {
                        String header = headerObj.toString();
                        alias = (String) headerAlias.get(header);
                        if (null == alias) {
                            alias = header;
                        }
                    }
                }
                return result;
            }
        }
    
        /**
         * 根据excel注解将属性名和注解name值转成map
         * el:{"姓名":name,"年龄":age}
         *
         * @param clazz
         * @return
         */
        public static Map<String, String> getFiledMap(Class<?> clazz) {
            BeanDesc beanDesc = BeanUtil.getBeanDesc(clazz);
            Collection<BeanDesc.PropDesc> props = beanDesc.getProps();
            Map<String, String> filedMap = new HashMap<>(props.size());
            for (BeanDesc.PropDesc p : props) {
                Field field = beanDesc.getField(p.getFieldName());
                Excel annotation = field.getAnnotation(Excel.class);
                if (annotation != null) {
                    filedMap.put(annotation.name(), p.getFieldName());
                }
            }
            return filedMap;
        }
    
        public List<Map<String, Object>> getRows() {
            return rows;
        }
    
    
    }
    
    
    public class MapUtil {
    
        /**
         * map转对象
         * 成功
         * {
         *     data:成功list
         *     size:成功条数
         *     code:true
         * }
         * or
         * 失败
         * {
         *     data:失败list警告
         *     size:失败条数
         *     code:false
         * }
         * @param maps      读取的对象
         * @param clazz     需要转换的对象
         * @param headerRow 表头行 第一行是 1
         * @author suyuan
         * @date 2021/5/28 14:16
         */
        public static <T> Result map2Object(List<Map<String, Object>> maps, Class<T> clazz,int headerRow) throws Exception {
            // DO: 2021/5/28 封装一个消息返回成功或者错误信息
            Result result = new Result();
            ArrayList<T> listsuccess = new ArrayList<T>();
            ArrayList<String> listfail = new ArrayList<>();
            Map<Object, Integer> m=new HashMap<>();
            int i=headerRow;
            for(Map<String, Object> map : maps){
                i++;
                T t = clazz.newInstance();
                // DO: 2021/5/28 下划线转驼峰
                Iterator<Map.Entry<String, Object>> entries = map.entrySet().iterator();
                while (entries.hasNext()) {
                    Map.Entry<String, Object> entry = entries.next();
                    Object fieldValue = entry.getValue();
                    // TODO: 2021/6/4 字典转换转换的不对要不要提醒?
                    Field newField = ReflectUtil.getField(clazz, StrUtil.toCamelCase(entry.getKey()));
                    if (newField != null) {
                        newField.setAccessible(true);
                        if (newField.isAnnotationPresent(ExcelProperty.class)) {
                            Class<? extends Converter> converter1 = newField.getAnnotation(ExcelProperty.class).converter();
                            if(!converter1.equals(AutoConverter.class)){
                                Converter converter = newField.getAnnotation(ExcelProperty.class).converter().newInstance();
                                CellData cellData = new CellData( converter.convertToJavaData(new CellData(fieldValue) , null, null));
                                if (cellData.getData() != null) {
                                    fieldValue = cellData.getData();
                                }
                            }
                        }
                        Class<?> fieldType = newField.getType();
                        if (fieldType.equals(String.class) && String.valueOf(fieldValue).contains(".0")) {
                            fieldValue = convertRate2Decimal(fieldValue);
                        }
                        Object convert = Convert.convert(fieldType, fieldValue);
                        newField.set(t, Convert.convert(fieldType, fieldValue));
                        if (convert == null) {
                            listfail.add("第" + i + "行的" + entry.getKey() + "列数据类型有问题,请自行检查");
                        } else {
                            //唯一性校验
                            if (newField.isAnnotationPresent(Unique.class)) {
                                Integer count = m.get(convert);
                                m.put(convert, (count == null) ? 1 : count + 1);
                                if (m.get(convert) != null && m.get(convert) > 1) {
                                    listfail.add("第" + i + "行的" + entry.getKey() + "列数据值有重复:" + convert + ",请自行检查");
                                }
                            }
                        }
                    }
                }
                listsuccess.add(t);
            }
            if(listfail.size()!=0){
                result.setData(listfail);
                result.setSize(listfail.size());
                result.setCode(false);
            }
            else {
                result.setData(listsuccess);
                result.setSize(listsuccess.size());
                result.setCode(true);
            }
            return result;
        }
    
    
        public static String convertRate2Decimal(Object rate) {
            if (rate == null) {
                return null;
            }
            BigDecimal bd = Convert.toBigDecimal(rate);
            if (bd == null) {
                return Convert.toStr(rate);
            }
            return bd.stripTrailingZeros().toPlainString();
        }
    
        /**
         * Object转List
         * @author suyuan
         * @date 2021/6/8 9:32
         */
        public static <T> List<T> castList(Object obj, Class<T> clazz)
        {
            List<T> result = new ArrayList<T>();
            if(obj instanceof List<?>)
            {
                for (Object o : (List<?>) obj)
                {
                    result.add(clazz.cast(o));
                }
                return result;
            }
            return null;
        }
    
    }
    
  • 相关阅读:
    做了一个非常小的WPS插件
    小幸福
    被迫积极
    将要从csdn搬过来了!
    游戏框架设计Ⅰ—— 游戏中的事件机制
    *VB中实现OpenGL的截屏(glReadPixels)
    *D3D中把场景保存为图片的方法
    *vc中检测内存泄露的工具VLD(visual leak detector) 转
    纯虚函数能为private吗?
    vc中调用Com组件的方法详解
  • 原文地址:https://www.cnblogs.com/symkmk123/p/14863476.html
Copyright © 2020-2023  润新知