ExcelData.java 15.9 KB
package com.bsth.service.schedule.timetable;

import com.bsth.entity.Line;
import com.bsth.entity.LsStationRoute;
import com.bsth.entity.schedule.GuideboardInfo;
import com.bsth.service.LineService;
import com.bsth.service.StationRouteService;
import com.bsth.service.schedule.GuideboardInfoService;
import com.bsth.service.schedule.exception.ScheduleException;
import com.bsth.service.schedule.utils.DataToolsFile;
import com.bsth.service.schedule.utils.DataToolsFileType;
import com.bsth.service.schedule.utils.PoiUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 时刻表Excel数据类。
 */
public class ExcelData {

    /** 导入后的Excel文件对象 */
    private DataToolsFile dataToolsFile;
    /** 数据sheet名 */
    private String sheetName;
    /** 线路 */
    private Line line;
    /** 线路路由列表(包含上下行的起终点站) */
    private List<LsStationRoute> lsStationRouteList;
    /** 路牌列表 */
    private List<GuideboardInfo> guideboardInfoList;
    /** excel数据格式 */
    private ExcelFormatType excelFormatType;

    public DataToolsFile getDataToolsFile() {
        return dataToolsFile;
    }

    public void setDataToolsFile(DataToolsFile dataToolsFile) {
        this.dataToolsFile = dataToolsFile;
    }

    public String getSheetName() {
        return sheetName;
    }

    public void setSheetName(String sheetName) {
        this.sheetName = sheetName;
    }

    public Line getLine() {
        return line;
    }

    public void setLine(Line line) {
        this.line = line;
    }

    public List<LsStationRoute> getLsStationRouteList() {
        return lsStationRouteList;
    }

    public void setLsStationRouteList(List<LsStationRoute> lsStationRouteList) {
        this.lsStationRouteList = lsStationRouteList;
    }

    public List<GuideboardInfo> getGuideboardInfoList() {
        return guideboardInfoList;
    }

    public void setGuideboardInfoList(List<GuideboardInfo> guideboardInfoList) {
        this.guideboardInfoList = guideboardInfoList;
    }

    public ExcelFormatType getExcelFormatType() {
        return excelFormatType;
    }

    public void setExcelFormatType(ExcelFormatType excelFormatType) {
        this.excelFormatType = excelFormatType;
    }

    /**
     * 构造函数
     * @param filePath 文件路径
     * @param sheetName sheet名字
     * @param lineId 线路Id
     * @param lineService LineServiceImpl
     * @param guideboardInfoService GuideboardInfoServiceImpl
     * @param lineRouteVersion 线路路由版本
     * @param stationRouteService StationRouteServiceImpl
     * @param excelFormatType excel数据格式
     * @throws ScheduleException
     * @see com.bsth.service.impl.LineServiceImpl
     * @see com.bsth.service.schedule.impl.GuideboardInfoServiceImpl
     * @see com.bsth.service.impl.StationRouteServiceImpl
     */
    public ExcelData(
            String filePath, String sheetName,
            Integer lineId, LineService lineService, GuideboardInfoService guideboardInfoService,
            Integer lineRouteVersion, StationRouteService stationRouteService,
            ExcelFormatType excelFormatType) throws ScheduleException {
        // 获取Excel文件对象,类型
        File file = new File(filePath);
        this.dataToolsFile = new DataToolsFile();
        this.dataToolsFile.setFile(file);
        if (DataToolsFileType.XLS.isThisType(file)) {
            this.dataToolsFile.setFileType(DataToolsFileType.XLS);
        } else if (DataToolsFileType.XLSX.isThisType(file)) {
            this.dataToolsFile.setFileType(DataToolsFileType.XLSX);
        } else {
            throw new ScheduleException("不是xls xlsx文件!");
        }

        // sheet名字
        this.sheetName = sheetName;
        // 线路
        this.line = lineService.findById(lineId);
        // 线路路由列表
        Map<String, Object> p1 = new HashMap<>();
        p1.put("line.id_eq", lineId);
        p1.put("stationMark_in", "B,E"); // 起点站
        p1.put("destroy_eq", 0); // 未撤销
        p1.put("versions_eq", lineRouteVersion); // 带线路版本
        lsStationRouteList = (List<LsStationRoute>) stationRouteService.list_ls(p1);
        // 路牌列表
        p1.clear();
        p1.put("xl.id_eq", lineId);
        p1.put("isCancel_eq", false);
        guideboardInfoList = guideboardInfoService.list(p1);

        // excel数据格式
        this.excelFormatType = excelFormatType;
    }

