Using EasyExcel's complete springboot +vue front and rear ends to export and download excel tables

Posted by AdB on Wed, 05 Jan 2022 05:58:41 +0100

Write in front

It's only for record. It's a great honor to help a little partner who is still lost.
In the past two or three days, I have done a good job in exporting and downloading excel tables using easyexcel. I also feel a lot. In the humanitarian spirit of planting trees by predecessors and enjoying the cool by future generations, I quickly code this article to deepen my memory.

text

1. Introduction and use of easyexcel in springboot backend

1.1 introducing dependencies
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>easyexcel</artifactId>
</dependency>
1.2 interface serviceImpl method

xxxServiceImpl.java

public void exportExcelData(String wo_id, HttpServletResponse response) throws BusinessException {

    // Note that some students have reported that using swagger will lead to various problems. Please use the browser or postman directly
    try {
        // template file
        //Here, getResourceAsStream is used to obtain the Excel template file stream packaged by the server;
        //If the getPath method is used to obtain the file address, the local ieda environment can obtain it, and it will become invalid after uploading to the server. All adopted streams can take effect. The specific reasons have not been carefully checked yet. Interested children's shoes can try by themselves!
        //InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("templates/excelTemplate.xls");
        // Get header data
        if (wo_id == null || wo_id.isEmpty()) {
            throw new BusinessException(BusinessCodeEnum.PARAMETER_ERROR, "Work order id Get empty!");
        }
        // The following two lines are my own business. Don't worry
        CkdPoInfo ckdPoInfo = ckdPoInfoMapper.selectByPrimaryKey(wo_id);
        String work_order = ckdPoInfo.getWork_order();

        List<ExportExcelData> excelDataList = getData(wo_id);  // Get excel table body data from database
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        //Set header Center
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //Content strategy
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //Set horizontal center
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); //Because the merge policy is customized, the default merge policy here is not used
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // Here is urlencoder Encode can prevent Chinese garbled code. Of course, it has nothing to do with easyexcel
        String fileName = URLEncoder.encode(work_order+"Report export test", "UTF-8").replaceAll("\\+", "%20");
//            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        response.addHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        response.addHeader("Access-Control-Expose-Headers", "Content-disposition");
        // Custom merge strategy, important
        Map<String, List<RowRangeDto>> srategyMap = ExcelUtil.addMergeStrategy(excelDataList);

        // Here, you need to set not to close the flow;
        EasyExcel.write(response.getOutputStream(), ExportExcelData.class).autoCloseStream(Boolean.FALSE)
                // Register merge policy
                .registerWriteHandler(new BizMergeStrategy(srategyMap)) // The custom merge policy is called here
                .registerWriteHandler(ExcelUtil.CellStyleStrategy()) // Call a custom tool class
                .sheet("xxx Information table")
                .doWrite(excelDataList); // Write the acquired list data set
    } catch (Exception e) {
        // Reset response
        response.reset();
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
//            Map<String, String> map = MapUtils.newHashMap();
//            map.put("status", "failure");
//            map.put("message", "file download failed" + e.getMessage());
//            try {
//                response.getWriter().println(JSON.toJSONString(map));
//            } catch (IOException ex) {
//                ex.printStackTrace();
//            }
        e.printStackTrace();
        throw new BusinessException(BusinessCodeEnum.PARAMETER_ERROR, "export excel Failed!");
    }
}
  • Above try_catch statement calls easyexcel to write the file stream. I wrote it in the class method of serviceImpl, or I can write this paragraph directly in the controller. All individual business layer codes are placed in the service, and the controller layer is only responsible for calling the service layer.
  • wo_id is the parameter I need to transfer. It can be transferred or not according to my actual situation.
  • Excel datalist is the list set encapsulated by me to obtain my database data, and getData() is the specific implementation method;
  • The implementation of getData() is different in different scenarios, so we won't post its implementation here
  • Custom merge policies and custom ExcelUtil tool classes will be placed below. It's not urgent.
