1. 程式人生 > >POI讀取excel百萬級-SAX方式解析

POI讀取excel百萬級-SAX方式解析

一. 簡介

           在excel解析的時候,採用SAX方方式會將excel轉換為xml進行解析避免了記憶體溢位。

           速度在3秒1W的資料寫入,100W條記錄,大概50M的資料,耗時大概4分半(如果不需要校驗,可能會更快);

           暫時先直接將專案中的拷貝出來,使用的時候直接調工具類即可。目前正在搞自己的一個專案,後期會將匯入,匯出都弄上去,再優化下,放到git上。

           另外,膜拜下原生jdbc, 昨天問了下,聽說可以達到1秒10W數量級的寫入;

           是在網上找的一個,然後自己封裝了下, 加了一個委託,目前存在一個BUG: 就是如果excel中沒有資料,會自動跳過該空格。目前解決方案是:excel中為空的使用“-”來標識,後期解決。

二. 程式碼DEMO

2.1 POM依賴

<!-- poi -->
      <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi</artifactId>
          <version>3.17</version>
      </dependency>
      <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi-ooxml</artifactId>
          <version>3.17</version>
      </dependency>
      <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi-ooxml-schemas</artifactId>
          <version>3.17</version>
      </dependency>


      <!-- sax -->
      <dependency>
          <groupId>sax</groupId>
          <artifactId>sax</artifactId>
          <version>2.0.1</version>
      </dependency>
      <dependency>
          <groupId>xml-apis</groupId>
          <artifactId>xml-apis</artifactId>
          <version>1.4.01</version>
      </dependency>
      <dependency>
          <groupId>org.apache.xmlbeans</groupId>
          <artifactId>xmlbeans</artifactId>
          <version>2.6.0</version>
      </dependency>
      <dependency>
          <groupId>xerces</groupId>
          <artifactId>xercesImpl</artifactId>
          <version>2.11.0</version>
      </dependency>

2.2 EXCEL常量類

package com.yzx.osp.common.constant;

/**
 * @author qjwyss
 * @date 2018/9/19
 * @description EXCEL常量類
 */
public class ExcelConstant {

    /**
     * excel2007副檔名
     */
    public static final String EXCEL07_EXTENSION = ".xlsx";


    /**
     * 每個sheet儲存的記錄數 100W
     */
    public static final Integer PER_SHEET_ROW_COUNT = 1000000;

    /**
     * 每次向EXCEL寫入的記錄數(查詢每頁資料大小) 20W
     */
    public static final Integer PER_WRITE_ROW_COUNT = 200000;


    /**
     * 每個sheet的寫入次數 5
     */
    public static final Integer PER_SHEET_WRITE_COUNT = PER_SHEET_ROW_COUNT / PER_WRITE_ROW_COUNT;


    /**
     * 讀取excel的時候每次批量插入資料庫記錄數
     */
    public static final Integer PER_READ_INSERT_BATCH_COUNT = 10000;



}

2.3 讀取EXCEL輔助類

package com.yzx.osp.common.util;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

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

/**
 * @author qjwyss
 * @date 2018/12/19
 * @description 讀取EXCEL輔助類
 */
public class ExcelXlsxReaderWithDefaultHandler extends DefaultHandler {

    private ExcelReadDataDelegated excelReadDataDelegated;

    public ExcelReadDataDelegated getExcelReadDataDelegated() {
        return excelReadDataDelegated;
    }

    public void setExcelReadDataDelegated(ExcelReadDataDelegated excelReadDataDelegated) {
        this.excelReadDataDelegated = excelReadDataDelegated;
    }

    public ExcelXlsxReaderWithDefaultHandler(ExcelReadDataDelegated excelReadDataDelegated) {
        this.excelReadDataDelegated = excelReadDataDelegated;
    }

    /**
     * 單元格中的資料可能的資料型別
     */
    enum CellDataType {
        BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, DATE, NULL
    }

