import io.swagger.annotations.ApiModelProperty;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* CSV文件,全稱(chēng)Comma-separated values珍特,就是逗號(hào)分隔的數(shù)據(jù)文件备闲。
* CSV格式規(guī)范:
* MS-DOS-style lines that end with (CR/LF) characters (optional for the last line)
* Jiger: {使用回車(chē)換行(兩個(gè)字符)作為行分隔符檐束,最后一行數(shù)據(jù)可以沒(méi)有這兩個(gè)字符。}
* An optional header record (there is no sure way to detect whether it is present, so care is required when importing).
* Jiger:{標(biāo)題行是否需要饺著,要雙方顯示約定}.
* Each record "should" contain the same number of comma-separated fields.
* Jiger:{每行記錄的字段數(shù)要相同,使用逗號(hào)分隔。} 逗號(hào)是默認(rèn)使用的值饵较,雙方可以約定別的。
* Any field may be quoted (with double quotes).
* Jiger:{任何字段的值都可以使用雙引號(hào)括起來(lái)}. 為簡(jiǎn)單期間遭赂,可以要求都使用雙引號(hào)循诉。
* Fields containing a line-break, double-quote, and/or commas should be quoted. (If they are not, the file will likely be impossible to process correctly).
* Jiger:{字段值中如果有換行符,雙引號(hào)撇他,逗號(hào)的茄猫,必須要使用雙引號(hào)括起來(lái)狈蚤。這是必須的。}
* A (double) quote character in a field must be represented by two (double) quote characters.
* Jiger:{如果值中有雙引號(hào)划纽,使用一對(duì)雙引號(hào)來(lái)表示原來(lái)的一個(gè)雙引號(hào)}
*
* 根據(jù)swagger注解生成csv文件
* @Author: xuebin.liu
* @Date: 2021/5/30 14:17
*
*/
public class CsvUtil {
/**
* 使用Map作為緩存(提高對(duì)象解析速度)脆侮,ThreadLocal 處理緩存的線程安全問(wèn)題
*/
private static ThreadLocal<ConcurrentHashMap<Class ,Field[]>> cacheHelper = new ThreadLocal();
/**
* 根據(jù)csv格式規(guī)范處理csv單元格中特殊字符 " and ,
* @param singleVal
* @return
*/
public static Object dealSpecialObject(Object singleVal) {
if (singleVal == null) {
return null;
}
if (singleVal instanceof String) {
boolean hasSpecialChar = false;
String val = (String) singleVal;
if (val.contains("\"")) {
val = val.replaceAll("\"", "\"\"");
hasSpecialChar = true;
} else if (val.contains(",")) {
hasSpecialChar = true;
} else if (val.contains("\r") || val.contains("\n")) {
hasSpecialChar = true;
}
return hasSpecialChar ? "\"".concat(val).concat("\"") : val;
} else {
return singleVal;
}
}
/**
* 自動(dòng)追加換行符
* @param sb
*/
public static void line (StringBuilder sb ,String lineContent){
sb.append(lineContent).append("\r\n");
}
/**
* 新增空白行
* @param sb
*/
public static void line (StringBuilder sb ){
sb.append(",,").append("\r\n");
}
/**
* 新增單元格
* @param sb
* @param data
*/
public static void cell (StringBuilder sb ,Object data){
sb.append(dealSpecialObject(data)).append(",");
}
/**
* 列表數(shù)據(jù)為空,展示空白行
* @param sb
*/
private static void lineAsEmptyList(StringBuilder sb){
sb.append(",列表無(wú)數(shù)據(jù),").append("\r\n");
}
/**
* 創(chuàng)建表頭
* @param clazz
* @param sb
*/
private static void buildHeader(Class<?> clazz ,StringBuilder sb){
Field[] fields = clazz.getDeclaredFields();
CsvUtil.buildHeader(Arrays.asList(fields) , sb);
}
/**
* 創(chuàng)建表頭
* @param fields
* @param sb
*/
private static void buildHeader(List<Field> fields ,StringBuilder sb){
StringBuilder headerLine = new StringBuilder();
for (Field objField : fields) {
ApiModelProperty properties = objField.getAnnotation(ApiModelProperty.class);
String propertyName = properties.value();
CsvUtil.cell(headerLine, propertyName);
}
CsvUtil.line(sb, headerLine.toString());
}
/**
* 獲取對(duì)象的字段勇劣,并進(jìn)行列表字段優(yōu)先排序
* @param value
* @return
*/
private static Field[] getObjFields (Object value){
Class clazz = value.getClass();
if(cacheHelper.get().containsKey(clazz)){
return cacheHelper.get().get(clazz);
}else{
Field[] fields = value.getClass().getDeclaredFields();
Arrays.sort(fields,((o1, o2) -> {
if(o1.getType().getClass().equals(List.class)){
return 1 ;
}
return 0;
}));
cacheHelper.get().putIfAbsent(clazz,fields);
return fields;
}
}
/**
* 處理普通對(duì)象,普通對(duì)象的字段中包含List也可處理
* @param value
* @param sb
* @param isNeedHeader
*/
private static void processObj(Object value,StringBuilder sb,boolean isNeedHeader){
Field[] fields = getObjFields(value);
StringBuilder bodyLine = new StringBuilder();
List<Field> noListField = new ArrayList<>();
for(Field objField :fields){
String fieldName = objField.getName();
Object fieldVal = ReflectUtil.getFieldValueByName(value,fieldName);
if(fieldVal instanceof List){
processList( fieldVal, sb);
continue;
}
noListField.add(objField);
CsvUtil.cell(bodyLine,fieldVal);
}
if(isNeedHeader){
buildHeader(noListField , sb);
}
CsvUtil.line(sb,bodyLine.toString());
}
/**
* 處理列表對(duì)象
* @param value
* @param sb
*/
private static void processList(Object value,StringBuilder sb){
int count = 0;
if(value==null ||((List)value).size()<=0){
CsvUtil.lineAsEmptyList(sb);
}
for( Object instance :(List)value){
if(count == 0){
buildHeader(instance.getClass() , sb);
}
processObj( instance, sb,false);
count ++;
}
}
/**
* 處理Map里面的數(shù)據(jù)
* @param value
* @param sb
*/
private static void processVal(Object value,StringBuilder sb){
if(value instanceof List){
processList( value, sb);
// todo
}else{
processObj( value, sb,true);
}
}
/**
* 根據(jù)數(shù)據(jù)集配合swagger注解獲取CSV文件內(nèi)容
* @param data
* @return
*/
public static String getCsvContent(LinkedHashMap<String,Object> data) {
StringBuilder content = new StringBuilder();
try {
CsvUtil.initCache();
data.entrySet().forEach((entry) -> {
CsvUtil.line(content, (String) dealSpecialObject(entry.getKey()));
processVal(entry.getValue(), content);
CsvUtil.line(content);
});
} finally {
CsvUtil.releaseCache();
}
return content.toString();
}
/**
* 下載csv文件靖避,文件名不需要.csv后綴,字符編碼處理成GBK,防止在MS-EXCEL中字符亂碼
* @param fileName
* @param content
* @param response
*/
public static void downLoadCsv(String fileName,String content, HttpServletResponse response) {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type","application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode( fileName + ".csv", "UTF-8"));
response.getOutputStream().write(content.getBytes("GBK"));
response.getOutputStream().flush();
response.getOutputStream().close();
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 初始化當(dāng)前線程的ThreadLocal變量
*/
private static void initCache(){
cacheHelper.set(new ConcurrentHashMap<Class ,Field[]>());
}
/**
* 釋放內(nèi)存數(shù)據(jù),防止OOM
*/
private static void releaseCache(){
cacheHelper.get().clear();
cacheHelper.remove();
}
使用示例
List<TimeNodeVo> getGmvTrend(DashboardQueryDto dto);
@Data
@ApiModel(value="時(shí)間節(jié)點(diǎn)通用")
@Builder
public class TimeNodeVo {
@ApiModelProperty(value = "時(shí)間")
private String time ;
@ApiModelProperty(value = "數(shù)值")
private BigDecimal num ;
}
LinkedHashMap<String ,Object> kvs = new LinkedHashMap<>(16);
kvs.put("4.流水趨勢(shì)",learnService.getGmvTrend(dto));
String content = CsvUtil.getCsvContent(kvs);
CsvUtil.downLoadCsv(fileName,content,response);
結(jié)果
屏幕截圖 2021-06-02 145156.jpg