1.3 provide a method to de duplicate the list set (remove duplicates and merge value values according to the same key)
public List<CkdMaterialPackage> getNewList(List<CkdMaterialPackage> oldList) {

   List<CkdMaterialPackage> newList = new ArrayList<>();
   HashMap<NewCkdMtrPackage, CkdMaterialPackage> tempMap = new HashMap<NewCkdMtrPackage, CkdMaterialPackage>();
   // Remove duplicate key s
   for (CkdMaterialPackage ckdMaterialPackage : oldList) {
       String odm_pn = ckdMaterialPackage.getOdm_pn();  // The item number acts as the key name
       String exporter_pn = ckdMaterialPackage.getExporter_pn();
       String importer_pn = ckdMaterialPackage.getImporter_pn();

       NewCkdMtrPackage newCkdMtrPackage = new NewCkdMtrPackage();  // As the key value of map
       // Assign a value to the attribute, and the class object composed of the following three parts is used as the key
       newCkdMtrPackage.setOdm_pn(odm_pn);
       newCkdMtrPackage.setExporter_pn(exporter_pn);
       newCkdMtrPackage.setImporter_pn(importer_pn);
		// 
       if (tempMap.containsKey(newCkdMtrPackage)) {

           // Merge value s of the same item No
           ckdMaterialPackage.setQuantity(tempMap.get(newCkdMtrPackage).getQuantity() + ckdMaterialPackage.getQuantity());
           // hashmap does not allow duplicate keys. When there are duplicate keys, the value corresponding to the previous key will be overwritten
           tempMap.put(newCkdMtrPackage, ckdMaterialPackage);
       } else {
           tempMap.put(newCkdMtrPackage, ckdMaterialPackage);
       }
   }

   for (Map.Entry<NewCkdMtrPackage, CkdMaterialPackage> entry : tempMap.entrySet()) {
       newList.add(entry.getValue());
   }

   return newList;
}
  • This method is to pass in an original data list, and then return a list after de coincidence
  • List < CkdMaterialPackage > the CkdMaterialPackage in the oldlist is my own database table entity class. Replace it with your own
  • NewCkdMtrPackage is a specially defined entity class. It can be understood that the attributes inside act as keys as a whole; For example, a list set contains several elements of name,sex,height and score. I want to merge the duplicate name,sex and height and sum the score. That means I have more than one column merged. Then encapsulate these duplicates into a class and execute them as a key as a whole.
1.4 BizMergeStrategy merge policy class
public class BizMergeStrategy extends AbstractMergeStrategy {

    private Map<String, List<RowRangeDto>> strategyMap; // RowRangeDto row start end range class
    private Sheet sheet;

    public BizMergeStrategy(Map<String, List<RowRangeDto>> strategyMap) {
        this.strategyMap = strategyMap;
    }

    @Override
    protected void merge(org.apache.poi.ss.usermodel.Sheet sheet, Cell cell, Head head, Integer integer) {
        this.sheet = sheet;
        if (cell.getRowIndex() == 1 && cell.getColumnIndex() == 0) {
            /**
             * Ensure that each cell is merged once. If the above judgment is not added, because it is operated by one cell,
             * For example, merge A2:A3. When the cell is A2, merge A2 and A3, but when the cell is A3, merge A2 and A3,
             * At this time, A2 and A3 are merged cells
             */
            for (Map.Entry<String, List<RowRangeDto>> entry : strategyMap.entrySet()) {
                Integer columnIndex = Integer.valueOf(entry.getKey());
                entry.getValue().forEach(rowRange -> {
                    //Add a merge request
                    sheet.addMergedRegionUnsafe(new CellRangeAddress(rowRange.getStart(),
                            rowRange.getEnd(), columnIndex, columnIndex));
                });
            }
        }
    }

}
  • RowRangeDto
