栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Excel的动态解析+MybatisPlus+Springboot

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Excel的动态解析+MybatisPlus+Springboot

Excel的动态解析+MybatisPlus+Springboot

(省去了手动录入数据)主要用于前端动态渲染


将Excel分为结构数据、业务数据解析为 JSON,分别存储在数据库中
  1. 结构数据就是指excel表的表头部分,
  2. 业务数据就是正文部分的数据
  • 表头部分的数据,我们会用父子及关系来存储
1、pom依赖


    org.apache.poi
    poi
    4.1.2


    org.apache.poi
    poi-ooxml
    4.1.2
    
        
            org.apache.poi
            poi-ooxml-schemas
        
    



    org.projectlombok
    lombok



    com.alibaba
    fastjson
    1.2.47



    cn.hutool
    hutool-all
    5.6.5

2、实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CellNode {

    private String id;
    private String pId;
    
    // 行
    private Integer row;
    // 列
    private Integer column;
    // 单元格地址
    CellAddress address;
    // 单元格横向合并个数
    private Integer mergeCellCol;
    // 单元格纵向合并个数
    private Integer mergeCellRow;
    private String value;
    // 子节点
    private List children;

}


@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("excel")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Excel {
    
	// 表Id
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
	// 目录 or 类型
    @NotEmpty(message = "catalog为必填")
    private String catalog;
	// json-> 表头
    private String header;
	// json-> 业务数据
    private String data;
	// excel的名称
    private String excelName;
	// 表头数据部分结束行
    @JsonIgnore
    private Integer endHeaderRow;
	// 业务数据部分结束行
    @JsonIgnore
    private Integer endDataRow;
	// 创建时间
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参
    private Date createDate = new Date();
}
3、工具类
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.piesat.kdlsnatdis.entity.vo.CellNode;
import com.piesat.kdlsnatdis.enums.Constants;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellRangeAddress;

import java.io.File;
import java.io.FileInputStream;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

