问题情景:
最近项目中遇到了根据第三方系统传递过来的参数,封装为List<实体类对象>后,将该实体类转换为csv文件,然后上传到远程的sftp服务器指定目录的需求。
实现思路:
- List<实体类对象>转为csv文件的过程。通过OpenCsv实现。
阻塞点:
1.最开始遇到了生成的csv文件的第一行生成的字段名,变成了实体类的大写字段名了,例如userName变为USERNAME并且排序混乱。
2.通过查阅资料找到了以下两个注解,后期却发现两个注解不能同时出现。详见文章
为什么 opencsv 在写入文件时将 csv 标头大写
@CsvBindByName(column = "TradeID")
@CsvBindByPosition(position = 0)
解决方案:
方案一:
创建自定义MappingStrategy :
class CustomMappingStrategy extends ColumnPositionMappingStrategy {
private static final String[] HEADER = new String[]{"TradeID", "GWML GUID", "MXML GUID", "GWML File", "MxML File", "MxML Counterparty", "GWML Counterparty"};
@Override
public String[] generateHeader() {
return HEADER;
}
}
并在StatefulBeanToCsvBuilder使用它:
final CustomMappingStrategy mappingStrategy = new CustomMappingStrategy<>();
mappingStrategy.setType(MappingsBean.class);
final StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(writer)
.withMappingStrategy(mappingStrategy)
.build();
beanToCsv.write(makeFinalMappingBeanList());
writer.close()
在MappingsBean类中,我们留下了CsvBindByPosition注释 - 以控制排序(在此解决方案中, CsvBindByName注释)。 由于自定义映射策略,标题列名称包含在生成的 CSV 文件中。
此解决方案的缺点是,当我们通过CsvBindByPosition注释更改列顺序时,我们必须手动更改自定义映射策略中的HEADER常量。
方案二:
该方案可以根据实体类字段名称生成CSV第一行,注意一定不要加@CsvBindByName注解,具体文章可以参考opencsv 将对象数组导出为 csv 文件时、文件列按对象字段定义顺序排序的实现
@SneakyThrows
public <T> String generateCsvFile(List<? extends T> exportResults, String fileName)
throws IOException, CsvDataTypeMismatchException, CsvRequiredFieldEmptyException {
String finalFileName = new File(nginxDownloadPath,
fileName + System.currentTimeMillis() + ".csv").getPath();
Writer writer = new FileWriter(finalFileName);
CSVWriter csvWriter = new CSVWriter(
writer,
CSVWriter.DEFAULT_SEPARATOR,
CSVWriter.DEFAULT_QUOTE_CHARACTER,
CSVWriter.NO_ESCAPE_CHARACTER,
CSVWriter.DEFAULT_LINE_END);
csvWriter.writeNext(header);
if (exportResults.size() > 0) {
//写内容
StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder<T>(writer).
withMappingStrategy(new OrderColumnMappingStrategy(exportResults.get(0).getClass())).
withIgnoreField(exportResults.get(0).getClass(), Arrays.stream(exportResults.get(0).getClass().getDeclaredFields()).filter(one -> {
one.setAccessible(true);
return one.isAnnotationPresent(CsvIgnore.class);
}).findFirst().orElse(null)).
build();
beanToCsv.write(exportResults);
}
csvWriter.close();
writer.close();
return finalFileName;
}
public class OrderColumnMappingStrategy<T> extends HeaderColumnNameMappingStrategy<T> {
private Locale errorLocale = Locale.getDefault();
public OrderColumnMappingStrategy(Class<? extends T> type) {
super();
this.setErrorLocale(errorLocale);
this.setType(type);
}
@Override
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
if (type == null) {
throw new IllegalStateException(ResourceBundle
.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
.getString("type.before.header"));
}
if (headerIndex.isEmpty()) {
List<String> realHeaderList = new ArrayList<>();
/**getFieldNameForCsvHeader()方法是通过反射获取对象的字段, 字段
是按照定义顺序返回的. 这里就不贴出代码了*/
getFieldNameForCsvHeader(type).forEach(one -> {
realHeaderList.add(one.toUpperCase());
});
String[] header = realHeaderList.toArray(new String[0]);
headerIndex.initializeHeaderIndex(header);
return header;
}
return headerIndex.getHeaderIndex();
}
}