/**
 * dto class for starting and ending positions of line segments
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class RowRangeDto {

    private int start;
    private int end;
}
1.5 custom ExcelUtil tool class
public class ExcelUtil{

    public static Map<String, List<RowRangeDto>> addMergeStrategy(List<ExportExcelData> excelDataList) {
        Map<String, List<RowRangeDto>> strategyMap = new HashMap<>();
        ExportExcelData preExcelData = null;
        for (int i = 0; i < excelDataList.size(); i++) {
            ExportExcelData currExcelData = excelDataList.get(i);
            if (preExcelData != null) {
                //Judge whether to merge from the second line
                if (currExcelData.getPallet_number().equals(preExcelData.getPallet_number())) {
                    //If the pallet number is the same, the pallet number, pallet gross weight and pallet size can be combined
                    fillStrategyMap(strategyMap, "0", i);
                    fillStrategyMap(strategyMap, "1", i);
                    fillStrategyMap(strategyMap, "2", i);
                    //If the pallet number is the same and the cartoon box number is the same, three columns of cartoon box number, single box gross weight and single box size can be combined
                    if (currExcelData.getCarton_number().equals(preExcelData.getCarton_number())) {
                        fillStrategyMap(strategyMap, "3", i);
                        fillStrategyMap(strategyMap, "4", i);
                        fillStrategyMap(strategyMap, "5", i);
//                        //If the pallet number and cartoon box number are the same, and the material item number is the same, the material information in the column of material item number can be consolidated, which has been consolidated during query
//                        if (currExcelData.getCoopOrg().equals(preExcelDto.getCoopOrg())) {
//                            fillStrategyMap(strategyMap, "2", i);
//                        }
                    }
                }
            }
            preExcelData = currExcelData;
        }
        return strategyMap;
    }


    private static void fillStrategyMap(Map<String, List<RowRangeDto>> strategyMap, String key, int index) {
        List<RowRangeDto> rowRangeDtoList = strategyMap.get(key) == null ? new ArrayList<>() : strategyMap.get(key);
        boolean flag = false;
        for (RowRangeDto dto : rowRangeDtoList) {
            //Whether the end index in the segmented list is the index of the previous row. If so, the index + 1
            if (dto.getEnd() == index) {
                dto.setEnd(index + 1);
                flag = true;
            }
        }
        //If not, add a new segment
        if (!flag) {
            rowRangeDtoList.add(new RowRangeDto(index, index + 1));
        }
        strategyMap.put(key, rowRangeDtoList);
    }

    public static HorizontalCellStyleStrategy CellStyleStrategy(){
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        //Set background color
        headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        //Set header font
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short)13);
        headWriteFont.setBold(true);
        headWriteCellStyle.setWriteFont(headWriteFont);
        //Set header Center
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //Content strategy
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //Set horizontal center
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
        return horizontalCellStyleStrategy;
    }
}

Controller method:

/**
     * Export and download excel report
     * @param wo_id
     * @param response
     * @throws BusinessException
     */
    @GetMapping("/downloadExcel")
    public void exportExcelData(
            @RequestParam(value = "wo_id") String wo_id,
            HttpServletResponse response
    ) throws BusinessException {
        // Get report data
        ckdPoInfoService.exportExcelData(wo_id, response);
    }

The above are all back-end interfaces and methods.

2. The Vue front end calls the background download excel interface to complete the download by clicking the button

2.1 vue code corresponding to the figure above
<span v-if="(scope.row.status&63)===60">
        <el-popconfirm   // Bubble box
            style="margin-left:20px;"
            @confirm="export_excel(scope.row.wo_id)"
            title="Export customs declaration?"
        >
            <el-button type="text" slot="reference" size="small" icon="el-icon-document-copy">Export customs declaration</el-button>
        </el-popconfirm>
