Springboot I conveniently packaged a universal excel export tool, which can export anything.

Preface


As the title states, this gadget does not limit which table you are checking or what category you are using.

I'll just take the shuttle and give you a meal.

I know that this is something many people have thought about. At least I have received many people asking me this similar question.

I also told them, but they just didn't do it. In fact, it's really simple.

What to do if you don’t take action? I'll take action.
 

Don't talk too much and start doing it.

text

The gameplay is very simple. I have written an article before that uses the csv file content format to export excel files.

If you haven’t seen it, what are you waiting for? Go check it out now:

Springboot At that time, I put my hands in my pockets and wrote an excel export by hand.

Effect to be achieved:


Class is undefined, User? Student ? District ? Not sure.

But the function we encapsulate must be enough to support different classes. We automatically read and traverse the list, and then export the generated file.
 

What is the core idea? 
 

In fact, we still make use of the essence of the content format of the csv file. Look at these two pictures:


We want to implement a universal class to export excel! ! !

What is the idea:

① We take out the classes in the collection list of uncertain classes.

Reflex your hand, take out the attribute names inside, and splice the first row of table row title names.


②Splicing content
Because the class is uncertain, we use reflection to throw all the field attributes of the class as keys into the map, and
at the same time throw the values ​​into the value.

In this way, when we splice content, we only need to traverse and splice according to the map.

1. Dependence

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.15</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>3.15</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.69</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>
        

2. I have encapsulated the core tool classes and functions

MyCsvFileUtil.java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author JCccc
 * @Remark 是我
 */
@Slf4j
public class MyCsvFileUtil {
    public static final String FILE_SUFFIX = ".csv";
    public static final String CSV_DELIMITER = ",";
    public static final String CSV_TAIL = "\r\n";
    protected static final String DATE_STR_FILE_NAME = "yyyyMMddHHmmssSSS";

    /**
     * 将字符串转成csv文件
     */
    public static void createCsvFile(String savePath, String contextStr) throws IOException {

        File file = new File(savePath);
        //创建文件
        file.createNewFile();
        //创建文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        //将指定字节写入此文件输出流
        fileOutputStream.write(contextStr.getBytes("gbk"));
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    /**
     * 写文件
     *
     * @param fileName
     * @param content
     */
    public static void writeFile(String fileName, String content) {
        FileOutputStream fos = null;
        OutputStreamWriter writer = null;
        try {
            fos = new FileOutputStream(fileName, true);
            writer = new OutputStreamWriter(fos, "GBK");
            writer.write(content);
            writer.flush();
        } catch (Exception e) {
            log.error("写文件异常|{}", e);
        } finally {
            if (fos != null) {
                IOUtils.closeQuietly(fos);
            }
            if (writer != null) {
                IOUtils.closeQuietly(writer);
            }
        }
    }

    /**
     * 构建文件名称
     * @param dataList
     * @return
     */
    public static String buildCsvFileFileName(List dataList) {
        return dataList.get(0).getClass().getSimpleName() + new SimpleDateFormat(DATE_STR_FILE_NAME).format(new Date()) + FILE_SUFFIX;
    }

    /**
     * 构建excel 标题行名
     * @param dataList
     * @return
     */
    public static String buildCsvFileTableNames(List dataList) {
        Map<String, Object> map = toMap(dataList.get(0));
        StringBuilder tableNames = new StringBuilder();
        for (String key : map.keySet()) {
            tableNames.append(key).append(MyCsvFileUtil.CSV_DELIMITER);
        }
        return tableNames.append(MyCsvFileUtil.CSV_TAIL).toString();
    }

    /**
     * 构建excel内容
     * @param dataLists
     * @return
     */
    public static String buildCsvFileBodyMap(List dataLists) {
        //不管你传什么玩意,我都给你反射一手,搞成Map
        List<Map<String, Object>> mapList = new ArrayList<>();
        for (Object o : dataLists) {
            mapList.add(toMap(o));
        }
        //然后利用csv格式,对着map嘎嘎一顿拼接数据
        StringBuilder lineBuilder = new StringBuilder();
        for (Map<String, Object> rowData : mapList) {
            for (String key : rowData.keySet()) {
                Object value = rowData.get(key);
                if (Objects.nonNull(value)) {
                    lineBuilder.append(value).append(MyCsvFileUtil.CSV_DELIMITER);
                } else {
                    lineBuilder.append("--").append(MyCsvFileUtil.CSV_DELIMITER);
                }
            }
            lineBuilder.append(MyCsvFileUtil.CSV_TAIL);
        }
        return lineBuilder.toString();
    }

    /**
     * 类转map
     * @param entity
     * @param <T>
     * @return
     */
    public static<T> Map<String, Object> toMap(T entity){
        Class<? extends Object> bean = entity.getClass();
        Field[] fields = bean.getDeclaredFields();
        Map<String, Object> map = new HashMap<>(fields.length);
        for(Field field:fields){
            try {
                if(!"serialVersionUID".equals(field.getName())){
                    String methodName = "get"+field.getName().substring(0, 1).toUpperCase()+field.getName().substring(1);
                    Method method = bean.getDeclaredMethod(methodName);
                    Object fieldValue = method.invoke(entity);
                    map.put(field.getName(),fieldValue);
                }
            } catch (Exception e) {
                log.warn("toMap() Exception={}",e.getMessage());
            }
        }
        return map;
    }
}