    /**
     * 验证路由名字。
     * @param colNum 列标号(从0开始)
     * @param routeName 路由名字
     * @throws ScheduleException
     */
    private void validateRouteName(int colNum, String routeName) throws ScheduleException {
        List<LsStationRoute> lsStationRoutes = new ArrayList<>();
        for (LsStationRoute lsStationRoute : this.lsStationRouteList) {
            if ("B".equals(lsStationRoute.getStationMark()) && routeName.equals(lsStationRoute.getStationName())) {
                lsStationRoutes.add(lsStationRoute);
            }
        }

        if (CollectionUtils.isEmpty(lsStationRoutes)) {
            throw new ScheduleException(String.format("第1行,第%d列数据%s在%s站点路由中不是起点站",
                    colNum + 1, routeName, this.line.getName()));
        } else if (lsStationRoutes.size() > 1) {
            throw new ScheduleException(String.format("第1行,第%d列数据%s在%s站点路由中上下行都是起点站",
                    colNum + 1, routeName, this.line.getName()));
        } else if (StringUtils.isEmpty(lsStationRoutes.get(0).getStationCode())) {
            throw new ScheduleException(String.format("第1行,第%d列数据%s在%s站点路由中没有站点编码",
                    colNum + 1, routeName, this.line.getName()));
        }

    }

    /**
     * 验证路牌名字。
     * @param colNum excelSheet 列index
     * @param guideboardName 路牌名字
     * @throws ScheduleException
     */
    private void validateGuideboardName(int colNum, String guideboardName) throws ScheduleException {
        List<GuideboardInfo> guideboardInfos = new ArrayList<>();
        for (GuideboardInfo guideboardInfo : this.guideboardInfoList) {
            if (guideboardName.equals(guideboardInfo.getLpName())) {
                guideboardInfos.add(guideboardInfo);
            }
        }

        if (CollectionUtils.isEmpty(guideboardInfos)) {
            throw new ScheduleException(String.format("第%d行,第1列的路牌在%s中不存在",
                    colNum + 1, this.line.getName()));
        } else if (guideboardInfos.size() > 1) {
            throw new ScheduleException(String.format("第%d行,第1列的路牌在%s中重复",
                    colNum + 1, this.line.getName()));
        }
    }

    /**
     * 验证发车时间内容。
     * @param rowNum excelSheet 行index
     * @param colNum excelSheet 列index
     * @param fcsjContent 发车时间内容
     * @throws ScheduleException
     */
    private void validateFcsjContent(int rowNum, int colNum, String fcsjContent) throws ScheduleException {
        if (this.excelFormatType == ExcelFormatType.Normal) {
            // 班次时间验证,正则表达式,格式hh:mm或者hhmm
            String rex1 = "^([01]?[0-9]|2[0-3]):[0-5][0-9]$"; // hh:mm格式
            String rex2 = "^([01]?[0-9]|2[0-3]),[0-5][0-9]$"; // hh,mm格式
            String rex3 = "^([01]?[0-9]|2[0-3])[0-5][0-9]$"; // hhmm格式
            Pattern p1 = Pattern.compile(rex1);
            Pattern p2 = Pattern.compile(rex2);
            Pattern p3 = Pattern.compile(rex3);

            Matcher m1 = p1.matcher(fcsjContent.trim());
            Matcher m2 = p2.matcher(fcsjContent.trim());
            Matcher m3 = p3.matcher(fcsjContent.trim());
            if ((!m1.matches()) && (!m2.matches()) && (!m3.matches())) {
                throw new ScheduleException(String.format("第%d行,第%d列的发车时间格式不正确,格式应为hh:mm或hh,mm或hhmm",
                        rowNum + 1, colNum + 1));
            }
        } else if (this.excelFormatType == ExcelFormatType.Dynamic) {
            String rex1 = "^(\u62a5|\u51fa)?([01]?[0-9]|2[0-3]):[0-5][0-9]$"; // (报|出)hh:mm格式
            String rex2 = "^([01]?[0-9]|2[0-3]):[0-5][0-9](X|\u203b)?$"; // hh:mm(X|※)格式
            Pattern p1 = Pattern.compile(rex1);
            Pattern p2 = Pattern.compile(rex2);
            Matcher m1 = p1.matcher(fcsjContent.trim());
            Matcher m2 = p2.matcher(fcsjContent.trim());
            if ((!m1.matches()) && (!m2.matches())) {
                throw new ScheduleException(String.format(
                        "第%d行,第%d列的发车时间格式不正确,格式应为(报|出)hh:mm或hh:mm(X|※)",
                        rowNum + 1, colNum + 1));
            }
        } else {
            throw new ScheduleException("未知ExcelFormatType");
        }

    }