    /**
     * 共享字串表
     */
    private SharedStringsTable sst;

    /**
     * 上一次的索引值
     */
    private String lastIndex;

    /**
     * 檔案的絕對路徑
     */
    private String filePath = "";

    /**
     * 工作表索引
     */
    private int sheetIndex = 0;

    /**
     * sheet名
     */
    private String sheetName = "";

    /**
     * 總行數
     */
    private int totalRows = 0;

    /**
     * 一行內cell集合
     */
    private List<String> cellList = new ArrayList<String>();

    /**
     * 判斷整行是否為空行的標記
     */
    private boolean flag = false;

    /**
     * 當前行
     */
    private int curRow = 1;

    /**
     * 當前列
     */
    private int curCol = 0;

    /**
     * T元素標識
     */
    private boolean isTElement;

    /**
     * 異常資訊,如果為空則表示沒有異常
     */
    private String exceptionMessage;

    /**
     * 單元格資料型別,預設為字串型別
     */
    private CellDataType nextDataType = CellDataType.SSTINDEX;

    private final DataFormatter formatter = new DataFormatter();

    /**
     * 單元格日期格式的索引
     */
    private short formatIndex;

    /**
     * 日期格式字串
     */
    private String formatString;

    //定義前一個元素和當前元素的位置,用來計算其中空的單元格數量,如A6和A8等
    private String preRef = null, ref = null;

    //定義該文件一行最大的單元格數,用來補全一行最後可能缺失的單元格
    private String maxRef = null;

    /**
     * 單元格
     */
    private StylesTable stylesTable;


    /**
     * 總行號
     */
    private Integer totalRowCount;

    /**
     * 遍歷工作簿中所有的電子表格
     * 並快取在mySheetList中
     *
     * @param filename
     * @throws Exception
     */
    public int process(String filename) throws Exception {
        filePath = filename;
        OPCPackage pkg = OPCPackage.open(filename);
        XSSFReader xssfReader = new XSSFReader(pkg);
        stylesTable = xssfReader.getStylesTable();
        SharedStringsTable sst = xssfReader.getSharedStringsTable();
        XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        this.sst = sst;
        parser.setContentHandler(this);
        XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
        while (sheets.hasNext()) { //遍歷sheet
            curRow = 1; //標記初始行為第一行
            sheetIndex++;
            InputStream sheet = sheets.next(); //sheets.next()和sheets.getSheetName()不能換位置,否則sheetName報錯
            sheetName = sheets.getSheetName();
            InputSource sheetSource = new InputSource(sheet);
            parser.parse(sheetSource); //解析excel的每條記錄,在這個過程中startElement()、characters()、endElement()這三個函式會依次執行
            sheet.close();
        }
        return totalRows; //返回該excel檔案的總行數,不包括首列和空行
    }

    /**
     * 第一個執行
     *
     * @param uri
     * @param localName
     * @param name
     * @param attributes
     * @throws SAXException
     */
    @Override
    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {

        // 獲取總行號  格式: A1:B5    取最後一個值即可
        if("dimension".equals(name)) {
            String dimensionStr = attributes.getValue("ref");
            totalRowCount = Integer.parseInt(dimensionStr.substring(dimensionStr.indexOf(":") + 2)) - 1;
        }

        //c => 單元格
        if ("c".equals(name)) {
            //前一個單元格的位置
            if (preRef == null) {
                preRef = attributes.getValue("r");
            } else {
                preRef = ref;
            }

            //當前單元格的位置
            ref = attributes.getValue("r");
            //設定單元格型別
            this.setNextDataType(attributes);
        }

        //當元素為t時
        if ("t".equals(name)) {
            isTElement = true;
        } else {
            isTElement = false;
        }

        //置空
        lastIndex = "";
    }