public class ReadExcelUtils {
	
    
    public static void main(String[] args) {
        String path = "E:\QMDownload\AppData\Firefox" + File.separator + "调查XXX况表.xls";
        try {
            List header = getHeader(5, path);
            header.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    public static List getHeader(Integer rows, String excelPath) throws Exception {
        // 获取源文件的输入流
        FileInputStream inputStream = new FileInputStream(excelPath);
        // 根据流,来创建对应的 工作簿
        HSSFWorkbook workbook = new HSSFWorkbook(inputStream);
        // 获取工作表
        HSSFSheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
        // 表头结果集
        List nodeList = new CopyOnWriteArrayList<>();
        // 表示当前行
        int row = 1;
        // 斗节点的总宽度
        int allColumn = 0;
        // 指针
        int mergerNode = 0;
        Boolean flag = false;
        try {
            // 遍历行
            for (Row next : sheet) {

                // 遍历每一行的单元格
                for (Cell cell : next) {
                    String value = getCellValue(cell); // 值
                    CellAddress address = cell.getAddress();
                    int lie = address.getColumn(); // 列
                    int hang = address.getRow(); // 行
                    int mergerNumCol = getMergerCellRegionCol(sheet, hang, lie); // 获取单元格-横向合并的数量
                    int mergerNumRow = getMergerCellRegionRow(sheet, hang, lie); // 获取单元格-纵向合并的数量
                    if (mergerNumCol == 0 && mergerNumRow == 0) {
                        // 这一步非常重要,但是又坑!!坑死了
                        mergerNumCol = 1;
                    }

                    // `mergerNode==0` 表示没有单元格合并
                    if (mergerNode == 0 && mergerNumCol > 1) {
                        mergerNode = mergerNumCol - 1;
                    }
                    if (row != (hang + 1)) {
                        // 当前行
                        row++;
                    }
                    // 对第一行操作
                    if ((mergerNode != 0) && (hang + 1) == row && StringUtils.isBlank(value)) {
                        // 过滤掉合并单元格的空值单元格
                        mergerNode--;
                        continue;
                    }
                    if (row == 1) {
                        nodeList.add(new CellNode(IdUtil.simpleUUID(), "0", hang + 1, lie + 1, address, mergerNumCol, mergerNumRow, value, null));
                        // 记录第一行,所有字段的总宽度
                        allColumn += mergerNumCol;

                    } else {
                        for (CellNode i : nodeList) {
                            // 起始列
                            Integer startCol = i.getColumn();
                            // 父节点的横向长度
                            int lengthCol = i.getColumn() + i.getMergeCellCol() - 1;
                            if (StringUtils.isNotBlank(value) && startCol <= (lie + 1) && lengthCol >= (lie + 1)) {
                                List resList = nodeList.stream()
                                        .filter(t -> ((t.getColumn() + t.getMergeCellCol() - 1) >= (lie + 1) && (t.getColumn() <= (lie + 1))))
                                        .sorted(Comparator.comparing(CellNode::getRow).reversed())
                                        .collect(Collectors.toList());
                                if (resList.size() > 0) {
                                    nodeList.add(new CellNode(IdUtil.simpleUUID(), resList.get(0).getId(), hang + 1, lie + 1, address, mergerNumCol, mergerNumRow, value, null));
                                    break;
                                }
                            }
                        }
                    }
                }
                if (row == (rows - 1)) {
                    inputStream.close();
                    return nodeList;
                }
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            inputStream.close();
        }
        inputStream.close();
        return null;
    }

    public static List> getData(Integer startRow, Integer endRow, String excelPath) throws Exception {
        // 获取源文件的输入流
        FileInputStream inputStream = new FileInputStream(excelPath);
        // 根据流,来创建对应的 工作簿
        HSSFWorkbook workbook = new HSSFWorkbook(inputStream);
        // 获取工作表
        HSSFSheet sheet = workbook.getSheetAt(0); // 获取第一个工作表

        List> list = new CopyOnWriteArrayList<>();
        List nodeList = new CopyOnWriteArrayList<>();
        int row = startRow;
        // 遍历行
        for (Row next : sheet) {
            List data = new CopyOnWriteArrayList<>();
            int rowNum = next.getRowNum();
            if (rowNum < startRow - 1) {
                continue;
            }
            if (rowNum > endRow - 1) {
                inputStream.close();
                return list;
            }
            // 遍历列
            next.forEach(i -> {
                try {
                    String value = getCellValue(i); // 值
                    CellAddress address = i.getAddress();
                    int lie = address.getColumn(); // 列
                    int hang = address.getRow(); // 行
                    int mergerNumRow = getMergerCellRegionRow(sheet, hang, lie); // 获取单元格-纵向合并的数量
                    // 如果为"", 则说明单元格纵向合并了。
                    if ("".equals(value)) {
                        for (CellNode t : nodeList) {
                            if (t.getRow() == hang && t.getColumn() == (lie + 1)) {
                                value = t.getValue();
                                nodeList.add(new CellNode("", "", hang + 1, lie + 1, address, 1, mergerNumRow, value, null));
                            }
                        }
                    }
                    nodeList.add(new CellNode("", "", hang + 1, lie + 1, address, 1, mergerNumRow, value, null));
                    data.add(value);
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            });
            list.add(data);
        }
        inputStream.close();
        return list;
    }

    
    private static String getCellValue(Cell cell) {
        String value;
        switch (cell.getCellType().name()) {
            case "NUMERIC": // 数字
                // 如果为时间格式的内容
                if (DateUtil.isCellDateFormatted(cell)) {
                    //注:format格式 yyyy-MM-dd hh:mm:ss 中小时为12小时制,若要24小时制,则把小h变为H即可,yyyy-MM-dd HH:mm:ss
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                    value = sdf.format(DateUtil.getJavaDate(cell.
                            getNumericCellValue()));
                    break;
                } else {
                    double cellNumericCellValue = cell.getNumericCellValue();
                    // 数字转字符串,自动并去除尾小数点儿后多余的0
                    value = NumberUtil.toStr(cellNumericCellValue);
                }
                break;
            case "STRING": // 字符串
                String cellValue = cell.getStringCellValue();
                // 对字符做过滤
                value = cellValue.replace(Constants.YMD_HMS, "")
                        .replace(Constants.YMD, "")
                        .replace(Constants.FBT, "");
                break;
            case "BOOLEAN": // Boolean
                value = cell.getBooleanCellValue() + "";
                break;
            case "FORMULA": // 公式
                value = cell.getCellFormula() + "";
                break;
            case "BLANK": // 空值
                value = "";
                break;
            case "ERROR": // 故障
                value = "非法字符";
                break;
            default:
                value = "未知类型";
                break;
        }
        return value;
    }

    
    private static int getMergerCellRegionCol(HSSFSheet sheet, int cellRow, int cellCol) throws Throwable {
        int retVal = 0;
        int sheetMergerCount = sheet.getNumMergedRegions();
        for (int i = 0; i < sheetMergerCount; i++) {
            CellRangeAddress cra = sheet.getMergedRegion(i);
            int firstRow = cra.getFirstRow(); // 合并单元格CELL起始行
            int firstCol = cra.getFirstColumn(); // 合并单元格CELL起始列
            int lastRow = cra.getLastRow(); // 合并单元格CELL结束行
            int lastCol = cra.getLastColumn(); // 合并单元格CELL结束列
            if (cellRow >= firstRow && cellRow <= lastRow) { // 判断该单元格是否是在合并单元格中
                if (cellCol >= firstCol && cellCol <= lastCol) {
                    retVal = lastCol - firstCol + 1; // 得到合并的列数
                    break;
                }
            }
        }
        return retVal;
    }

    
    private static int getMergerCellRegionRow(HSSFSheet sheet, int cellRow, int cellCol) throws Throwable {
        int retVal = 0;
        int sheetMergerCount = sheet.getNumMergedRegions();
        for (int i = 0; i < sheetMergerCount; i++) {
            CellRangeAddress cra = sheet.getMergedRegion(i);
            int firstRow = cra.getFirstRow(); // 合并单元格CELL起始行
            int firstCol = cra.getFirstColumn(); // 合并单元格CELL起始列
            int lastRow = cra.getLastRow(); // 合并单元格CELL结束行
            int lastCol = cra.getLastColumn(); // 合并单元格CELL结束列
            if (cellCol >= firstCol && cellCol <= lastCol) {
                if (cellRow >= firstRow && cellRow <= lastRow) { 
                    retVal = lastRow - firstRow + 1; 
                    break;
                }
            }
        }
        return retVal;
    }
}
4、Dao层
@Mapper
public interface ExcelMapper extends BaseMapper {
}

5、Service层
public interface ExcelService extends IService {

    Boolean readExcel(String path, Integer rows, Integer endRows, String catalog);

}

@Service
public class ExcelServiceImpl extends ServiceImpl implements ExcelService {
    @Resource
    private ExcelMapper excelMapper;

    @Override
    public Boolean readExcel(String path, Integer rows, Integer endRows, String catalog) {
        // 封装实体类
        Excel excel = new Excel();
        try {
            // 获取表头数据
            List header = ReadExcelUtils.getHeader(rows, path);
            if (header != null) {
                // 转JSON
                List tree = buildTree(header);
                String headerStr = JSON.toJSONString(tree);
                excel.setHeader(headerStr);
            } else {
                return false;
            }
            // 获取表的业务数据
            List> data = ReadExcelUtils.getData(rows, endRows, path);
            String dataStr = JSON.toJSONString(data);
            excel.setData(dataStr);
            // 封装简称
            excel.setCatalog(catalog);
            // 封装表头结束行和业务数据结束行
            excel.setEndHeaderRow(rows);
            excel.setEndDataRow(endRows);
            // 封装文件名
            File file = new File(path);
            String fileName = file.getName();
            excel.setExcelName(fileName);
            return excelMapper.insert(excel) == 1;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public List buildTree(List pidList) {
        // 转为 IP-PID结构
        Map> pidListMap = pidList.stream().collect(Collectors.groupingBy(CellNode::getPId));
        pidList.forEach(i -> {
            i.setChildren(pidListMap.get(i.getId()));
        });
        //返回结果也改为返回顶层节点的list
        return pidListMap.get("0");
    }
}
6、Controller
@RestController
@RequestMapping("/excel")
public class ExcelController {

    private final Logger log = LoggerFactory.getLogger(NdZrzhServiceImpl.class);

    @Autowired
    private ExcelService excelService;

    
    @PostMapping("/read")
    public Result addJsonToSql(@RequestBody Map map) {
        // 参数校验
        int rows;
        int endRows;
        if (Objects.isNull(map.get("path")) || StringUtils.isBlank(map.get("path").toString())) {
            return new Result<>(StatusCode.ERROR, "'path'必须必填");
        }
        if (Objects.isNull(map.get("catalog")) || StringUtils.isBlank(map.get("catalog").toString())) {
            return new Result<>(StatusCode.ERROR, "'catalog'必须必填");
        }
        if (map.get("rows") != null) {
            rows = Integer.parseInt(map.get("rows").toString());
            if (rows < 0) {
                return new Result<>(StatusCode.ERROR, "rows必须>0");
            }
        } else {
            return new Result<>(StatusCode.ERROR, "rows必填");
        }
        if (map.get("endRows") != null) {
            endRows = Integer.parseInt(map.get("endRows").toString());
            if (endRows < 0) {
                return new Result<>(StatusCode.ERROR, "endRows必须>0");
            }
        } else {
            return new Result<>(StatusCode.ERROR, "endRows必填");
        }
        String path = map.get("path").toString();
        String catalog = map.get("catalog").toString();
        // 解析
        if (excelService.readExcel(path, rows, endRows, catalog)) {
            return new Result<>(StatusCode.SUCCESS, "excel解析成功");
        } else {
            return new Result<>(StatusCode.ERROR, "excel解析失败");
        }
    }


}
7、Postman 测试

  • 参数:catalog,为文件名的简写。
  • 参数:rows 和 endrows 的取值说明

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/881632.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号