    private List<Integer> calcuExcelSheetColIndexList(Workbook workbook) throws ScheduleException {
        // 有些列不需要参与计算,找出参与计算的列
        List<Integer> colIndexList = new ArrayList<>();
        Sheet sheet = workbook.getSheet(this.sheetName);
        Row firstRow = sheet.getRow(0); // 获取第一行数据列
        for (int i = 0; i < firstRow.getLastCellNum(); i++) {
            if (excelFormatType == ExcelFormatType.Normal) {
                colIndexList.add(i);
            } else if (excelFormatType == ExcelFormatType.Dynamic) {
                String cell_con = StringUtils.trimToEmpty(
                        PoiUtils.getStringValueFromCell(firstRow.getCell(i))); // trimToEmpty
                if ("报到".equals(cell_con) ||
                        "到场".equals(cell_con) ||
                        "离场".equals(cell_con) ||
                        "总公里".equals(cell_con) ||
                        "营业公里".equals(cell_con) ||
                        "空驶公里".equals(cell_con) ||
                        "总工时".equals(cell_con) ||
                        "营业工时".equals(cell_con) ||
                        "营运班次".equals(cell_con)) {
                    // 这些列全部祛除
                    continue;
                } else {
                    colIndexList.add(i);
                }
            } else {
                throw new ScheduleException("未知excelFormatType");
            }
        }
        return colIndexList;
    }

    /**
     * 验证老系统ExcelSheet格式。
     * @throws ScheduleException
     */
    public void validateExcelSheet() throws ScheduleException {
        try {
            Workbook workbook = this.dataToolsFile.getFileType().getWorkBook(
                    this.dataToolsFile.getFile()
            );

            Sheet sheet = workbook.getSheet(this.sheetName);
            int rowNums = sheet.getLastRowNum() + 1; // 基于0 base的,长度加1
            int colNums = sheet.getRow(0).getLastCellNum(); // 不需要加1,就是长度

            if (rowNums == 0 || colNums == 0) { // 工作区是否为空
                throw new Exception(String.format("%s 工作区没有数据!", this.sheetName));
            } else {
                if (rowNums <= 1 || rowNums <= 1) {
                    throw new Exception(String.format("工作区至少包含2行2列的数据"));
                } else {
                    List<Integer> colIndexList = this.calcuExcelSheetColIndexList(workbook);
                    Row firstRow = sheet.getRow(0); // 获取第一行数据列
                    for (int n = 0; n < colIndexList.size(); n++) {
                        int i = colIndexList.get(n);
                        String cell_con = StringUtils.trimToEmpty(
                                PoiUtils.getStringValueFromCell(firstRow.getCell(i))); // trimToEmpty

                        if (StringUtils.isEmpty(cell_con)) {
                            throw new Exception(String.format("第1行,第%d列数据不能为空", i + 1));
                        } else {
                            // 正则表达式去除右侧数字
//                            cell_con = cell_con.replaceAll("[\\d+]", "");
//                            cell_con = cell_con.replaceAll("(\\d+)$", "");

                            // 如果站名中有类似->{数字},使用正则表达式过滤掉
                            cell_con = cell_con.replaceAll("(->\\d+)", "");

                            if (i == 0) { // 第一列必须是路牌2个字
                                if (!"路牌".equals(cell_con.trim())) {
                                    throw new Exception("第1行,第1列数据必须是路牌2个字");
                                }
                            } else { // 排除出场,进场,其余内容到站点路由里查询,以各个方向的起点站为查询依据
                                if ((!"出场".equals(cell_con.trim())) &&
                                        (!"进场".equals(cell_con.trim()))) {
                                    validateRouteName(i, cell_con.trim()); // 验证路由名字
                                }

                            }
                        }
                    }

                    // 验证路牌内容
                    Map<String, Integer> gbindexmap = new HashMap<>(); // 记录每个路牌在第几行
                    for (int i = 1; i < rowNums; i++) { // 从第2行开始验证数据
                        Cell cell = sheet.getRow(i).getCell(0); // 获取第1列
                        String bcell_con = StringUtils.trimToEmpty(
                                PoiUtils.getStringValueFromCell(cell)); // trimToEmpty
                        if (StringUtils.isEmpty(bcell_con)) {
                            throw new Exception(String.format("第%d行,第1列路牌无数据", i + 1));
                        } else if (gbindexmap.get(bcell_con.trim()) != null) {
                            throw new Exception(String.format("第%d行,第1列的路牌数据与第%d行,第1列数据重复",
                                    i + 1,
                                    gbindexmap.get(bcell_con.trim())));
                        } else {
                            validateGuideboardName(i, bcell_con.trim()); // 验证路牌名字
                            gbindexmap.put(bcell_con.trim(), i + 1);
                        }
                    }

                    // 验证发车时间格式
                    for (int i = 1; i < rowNums; i++) { // 从第2行开始验证数据
                        Row row = sheet.getRow(i);
                        for (int n = 1; n < colIndexList.size(); n++) { // 从第2列开始
                            int j = colIndexList.get(n);
                            String bcell_con = StringUtils.trimToEmpty(
                                    PoiUtils.getStringValueFromCell(
                                            row.getCell(j))).replaceAll("\\s*", ""); // trimToEmpty
                            if (StringUtils.isNotEmpty(bcell_con)) {
                                validateFcsjContent(i, j, bcell_con);
                            }
                        }
                    }
                }

            }

        } catch (Exception exp) {
            exp.printStackTrace();
            throw new ScheduleException(exp.getMessage());
        }
    }

}