/**
* 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");
}
}
}
网友评论