IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Excel的动态解析+MybatisPlus+Springboot -> 正文阅读

[Java知识库]Excel的动态解析+MybatisPlus+Springboot

Excel的动态解析+MybatisPlus+Springboot

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


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

1、pom依赖

<!-- POI -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>
<!--hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.6.5</version>
</dependency>

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<CellNode> 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<CellNode> header = getHeader(5, path);
            header.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 获取Excel 的表结点
     *
     * @param rows      excel表,头结点的结束行
     * @param excelPath excel文件路径
     */
    public static List<CellNode> getHeader(Integer rows, String excelPath) throws Exception {
        // 获取源文件的输入流
        FileInputStream inputStream = new FileInputStream(excelPath);
        // 根据流,来创建对应的 工作簿
        HSSFWorkbook workbook = new HSSFWorkbook(inputStream);
        // 获取工作表
        HSSFSheet sheet = workbook.getSheetAt(0); // 获取第一个工作表
        // 表头结果集
        List<CellNode> 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<CellNode> 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<List<String>> 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<String>> list = new CopyOnWriteArrayList<>();
        List<CellNode> nodeList = new CopyOnWriteArrayList<>();
        int row = startRow;
        // 遍历行
        for (Row next : sheet) {
            List<String> 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<Excel> {
}

5、Service层

public interface ExcelService extends IService<Excel> {

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

}

@Service
public class ExcelServiceImpl extends ServiceImpl<ExcelMapper, Excel> implements ExcelService {
    @Resource
    private ExcelMapper excelMapper;

    @Override
    public Boolean readExcel(String path, Integer rows, Integer endRows, String catalog) {
        // 封装实体类
        Excel excel = new Excel();
        try {
            // 获取表头数据
            List<CellNode> header = ReadExcelUtils.getHeader(rows, path);
            if (header != null) {
                // 转JSON
                List<CellNode> tree = buildTree(header);
                String headerStr = JSON.toJSONString(tree);
                excel.setHeader(headerStr);
            } else {
                return false;
            }
            // 获取表的业务数据
            List<List<String>> 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<CellNode> buildTree(List<CellNode> pidList) {
        // 转为 IP-PID结构
        Map<String, List<CellNode>> 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;

    /**
     * 解析Excel文件成 json,并且存入数据库
     */
    @PostMapping("/read")
    public Result<?> addJsonToSql(@RequestBody Map<String, Object> 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 的取值说明

在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-16 11:13:45  更:2022-05-16 11:15:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:33:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码