    /**
     * 第二個執行
     * 得到單元格對應的索引值或是內容值
     * 如果單元格型別是字串、INLINESTR、數字、日期,lastIndex則是索引值
     * 如果單元格型別是布林值、錯誤、公式,lastIndex則是內容值
     *
     * @param ch
     * @param start
     * @param length
     * @throws SAXException
     */
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        lastIndex += new String(ch, start, length);
    }


    /**
     * 第三個執行
     *
     * @param uri
     * @param localName
     * @param name
     * @throws SAXException
     */
    @Override
    public void endElement(String uri, String localName, String name) throws SAXException {

        //t元素也包含字串
        if (isTElement) {//這個程式沒經過
            //將單元格內容加入rowlist中,在這之前先去掉字串前後的空白符
            String value = lastIndex.trim();
            cellList.add(curCol, value);
            curCol++;
            isTElement = false;
            //如果裡面某個單元格含有值,則標識該行不為空行
            if (value != null && !"".equals(value)) {
                flag = true;
            }
        } else if ("v".equals(name)) {
            //v => 單元格的值,如果單元格是字串,則v標籤的值為該字串在SST中的索引
            String value = this.getDataValue(lastIndex.trim(), "");//根據索引值獲取對應的單元格值
            //補全單元格之間的空單元格
            if (!ref.equals(preRef)) {
                int len = countNullCell(ref, preRef);
                for (int i = 0; i < len; i++) {
                    cellList.add(curCol, "");
                    curCol++;
                }
            }
            cellList.add(curCol, value);
            curCol++;
            //如果裡面某個單元格含有值,則標識該行不為空行
            if (value != null && !"".equals(value)) {
                flag = true;
            }
        } else {
            //如果標籤名稱為row,這說明已到行尾,呼叫optRows()方法
            if ("row".equals(name)) {
                //預設第一行為表頭,以該行單元格數目為最大數目
                if (curRow == 1) {
                    maxRef = ref;
                }
                //補全一行尾部可能缺失的單元格
                if (maxRef != null) {
                    int len = countNullCell(maxRef, ref);
                    for (int i = 0; i <= len; i++) {
                        cellList.add(curCol, "");
                        curCol++;
                    }
                }

                if (flag && curRow != 1) { //該行不為空行且該行不是第一行,則傳送(第一行為列名,不需要)
                    // 呼叫excel讀資料委託類進行讀取插入操作
                    excelReadDataDelegated.readExcelDate(sheetIndex, totalRowCount, curRow, cellList);
                    totalRows++;
                }

                cellList.clear();
                curRow++;
                curCol = 0;
                preRef = null;
                ref = null;
                flag = false;
            }
        }
    }

    /**
     * 處理資料型別
     *
     * @param attributes
     */
    public void setNextDataType(Attributes attributes) {
        nextDataType = CellDataType.NUMBER; //cellType為空,則表示該單元格型別為數字
        formatIndex = -1;
        formatString = null;
        String cellType = attributes.getValue("t"); //單元格型別
        String cellStyleStr = attributes.getValue("s"); //
        String columnData = attributes.getValue("r"); //獲取單元格的位置,如A1,B1

        if ("b".equals(cellType)) { //處理布林值
            nextDataType = CellDataType.BOOL;
        } else if ("e".equals(cellType)) {  //處理錯誤
            nextDataType = CellDataType.ERROR;
        } else if ("inlineStr".equals(cellType)) {
            nextDataType = CellDataType.INLINESTR;
        } else if ("s".equals(cellType)) { //處理字串
            nextDataType = CellDataType.SSTINDEX;
        } else if ("str".equals(cellType)) {
            nextDataType = CellDataType.FORMULA;
        }

        if (cellStyleStr != null) { //處理日期
            int styleIndex = Integer.parseInt(cellStyleStr);
            XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
            formatIndex = style.getDataFormat();
            formatString = style.getDataFormatString();
            if (formatString.contains("m/d/yy") || formatString.contains("yyyy/mm/dd") || formatString.contains("yyyy/m/d")) {
                nextDataType = CellDataType.DATE;
                formatString = "yyyy-MM-dd hh:mm:ss";
            }

            if (formatString == null) {
                nextDataType = CellDataType.NULL;
                formatString = BuiltinFormats.getBuiltinFormat(formatIndex);
            }
        }
    }

    /**
     * 對解析出來的資料進行型別處理
     *
     * @param value   單元格的值,
     *                value代表解析:BOOL的為0或1, ERROR的為內容值,FORMULA的為內容值,INLINESTR的為索引值需轉換為內容值,
     *                SSTINDEX的為索引值需轉換為內容值, NUMBER為內容值,DATE為內容值
     * @param thisStr 一個空字串
     * @return
     */
    @SuppressWarnings("deprecation")
    public String getDataValue(String value, String thisStr) {
        switch (nextDataType) {
            // 這幾個的順序不能隨便交換,交換了很可能會導致資料錯誤
            case BOOL: //布林值
                char first = value.charAt(0);
                thisStr = first == '0' ? "FALSE" : "TRUE";
                break;
            case ERROR: //錯誤
                thisStr = "\"ERROR:" + value.toString() + '"';
                break;
            case FORMULA: //公式
                thisStr = '"' + value.toString() + '"';
                break;
            case INLINESTR:
                XSSFRichTextString rtsi = new XSSFRichTextString(value.toString());
                thisStr = rtsi.toString();
                rtsi = null;
                break;
            case SSTINDEX: //字串
                String sstIndex = value.toString();
                try {
                    int idx = Integer.parseInt(sstIndex);
                    XSSFRichTextString rtss = new XSSFRichTextString(sst.getEntryAt(idx));//根據idx索引值獲取內容值
                    thisStr = rtss.toString();
                    rtss = null;
                } catch (NumberFormatException ex) {
                    thisStr = value.toString();
                }
                break;
            case NUMBER: //數字
                if (formatString != null) {
                    thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString).trim();
                } else {
                    thisStr = value;
                }
                thisStr = thisStr.replace("_", "").trim();
                break;
            case DATE: //日期
                thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString);
                // 對日期字串作特殊處理,去掉T
                thisStr = thisStr.replace("T", " ");
                break;
            default:
                thisStr = " ";
                break;
        }
        return thisStr;
    }

    public int countNullCell(String ref, String preRef) {
        //excel2007最大行數是1048576,最大列數是16384,最後一列列名是XFD
        String xfd = ref.replaceAll("\\d+", "");
        String xfd_1 = preRef.replaceAll("\\d+", "");

        xfd = fillChar(xfd, 3, '@', true);
        xfd_1 = fillChar(xfd_1, 3, '@', true);

        char[] letter = xfd.toCharArray();
        char[] letter_1 = xfd_1.toCharArray();
        int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]);
        return res - 1;
    }

    public String fillChar(String str, int len, char let, boolean isPre) {
        int len_1 = str.length();
        if (len_1 < len) {
            if (isPre) {
                for (int i = 0; i < (len - len_1); i++) {
                    str = let + str;
                }
            } else {
                for (int i = 0; i < (len - len_1); i++) {
                    str = str + let;
                }
            }
        }
        return str;
    }


}