</span>
2.2 export_excel() method
export_excel(wo_id){
		  //scope.row.wo_id
		  console.log("wo_id = "+wo_id);
		  const url = this.BASE_API_URL + 'springbootApi/downloadExcel?wo_id='+wo_id;
		  axios.get(url, 
		  	{
		  		responseType: 'blob'        <!--The response type must be set to binary stream-->
		  	})
		  .then((res) => {
		    if (!res) return
		
		    console.log("res data = "+res.data);
		    let blob = new Blob([res.data], {type: 'application/vnd.ms-excel;charset=utf-8'}) // file type
		    console.log(res.headers['content-disposition']); // Get the filename from the headers of the response, and the back-end response The file name set by setheader ("content disposition", "attachment; filename =" + filename + ". Xlsx");
		    //Take the element of array [1] as the file name with = split
		    let filename = window.decodeURI(res.headers['content-disposition'].split('=')[1])
		    let url = window.URL.createObjectURL(blob);  // Create download link
		    let aLink = document.createElement("a");    // href attribute assigned to a tag
		    aLink.style.display = "none";
		    aLink.href = url;
		    aLink.setAttribute("download", filename);
		    document.body.appendChild(aLink);   // Attach the a label
		    aLink.click();          // a tag click event
		    document.body.removeChild(aLink);  // Remove a tag
		    window.URL.revokeObjectURL(url);   // Destroy download link
		
		    return this.$message.success("Export of customs declaration succeeded");
		  }).catch(function (error) {
		    console.log(error);
		  })
}

3. vue calls the background interface to download excel in various ways (this section draws lessons from others' summary)

Method 1: directly through a label

<a href="/images/logo.jpg" download="logo" />

Advantages: simple and convenient.
Disadvantages: This download method only supports Firefox and Chrome, does not support IE and Safari, and the compatibility is not good enough.

Method 2: through window location

window.location = 'http://127.0.0.1:8080/api/download?name=xxx&type=xxx'

In fact, it is similar. I directly enter the interface address in the browser url address bar and press enter to download.
Advantages: simple and convenient.
Disadvantages: only get requests can be made, which is inconvenient when there is token verification.

Method 3: axios requests the background interface (directory 2 is based on this)

4. Summarize some problems encountered to avoid small partners stepping on the pit

① First, after the back-end interface is written, I directly enter the api interface into the browser to call the test:

http://localhost:8989/xxx/downloadExcel?wo_id=33b948460598420eb533d62930c9

Results the downloaded file and save box pops up to prove that the back-end interface is available; When testing, you can write and test the front and back ends separately.

② I define that the download logic uses easyexcel, but the previous logic of uploading files used Excel kit, and then the poi version is 3.1. However, my easyexcel contains version 4.0 poi, so some methods of uploading file logic report errors.
Solution: POM You can manually add a higher version of the conflicting jar package to the XML dependency.
③ The back-end has set the name of the downloaded excel table defined in the header, but the vue front-end does not get:

// Here is urlencoder Encode can prevent Chinese garbled code. Of course, it has nothing to do with easyexcel
  String fileName = URLEncoder.encode(work_order+"Report export test", "UTF-8").replaceAll("\\+", "%20");
//            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
  response.addHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
  //response.addHeader("Access-Control-Expose-Headers", "Content-disposition");  Not yet
console.log(res.headers['content-disposition']);  // The console output here is undefined

After checking the data, it is found that content disposition must be added to access control expose headers when setting the request header in the background, and the front end can get it.
That is, add a code in the background:

response.addHeader("Access-Control-Expose-Headers", "Content-disposition");

Then vue can get the file name.

Reference article:
1. Vue project uses axios request interface to download excel (with front-end code)
2. Downloading binary stream files with axios in vue
3. Brother wrote great
4. The boss set the template filling. You can have a look. You can also download compressed packages and leave a portal
5. This helps me the most
6. github source code easyexcel address
7. Use easy excel to export excel with complex header
8. What feels useful is here

Split line====

  1. There is an official document of easypoi
  2. Using EasyPoi to export complex one to many excel tables

Topics: Java Spring Boot Vue.js