利用swagger注解解析對(duì)象數(shù)據(jù)生成csv文件

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容