注: 此處使用了setter、getter、建構函式的方式將寫資料委託介面注入進去,想了半天。 

2.4 寫資料委託介面

package com.yzx.osp.common.util;

import java.util.List;

/**
 * @author qjwyss
 * @date 2018/12/19
 * @description 讀取excel資料委託介面
 */
public interface ExcelReadDataDelegated {

    /**
     * 每獲取一條記錄,即寫資料
     * 在flume裡每獲取一條記錄即寫,而不必快取起來,可以大大減少記憶體的消耗,這裡主要是針對flume讀取大資料量excel來說的
     *
     * @param sheetIndex    sheet位置
     * @param totalRowCount 該sheet總行數
     * @param curRow        行號
     * @param cellList      行資料
     */
    public abstract void readExcelDate(int sheetIndex, int totalRowCount, int curRow, List<String> cellList);

}

2.5 讀取工具類 

package com.yzx.osp.common.util;

import com.yzx.osp.common.constant.ExcelConstant;

import java.util.List;

/**
 * @author qjwyss
 * @date 2018/12/19
 * @description 讀取EXCEL工具類
 */
public class ExcelReaderUtil {


    public static void readExcel(String filePath, ExcelReadDataDelegated excelReadDataDelegated) throws Exception {
        int totalRows = 0;
        if (filePath.endsWith(ExcelConstant.EXCEL07_EXTENSION)) {
            ExcelXlsxReaderWithDefaultHandler excelXlsxReader = new ExcelXlsxReaderWithDefaultHandler(excelReadDataDelegated);
            totalRows = excelXlsxReader.process(filePath);
        } else {
            throw new Exception("檔案格式錯誤,fileName的副檔名只能是xlsx!");
        }
        System.out.println("讀取的資料總行數:" + totalRows);
    }


