美文网首页
CSV工具类

CSV工具类

作者: 陈追风 | 来源:发表于2019-04-23 11:22 被阅读0次
    /**
     * csv 文件工具类
     */
    public class CsvWriter {
        private static final Logger logger = LoggerFactory.getLogger(CsvWriter.class);
    
        private static final Charset ENCODING = Charset.forName("GBK");
        private static final String ROW_SEPARATOR = "\n";
        private static final String COLUMN_SEPARATOR = ",";
        private static final String TAB = "\t";
    
    
        public static Builder open() {
            return new Builder();
        }
    
        private CsvWriter() {
    
        }
    
    
        public static class Builder {
    
            private String header;
    
            private List<String> keys = new ArrayList<>();
    
            private List<JSONObject> data;
    
            private Function<JSONObject/*pageData*/, List/*data*/> dataProvider;
    
            private Predicate<JSONObject> itemFilter;
    
            // 特殊字符处理器
            private List<SpecialStrHandler> specialStrHandlers = new ArrayList<>();
    
            /**
             * Desc: 一个 key 可以设置多个值转换器
             */
            private Map<String/*key*/, List<ValueConverter>> valueConverterMap = new HashMap<>();
    
            private int pageSize;
    
    
            private Builder() {
            }
    
            /**
             * Desc: 设置csv文件的第一行头部
             *
             * @param header 头一行,可以直接传字符串并以逗号隔开,或者是国际化文件中的key
             */
            public Builder header(String header, Object... args) {
                this.header = I18nUtil.get(header, header);
                return this;
            }
    
            /**
             * Desc: 设置每一列对应的key,可以一次传递多个参数,
             * 或者一个参数中包含多个key,以逗号隔开,或者多次调
             * 用该方法设置多次
             * 例如: keys("id","name", "createTime", "updateTime");
             * 或者:keys("id,name,createTiime,updateTime");
             *
             * @param keys
             */
            public Builder keys(String... keys) {
                ObjectU.forEach(ObjectU.asList(keys), key -> {
                    key = key.replaceAll("[\\s]+", "");
                    if (key.contains(COLUMN_SEPARATOR)) {
                        this.keys.addAll(ObjectU.asList(key.split(COLUMN_SEPARATOR)));
                    } else {
                        this.keys.add(key);
                    }
                });
                return this;
            }
    
            /**
             * Desc: 设置数据列表
             */
            private <V> void data(List<V> data) throws Exception {
                this.data = ObjectU.getOrThrow(null, () -> ObjectU.convert(data, item -> {
                    JSONObject jsonItem = (JSONObject) JSON.toJSON(item);
                    // 过滤
                    if (itemFilter != null && !itemFilter.test(jsonItem)) {
                        return null;
                    }
                    // 处理值
                    if (!valueConverterMap.isEmpty()) {
                        handleValue(jsonItem);
                    }
                    return jsonItem;
                }));
            }
    
            /**
             * Desc: 分批轮询获取需要写入到csv文件的数据,如果onGetData回调返回的是null或者空List则停止轮询
             * 每次轮询传递页参数pageData{@link PageData}
             */
            public Builder dataProvider(int pageSize, Function<JSONObject, List> dataProvider) {
                this.pageSize = pageSize;
                this.dataProvider = dataProvider;
                return this;
            }
    
            /**
             * Desc: 值转换器
             *
             * @param keys           需要设置值转换器的key,
             *                       可以单个,可以多个(用英文逗号"," 隔开),key之间可以包含空白符
             *                       同一个key可以设置多个值转换器,转换器将按设置对顺序被使用
             *                       当需要对所有的字段都转换是,传null
             * @param valueConverter 值转换器
             */
            public Builder valueConverter(String keys, ValueConverter valueConverter) throws Exception {
                if (valueConverter == null) {
                    return this;
                }
                if (keys == null) {
                    for (String key : this.keys) {
                        addConverter(key, valueConverter);
                    }
                    return this;
                }
    
    
                keys = keys.replaceAll("[\\s]+", "");
                if (keys.contains(COLUMN_SEPARATOR)) {
                    for (String key : keys.split(COLUMN_SEPARATOR)) {
                        addConverter(key, valueConverter);
                    }
                } else {
                    addConverter(keys, valueConverter);
                }
                return this;
            }
    
    
            /**
             * Desc: 处理特殊字符
             *
             * 注:该方法可以多次调用,字符串的处理结果按照调用的顺序依次执行
             */
            public Builder forSpecialStr(String fromStr, String toStr, boolean fromStrIsRegex) {
                specialStrHandlers.add(SpecialStrHandler.get(fromStr, toStr, fromStrIsRegex));
                return this;
            }
    
    
    
            /**
             * Desc: 为指定 的key添加值转换器
             */
            private void addConverter(String key, ValueConverter convert) {
                List<ValueConverter> converters = valueConverterMap.get(key);
                if (converters == null) {
                    converters = new ArrayList<>();
                }
                converters.add(convert);
                valueConverterMap.putIfAbsent(key, converters);
            }
    
            /**
             * Desc: 值处理
             */
            private void handleValue(JSONObject item) throws Exception {
                for (Map.Entry<String, List<ValueConverter>> entry : valueConverterMap.entrySet()) {
                    for (ValueConverter converter : entry.getValue()) {
                        String key = entry.getKey();
                        Object value = converter.convert(key, item.get(key));
                        item.put(key, value);
                    }
                }
            }
    
            /**
             * Desc: 数据过滤器,对于每个数据项都会回调这个过滤器,
             * 返回true表示保留,
             * 返回false表示舍弃当前数据项,这种情况该行数据将不会被导出到csv文件中
             * <p>
             * 另外:还可以通过该回调修改数据项中的值
             */
            public Builder dataFilter(Predicate<JSONObject> dataFilter) throws Exception {
                this.itemFilter = dataFilter;
                return this;
            }
    
            public void into(String filePath) throws Exception {
                File file = new File(filePath);
                file.getParentFile().mkdirs();
                if (file.exists()) {
                    file.delete();
                }
                file.createNewFile();
                into(file);
            }
    
            public void into(File file) throws Exception {
                try (FileOutputStream outputStream = new FileOutputStream(file)) {
                    into(outputStream);
                } catch (Exception e) {
                    logger.warn("[ Builder - into - error ]: 发生异常: ", e);
                    throw e;
                }
            }
    
            /**
             * Desc:
             *
             * @param fileNamePrefix 文件名前缀
             */
            public void into(HttpServletResponse response, String fileNamePrefix) throws Exception {
                String fileName = fileNamePrefix + DateU.format("yyyyMMdd") + DateU.toMillis() + ".csv";
                response.setCharacterEncoding(ENCODING.toString());
                response.setContentType("text/csv;charset=" + ENCODING);
                response.setHeader("Content-Disposition", "attachment; filename=" + new String((fileName).getBytes(), ENCODING));
                into(response.getOutputStream());
            }
    
            public void into(OutputStream outputStream) throws Exception {
                ObjectU.throwNull(dataProvider, "dataProvider 为 null");
    
                if (header != null) {
                    outputStream.write(header.getBytes(ENCODING));
                    outputStream.write(ROW_SEPARATOR.getBytes(ENCODING));
                }
    
                int pageId = 1;
                JSONObject pageData = new JSONObject();
                while (true) {
                    pageData.clear();
                    pageData.put("pageID", pageId++);
                    pageData.put("pageSize", pageSize);
                    pageData.put("offset", (pageData.getInteger("pageID") - 1) * pageSize);
    
                    //noinspection unchecked
                    data(dataProvider.apply(pageData));
    
                    if (ObjectU.empty(data)) {
                        break;
                    }
                    writeData(outputStream);
                }
    
                outputStream.flush();
            }
    
            /**
             * Desc: 将data 写入到outputStream
             */
            private void writeData(OutputStream outputStream) throws Exception {
                for (JSONObject row : data) {
                    byte[] rowBytes = ObjectU.reduce(keys, new StringJoiner(COLUMN_SEPARATOR), (key, stringJoiner) -> {
                        // 对于每一个单元值,将所有的空白符和"," 替换为单个空格
                        stringJoiner.add(TAB + handleSpecialStr(row.get(key)));
                        return stringJoiner;
                    }).toString().getBytes(ENCODING);
                    outputStream.write(rowBytes);
                    outputStream.write(ROW_SEPARATOR.getBytes(ENCODING));
                }
            }
    
    
            /**
             * Desc: 对值进行特殊字符处理
             */
            private String handleSpecialStr(Object source) {
                String sourceStr = source instanceof Double ? BigDecimal.valueOf(((Double) source)).toPlainString() : ObjectU.str(source);
    
                if (specialStrHandlers.isEmpty()) {
                    return sourceStr.replaceAll("[,\\s]+", " ");
                }
                for (SpecialStrHandler specialStrHandler : specialStrHandlers) {
                    sourceStr = specialStrHandler.handle(sourceStr);
                }
                return sourceStr;
            }
    
    
            public static void main(String[] args) throws Exception {
    
                List<Map<String, Integer>> data = ObjectU.asList(ObjectU.asMap("1", 1, "2", 2, "3", 3));
                // 注意:CsvWriter在实际使用需要使用注解注入,例如@Autowired
                CsvWriter.open()
                        // header 可以是直接要展示的字符串或者是国际化文件中的key
                        .header("一,二,三")
                        // 数据项的字段名
                        .keys("1, 2, 3")
                        .forSpecialStr(",", " ", false)
                        // 分页导出数据,此场景适用于需要导出的数据量很大时使用,否则可以简单的使用上面的方法
                        .dataProvider(1000, pageData -> {
                            // 根据当前分页参数获取分页数据
                            if (pageData.getInteger("pageID") > 10) {
                                return null;
                            }
                            return ObjectU.asList(ObjectU.asMap("1", 1, "2", 2, "3", 3));
                        })
                        .valueConverter("1, 2", ScaleConverter.of())
                        // item 过滤器
                        .dataFilter(item -> {
                            // 返回true则保留该数据项
                            item.put("1", 11);
                            return true;
                        })
                        // 将生成的 csv 导出到指定文件中,该方法有多个重载方法可以使用
                        .into("/Users/Jinphy/Desktop/temp.csv");
            }
        }
    
    }
    
    

    相关文章

      网友评论

          本文标题:CSV工具类

          本文链接:https://www.haomeiwen.com/subject/vhzmgqtx.html