1. 程式人生 > >上傳EXCEL檔案到後端,匯入並解析EXCEL的前後端實現(Vue.js + java後端)

上傳EXCEL檔案到後端,匯入並解析EXCEL的前後端實現(Vue.js + java後端)

vue.js前端,Java後端,如何匯入excel檔案,並且解析,本文給了前後端程式碼的實現,以及完美實踐OK之後的分享。

前端主要用了element-ui的upload元件。

關於這個元件的官方文件很少:http://element-cn.eleme.io/#/zh-CN/component/upload ,也沒仔細給個完整的demo,所以踩完坑寫個完整的部落格。

1.VUE前端demo

 <el-upload

          class="upload-demo"
          ref="upload"
          :action="uploadUrl()"
          :data="uploadData"
          name="excelFile"
          :on-preview="handlePreview"
          :on-remove="handleRemove"
          :file-list="fileList"
          :on-error="uploadFalse"
          :on-success="uploadSuccess"
          :auto-upload="false"
          :before-upload="beforeAvatarUpload">
          <el-button slot="trigger" size="small" type="primary">選取檔案</el-button>
          <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">批量匯入</el-button>
          <div slot="tip" class="el-upload__tip">只能上傳excel檔案</div>
          </el-upload>

關於每個欄位的意思:

  • uploadUrl() 是後臺介面(接受上傳的檔案並做後端的邏輯處理)

    !!!注意:uploadUrl方法中,直接return的是你的後端URL介面,可以是相對路徑,也可以是絕對路徑

    取相對路徑的時候,會自動加上請求所在頁面的字首htpp://XXX作為請求URL的字首,如果這樣拼接的不對,自己就加上絕對路徑:http://localhost:8080/XX 這樣類似的URL

  • upLoadData是上傳檔案時要上傳的額外引數,也可以不寫,將引數直接帶在URL請求中,見下面的demo

    形似: url + "?businessName=" + this.businessName

  • uploadError是上傳檔案失敗時的回掉函式,uploadSuccess是檔案上傳成功時的回掉函式,

  • beforeAvatarUpload是在上傳檔案之前呼叫的函式,可以在這裡進行檔案型別的判斷,對上傳格式及大小作限制

  • :auto-upload="false"是停止檔案自動上傳模式

  • name="excelFile"這裡是將匯入的EXCEL檔案命名,並傳給後端,所以後端介面的入參也要是這個名字,下面會貼程式碼。

  • :file-list="fileList" 顯示已上傳的檔案列表,效果見下圖3

  • <el-button slot="trigger" size="small" type="primary">選取檔案</el-button>

    <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">批量匯入</el-button>

    是為了實現將選取檔案和上傳分開,效果如下:

  • 關於上傳多個檔案

  • 首先是設定是否可以同時選中多個檔案上傳,這個也是<input type='file'>的屬性,新增multiple即可。另外el-upload元件提供了:limit屬性來設定最多可以上傳的檔案數量,超出此數量後選擇的檔案是不會被上傳的。:on-exceed繫結的方法則是處理超出數量後的動作。(但一般單個檔案批量匯入夠用了)程式碼如下:

    <el-upload      
    multiple     
    :limit="3"     
    :on-exceed="handleExceed">   
    </el-upload>

    上面每個方法對應的實現:

 methods: {
    uploadUrl: function() {
      return (
        "/fanxing/import/batchInsertShops" +
        "?businessName=" +
        this.businessName +
        "&businessStatus=" +
        this.businessStatus +
        "&businessType=" +
        this.businessType
      );
    },
    uploadSuccess(response, file, fileList) {
      if (response.status) {
        alert("檔案匯入成功");
      } else {
        alert("檔案匯入失敗");
      }
    },
    uploadFalse(response, file, fileList) {
      alert("檔案上傳失敗!");
    },
    // 上傳前對檔案的大小的判斷
    beforeAvatarUpload(file) {
      const extension = file.name.split(".")[1] === "xls";
      const extension2 = file.name.split(".")[1] === "xlsx";
      const extension3 = file.name.split(".")[1] === "doc";
      const extension4 = file.name.split(".")[1] === "docx";
      const isLt2M = file.size / 1024 / 1024 < 10;
         if (!extension && !extension2 && !extension3 && !extension4) {
           alert("上傳模板只能是 xls、xlsx、doc、docx 格式!");
          }
          if (!isLt2M) {
           console.log("上傳模板大小不能超過 10MB!");
         }
        return extension || extension2 || extension3 || (extension4 && isLt2M);
    },
    submitUpload() {
      if (this.businessType != null) {
        //觸發元件的action
        this.$refs.upload.submit();
      }
      if (this.businessType == null) {
        this.businessType = "businessType不能為空";
      }
    },
    handleRemove(file, fileList) {
      console.log(file, fileList);
    },
    handlePreview(file) {
      if (file.response.status) {
        alert("此檔案匯入成功");
      } else {
        alert("此檔案匯入失敗");
      }
    }
  }