    public static void main(String[] args) throws Exception {
        String path = "E:\\temp\\5.xlsx";
        ExcelReaderUtil.readExcel(path, new ExcelReadDataDelegated() {
            @Override
            public void readExcelDate(int sheetIndex, int totalRowCount, int curRow, List<String> cellList) {
                System.out.println("總行數為:" + totalRowCount + " 行號為:" + curRow + " 資料:" + cellList);
            }
        });
    }
}

注: 此處可以直接使用,將sheet索引、總行數、當前行號、當前行記錄打印出來,結果如下: 

 

三. 使用案例

3.1 特殊說明

          剛開始進行匯入的 時候,因為每條記錄要進行三個欄位的校驗(是否存在)和轉換(名稱要轉換為對應的資料庫ID),剛開始的做法是 讀取一條記錄,然後發3次查詢sql進行校驗,然後再發一次插入sql進行儲存。然後悲劇了,測試匯入1W條資料要好幾分鐘。  第二天早上想來突然想到優化方案, 瓶頸在於頻繁傳送sql語句。然後就在開始匯入之前,將需要校驗的記錄先查詢出來放到map中,然後每次遍歷的時候直接從map中取出來進行校驗,不必發sql了; 還有就是批量儲存;

3.2 使用案例

           忙,沒時間修改,直接將程式碼拷貝上去了,自己用的時候直接呼叫工具類即可。 