 Notes on code (various small packages):
 

① Class to map 

② Reflection to map takes the field attribute name and splices the title

③ Convert list<uncertain class> into list<map>, and then splice the excel content 

 

Test code:
 

    @RequestMapping("/createCsvFileJcTest")
    public void createCsvFileJcTest() {
        //类不确定 随便怎么传都行
        List<District> districts = districtService.queryByParentCodes(Arrays.asList("110100"));
        //存放地址&文件名
        String fileName = "D:\\mycsv\\"+MyCsvFileUtil.buildCsvFileFileName(districts);
        //创建表格行标题
        String tableNames = MyCsvFileUtil.buildCsvFileTableNames(districts);
        //创建文件
        MyCsvFileUtil.writeFile(fileName, tableNames);
        //写入数据
        String contentBody = MyCsvFileUtil.buildCsvFileBodyMap(districts);
        //调用方法生成
        MyCsvFileUtil.writeFile(fileName, contentBody);
    }

Take a look at the effect:
 

 

Exported excel file content: 

 

Next, change the category and play:

 Then export to see the effect:

You can see that the data export is also OK: 

 

Yes, it's that simple. Of course, it's also a good introduction. I hope that after reading this article, you can learn from these reflection function methods and make more interesting encapsulations, such as adding some custom annotation analysis, such as adding Some front and rear interceptor extensions, etc.

Okay, that’s it for this article.

-------Continue to play with packaging when you have time---------

Oh, my readers came up with some great ideas:

 

Yes, the customer is not satisfied with the exported attribute name. Then get your hands dirty. Get custom annotations!

I'm never late for action!

JcExcelName.java 

/**
 * @Author : JCccc
 * @CreateTime : 2020/5/14
 * @Description :
 **/

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JcExcelName {

    String name() default "";

}

Then in the class you want to export, if you want to add a understandable name, add it. If you don’t want to add it, just use the attribute name:

 Just write another one, the new reflection analysis uses field attribute annotation value function:

    public static <T> List<String> resolveExcelTableName(T entity) {
        List<String> tableNamesList = new ArrayList<>();
        Class<? extends Object> bean = entity.getClass();
        Field[] fields = bean.getDeclaredFields();
        Map<String, Object> map = new HashMap<>(fields.length);
        for (Field field : fields) {
            try {
                if (!"serialVersionUID".equals(field.getName())) {
                    String tableTitleName = field.getName();
                    JcExcelName myFieldAnn = field.getAnnotation(JcExcelName.class);
                    String annName = myFieldAnn.name();
                    if (StringUtils.hasLength(annName)) {
                        tableTitleName = annName;
                    }
                    tableNamesList.add(tableTitleName);
                }
            } catch (Exception e) {
                log.warn("toMap() Exception={}", e.getMessage());
            }
        }
        return tableNamesList;
    }

Then the table title name format is spliced ​​according to the parsed annotation value column name:
 

    public static String buildCsvFileTableNamesNew(List<String> dataList) {
        StringBuilder tableNames = new StringBuilder();
        for (String name : dataList) {
            tableNames.append(name).append(MyCsvFileUtil.CSV_DELIMITER);
        }
        return tableNames.append(MyCsvFileUtil.CSV_TAIL).toString();
    }

Test to see the effect:

    public static void main(String[] args) {

        User user = new User();
        List<String> nameList = MapUtils.resolveExcelTableName(user);
        System.out.println(nameList.toString());
        String tableNames = buildCsvFileTableNamesNew(nameList);
        System.out.println(tableNames);

    }

The effect is great:

 Then backhand, we get the usage example in our previous article:

String tableNames = MyCsvFileUtil.buildCsvFileTableNamesNew( MyCsvFileUtil.resolveExcelTableName(dataList.get(0)));



 

Execute the sample interface and see the effect:

 

The file is out:

 Open it and see the effect:

 

Okay, that's it.

ps:

Springboot is the simplest combination of MYSQL data to implement EXCEL table export and data import


Springboot specifies a custom template to export an Excel file

SpringBoot exports multiple Excel files and compresses them into .zip format for downloading

Springboot gets the sheet column name of the imported Excel file

 Springboot imports and exports Excel, one-to-many relationships, compound tables, and merged cell data

Springboot At that time, I put my hands in my pockets and wrote an excel export by hand.

Guess you like

Origin blog.csdn.net/qq_35387940/article/details/129062470