貼上效果圖:

2.後端介面的實現

controller層的實現,主要看介面如何定義

@Controller
@RequestMapping(value = "/fanxing/import")
public class ImportController {
    @Resource
    ImportDataService importDataService;

    @RequestMapping(value = "/batchInsertShops", method = RequestMethod.POST)
    @ResponseBody
    public ResultData<Integer> batchInsert(@RequestParam("excelFile") MultipartFile excelFile,
                                           @RequestParam(value = "businessName", required = true ) String businessName,
                                           @RequestParam(value = "businessStatus", required = true) Integer businessStatus,
                                           @RequestParam(value = "businessType", required = true) Integer businessType) throws IOException {
        String name = excelFile.getOriginalFilename();
        if (name.length() < 6 || !name.substring(name.length() - 5).equals(".xlsx")) {
            return ResultDataBuilder.failWithNull("檔案格式錯誤", ResultCode.FILE_FORMAT_ERROR.getCode());
        }
        //TODO 業務邏輯,通過excelFile.getInputStream(),處理Excel檔案
        ExcelUtils.excelToShopIdList(excelFile.getInputStream());
    }
}

後端如何解析Excel檔案

package com.dianping.fanxing.system.admin.web.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ExcelUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtils.class);
    public static List<Integer> excelToShopIdList(InputStream inputStream) {
        List<Integer> list = new ArrayList<>();
        Workbook workbook = null;
        try {
            workbook = WorkbookFactory.create(inputStream);
            inputStream.close();
            //工作表物件
            Sheet sheet = workbook.getSheetAt(0);
            //總行數
            int rowLength = sheet.getLastRowNum() + 1;
            //工作表的列
            Row row = sheet.getRow(0);
            //總列數
            int colLength = row.getLastCellNum();
            //得到指定的單元格
            Cell cell = row.getCell(0);
            for (int i = 1; i < rowLength; i++) {
                row = sheet.getRow(i);
                for (int j = 0; j < colLength; j++) {
                    cell = row.getCell(j);
                    if (cell != null) {
                        cell.setCellType(Cell.CELL_TYPE_STRING);
                        String data = cell.getStringCellValue();
                        data = data.trim();
                        if (StringUtils.isNumeric(data))
                            list.add(Integer.parseInt(data));
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("parse excel file error :", e);
        }
        return shopIds;
    }
}

3.漢字傳輸的亂碼問題

關於前端輸入漢字,傳到後端的controller層亂碼問題,確定前端頁面設定的是UTF-8

原因:Spring MVC 是基於Servlet,在Http請求到達Servlet解析之前,GET過來的URL已經被Tomcat先做了一次URLDecode。

Tomcat對GET方式預設的URL解碼結果是iso-8859-1而不是UTF-8!

解決辦法1:

decodeFName = new String(fName.getBytes("iso-8859-1"),"utf-8");

解決方法2:

進入Tomcat的安裝目錄下,conf目錄下找到server.xml檔案,配置如下,主要新增 URIEncoding="UTF-8"就好了

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               URIEncoding="UTF-8" />

建議第二種,一勞永逸

參考連結:https://segmentfault.com/a/1190000013796215#articleHeader5

主要遇到的問題:

1.前端元件使用問題

2.前端請求的相對路徑,被upload元件自動補全了字首,導致請求到不了後端

3.post請求的漢字到了後端的controller層亂碼。

至此,前後端聯調完畢,done。後端的去寫前端真的心累,此文記錄下這兩天的摸索吧。