@Override
    public ResultVO<Void> importMobileManagerList(String filePath) throws Exception {

        logger.info("開始匯入號碼列表:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));

        List<Integer> errorRowNumber = new ArrayList<>();
        List<MobileManager> mobileManagerList = new ArrayList<>();

        MobileManagerVO mobileManagerVO = new MobileManagerVO();
        List<String> mobileList = this.mobileManagerMapper.selectMobileList(mobileManagerVO);

        SysAreaVO sysAreaVO = new SysAreaVO();
        List<SysAreaVO> sysAreaVOList = this.sysAreaMapper.selectSysAreaVOList(sysAreaVO);

        CustomerVO customerVO = new CustomerVO();
        List<String> customerIdList = this.customerDao.selectLineOrBusinessCustomerIdList(customerVO);
        List<CustomerVO> customerVOList = this.customerDao.selectCustomerIdAndAccountIdList(customerVO);


        ExcelReaderUtil.readExcel(filePath, new ExcelReadDataDelegated() {
            @Override
            public void readExcelDate(int sheetIndex, int totalRowCount, int curRow, List<String> cellList) {

                // 校驗資料合法性
                Boolean legalFlag = true;
                Integer provinceId = null;
                Integer cityId = null;
                List<String> accountIdList = null;

                // 號碼、成本號碼費、成本低消費、客戶號碼費、客戶低消費不能為空
                if (CommonUtil.checkStringIsNullOrLine(cellList.get(0)) || CommonUtil.checkStringIsNullOrLine(cellList.get(12))
                        || CommonUtil.checkStringIsNullOrLine(cellList.get(13)) || CommonUtil.checkStringIsNullOrLine(cellList.get(14))
                        || CommonUtil.checkStringIsNullOrLine(cellList.get(15))) {
                    legalFlag = false;
                }

                // 客戶型別為VBOSS並且分配了客戶時,賬戶不能為空
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(7)) && !CommonUtil.checkStringIsNullOrLine(cellList.get(8))) {
                    if (cellList.get(7).trim().equals("VBOSS") && CommonUtil.checkStringIsNullOrLine(cellList.get(8))) {
                        legalFlag = false;
                    }
                }

                // 客戶型別為空的時候客戶賬戶不能有值
                if (CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
                    if (!CommonUtil.checkStringIsNullOrLine(cellList.get(8)) || !CommonUtil.checkStringIsNullOrLine(cellList.get(9))) {
                        legalFlag = false;
                    }
                }

                // 客戶型別為bss的時候賬戶不能有值
                if (CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
                    if (cellList.get(7).trim().equals("BSS")) {
                        if (!CommonUtil.checkStringIsNullOrLine(cellList.get(9))) {
                            legalFlag = false;
                        }
                    }
                }


                // 號碼、區號必須為數字
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(0))) {
                    if (!CommonUtil.checkIsInteger(cellList.get(0).trim())) {
                        legalFlag = false;
                    }
                }
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(1))) {
                    if (!CommonUtil.checkIsInteger(cellList.get(1).trim())) {
                        legalFlag = false;
                    }
                }

                // 運營商只能為 移動、聯通、電信、鐵通、其它之一
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(5))) {
                    if (!cellList.get(5).trim().equals("移動") && !cellList.get(5).trim().equals("聯通")
                            && !cellList.get(5).trim().equals("電信") && !cellList.get(5).trim().equals("鐵通")
                            && !cellList.get(5).trim().equals("其它")) {
                        legalFlag = false;
                    }
                }

                // 客戶型別只能是 VBOSS或BSS之一
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
                    if (!cellList.get(7).trim().equalsIgnoreCase("VBOSS") && !cellList.get(7).trim().equalsIgnoreCase("BSS")) {
                        legalFlag = false;
                    }
                }

                // 成本號碼費、成本低消費、客戶號碼費、客戶低消費只能為小數
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(12))) {
                    if (!CommonUtil.checkIsSmallNumber(cellList.get(12).trim())) {
                        legalFlag = false;
                    }
                }
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(13))) {
                    if (!CommonUtil.checkIsSmallNumber(cellList.get(13).trim())) {
                        legalFlag = false;
                    }
                }
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(14))) {
                    if (!CommonUtil.checkIsSmallNumber(cellList.get(14).trim())) {
                        legalFlag = false;
                    }
                }
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(15))) {
                    if (!CommonUtil.checkIsSmallNumber(cellList.get(15).trim())) {
                        legalFlag = false;
                    }
                }


                // 資料庫校驗

                // 校驗號碼是否存在
                if (!CollectionUtils.isEmpty(mobileList)) {
                    if (mobileList.contains(cellList.get(0).trim())) {
                        legalFlag = false;
                    }
                }

                // 校驗省是否存在
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(2))) {
                    if (CollectionUtils.isEmpty(sysAreaVOList)) {
                        legalFlag = false;
                    } else {
                        Boolean hasFlag = false;
                        for (SysAreaVO eachSysAreaVO : sysAreaVOList) {
                            if (eachSysAreaVO.getAreaName().equals(cellList.get(2).trim())) {
                                hasFlag = true;
                                provinceId = eachSysAreaVO.getSaid();
                                break;
                            }
                        }
                        if (!hasFlag) {
                            legalFlag = false;
                        }
                    }
                }


                // 校驗市是否存在
                if (!CommonUtil.checkStringIsNullOrLine(cellList.get(3))) {
                    if (CollectionUtils.isEmpty(sysAreaVOList)) {
                        legalFlag = false;
                    } else {
                        Boolean hasFlag = false;
                        for (SysAreaVO eachSysAreaVO : sysAreaVOList) {
                            if (eachSysAreaVO.getAreaName().equals(cellList.get(3).trim())) {
                                hasFlag = true;
                                cityId = eachSysAreaVO.getSaid();
                                break;
                            }
                        }
                        if (!hasFlag) {
                            legalFlag = false;
                        }
                    }
                }


                // 如果選擇了客戶型別並且分配了客戶,則需要校驗客戶ID是否存在
                if(!CommonUtil.checkStringIsNullOrLine(cellList.get(7)) && !CommonUtil.checkStringIsNullOrLine(cellList.get(8))) {

                    // 校驗客戶ID是否存在
                    Boolean hasCustomerIdFlag = true;
                    if(CollectionUtils.isEmpty(customerIdList)) {
                        hasCustomerIdFlag = false;
                        legalFlag = false;
                    } else {
                        if(!customerIdList.contains(cellList.get(8).trim())) {
                            hasCustomerIdFlag = false;
                            legalFlag = false;
                        }
                    }

                    // 如果該客戶ID存在,並且選中的客戶型別是VBOSS,則需要校驗賬戶和客戶是否匹配
                    if(hasCustomerIdFlag) {
                        if(cellList.get(7).equals("VBOSS") && !CommonUtil.checkStringIsNullOrLine(cellList.get(9))) {

                            if(CollectionUtils.isEmpty(customerVOList)) {
                                legalFlag = false;
                            } else {
                                for (CustomerVO eachCustomerVO: customerVOList) {
                                    if(eachCustomerVO.getCustomerId().equals(cellList.get(8).trim())) {
                                        accountIdList = eachCustomerVO.getAccountIdList();
                                        break;
                                    }
                                }

                                if(CollectionUtils.isEmpty(accountIdList)) {
                                    legalFlag = false;
                                } else {
                                    if(!accountIdList.contains(cellList.get(9).trim())) {
                                        legalFlag = false;
                                    }
                                }
                            }
                        }
                    }
                }


                // 如果資料合法,則批量儲存號碼物件
                if (!legalFlag) {
                    if (!errorRowNumber.contains(curRow)) {
                        errorRowNumber.add(curRow);
                    }
                } else {

                    try {

                        MobileManager mobileManager = new MobileManager();
                        mobileManager.setMobile(cellList.get(0).trim());
                        mobileManager.setAreaCode(CommonUtil.checkStringIsNullOrLine(cellList.get(1)) ? null : cellList.get(1));
                        mobileManager.setProvinceId(provinceId == null ? null : provinceId);
                        mobileManager.setCityId(cityId == null ? null : cityId);
                        mobileManager.setType(CommonUtil.checkStringIsNullOrLine(cellList.get(4)) ? null : cellList.get(4));

                        if (!CommonUtil.checkStringIsNullOrLine(cellList.get(5))) {
                            Integer operator = null;
                            if (cellList.get(5).trim().equals("移動")) {
                                operator = MobileManagerConstant.OPERATOR_YIDONG;
                            } else if (cellList.get(5).trim().equals("聯通")) {
                                operator = MobileManagerConstant.OPERATOR_LIANTONG;
                            } else if (cellList.get(5).trim().equals("電信")) {
                                operator = MobileManagerConstant.OPERATOR_DIANXIN;
                            } else if (cellList.get(5).trim().equals("鐵通")) {
                                operator = MobileManagerConstant.OPERATOR_TIETONG;
                            } else if (cellList.get(5).trim().equals("其它")) {
                                operator = MobileManagerConstant.OPERATOR_ELSE;
                            }
                            mobileManager.setOperator(operator);
                        }

                        mobileManager.setSupplierName(CommonUtil.checkStringIsNullOrLine(cellList.get(6)) ? null : cellList.get(6));


                        if (!CommonUtil.checkStringIsNullOrLine(cellList.get(7))) {
                            Integer customerType = null;
                            if (cellList.get(7).trim().equalsIgnoreCase("VBOSS")) {
                                customerType = MobileManagerConstant.CUSTOMER_TYPE_VBOSS;
                                mobileManager.setAccountId(CommonUtil.checkStringIsNullOrLine(cellList.get(9)) ? null : cellList.get(9));
                            } else if (cellList.get(7).trim().equalsIgnoreCase("BSS")) {
                                customerType = MobileManagerConstant.CUSTOMER_TYPE_BSS;
                            }
                            mobileManager.setCustomerType(customerType);
                            mobileManager.setCustomerId(CommonUtil.checkStringIsNullOrLine(cellList.get(8)) ? null : cellList.get(8));
                            mobileManager.setState(CommonUtil.checkStringIsNullOrLine(cellList.get(8)) ?
                                    MobileManagerConstant.STATE_USEING : MobileManagerConstant.STATE_USEING);
                        } else {
                            mobileManager.setState(MobileManagerConstant.STATE_UNUSE);
                        }


                        mobileManager.setTerminalUser(CommonUtil.checkStringIsNullOrLine(cellList.get(10)) ? null : cellList.get(10));

                        if (!CommonUtil.checkStringIsNullOrLine(cellList.get(11))) {
                            mobileManager.setPlanRecoveryDate(DateUtil.formatDateStrWithLine2Date(cellList.get(11).trim()));
                        }

                        mobileManager.setCostMobileFee(CommonUtil.checkStringIsNullOrLine(cellList.get(12)) ? null : Double.parseDouble(cellList.get(12).trim()));
                        mobileManager.setCostLowFee(CommonUtil.checkStringIsNullOrLine(cellList.get(13)) ? null : Double.parseDouble(cellList.get(13).trim()));
                        mobileManager.setCustomerMobileFee(CommonUtil.checkStringIsNullOrLine(cellList.get(14)) ? null : Double.parseDouble(cellList.get(14).trim()));
                        mobileManager.setCustomerLowFee(CommonUtil.checkStringIsNullOrLine(cellList.get(15)) ? null : Double.parseDouble(cellList.get(15).trim()));
                        mobileManager.setRemark(CommonUtil.checkStringIsNullOrLine(cellList.get(16)) ? null : cellList.get(16).trim());
                        mobileManager.setCreateTime(new Date());
                        mobileManagerList.add(mobileManager);

                        if (mobileManagerList.size() == ExcelConstant.PER_READ_INSERT_BATCH_COUNT) {
                            mobileManagerMapper.saveMobileManagerBatch(mobileManagerList);
                            mobileManagerList.clear();
                        } else if (mobileManagerList.size() < ExcelConstant.PER_READ_INSERT_BATCH_COUNT) {
                            int lastInsertBatchCount = totalRowCount % ExcelConstant.PER_READ_INSERT_BATCH_COUNT == 0 ?
                                    totalRowCount / ExcelConstant.PER_READ_INSERT_BATCH_COUNT :
                                    totalRowCount / ExcelConstant.PER_READ_INSERT_BATCH_COUNT + 1;
                            if ((curRow - 1) >= ((lastInsertBatchCount - 1) * ExcelConstant.PER_READ_INSERT_BATCH_COUNT + 1)
                                    && (curRow - 1) < lastInsertBatchCount * ExcelConstant.PER_READ_INSERT_BATCH_COUNT) {
                                if (curRow - 1 == totalRowCount) {
                                    mobileManagerMapper.saveMobileManagerBatch(mobileManagerList);
                                }
                            }
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                        if (!errorRowNumber.contains(curRow)) {
                            errorRowNumber.add(curRow);
                        }
                    }
                }
            }
        });

        logger.info("匯入號碼列表完成:" + DateUtil.formatDate(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS));

        return ResultVO.getSuccess("批量匯入VOIP使用者完成, 共有" + errorRowNumber.size() + "條記錄存在問題,失敗行號為:" + errorRowNumber);
    }