CSV工具类

2019-04-23  本文已影响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");
        }
    }

}

上一篇 下一篇

猜你喜欢

热点阅读