Mooc项目开发笔记(九):EasyExcel使用、课程分类管理接口后台实现

一、课程分类管理需求分析

Mooc项目的课程分类采用二级分类,也就是分为两级,例如:

  • 后端
    • Java
    • C++
  • 机器学习
    • 传统机器学习
    • CV
    • NLP

在数据库中的存储结构主要包含三个字段

  • id:唯一标识一个课程分类
  • name:分类名称
  • parent_id:2级分类中父类的id,1级分类的parent_id为0

另外,课程分类的信息不再采用表单的形式,而是通过导入excle添加

二、EasyExcel简介

1、Excel导入导出的应用场景

1、数据导入:减轻录入工作量

2、数据导出:统计信息归档

3、数据传输:异构系统之间数据传输

2、EasyExcel特点

  • Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
  • EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
  • EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。

三、Excel写

1、pom中引入xml相关依赖

在service_edu的pom下引入下面的依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.1</version>
    </dependency>
</dependencies>

注意还需要引入poi的依赖,而且版本要和easyexcel对应service模块已经引入过了,所以不需要放入service_edu中

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
        </dependency>

2、创建实体类

设置表头和添加的数据字段

在service_edu的test目录下创建cn/hanzhuang42/excel/DemoData.java实体类

package cn.hanzhuang42.excel;

import lombok.Data;

@Data
public class DemoData {

    //设置表头名称,和它对应的列
    @ExcelProperty(value = "学生编号", index = 0)
    private Integer sno;

    @ExcelProperty(value = "学生姓名", index = 1)
    private String sname;
}

3、实现写操作

在test中进行测试:


public class TestEasyExcel {

    //实现写入excle
    @Test
    public void writeTest(){

        //设置写入文件夹地址和文件名称
        String filename = "C:\\Users\\ada\\Desktop\\writeTest.xlsx";

        //调用方法实现写操作
        //第一个参数是文件路径名,第二个参数是实体类的class
        EasyExcel.write(filename, DemoData.class).sheet("学生列表").doWrite(getData());
    }

    private static List<DemoData> getData() {
        List<DemoData> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            DemoData d = new DemoData();
            d.setSno(i);
            d.setSname("Eva" + i);
            list.add(d);
        }
        return list;
    }
}

结果:

在这里插入图片描述

4、实现读操作

1)创建对excel表中数据对应的实体类。这里仍然使用上面的DemoData

2)创建读取操作的监听器

public class ExcelListener extends AnalysisEventListener<DemoData> {

    /**
     * 一行一行的读出excel中的数据
     * @param demoData  每行的数据
     * @param analysisContext
     */
    @Override
    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
        System.out.println("------"+demoData);
    }

    /**
     * 读取表头的内容
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:" + headMap);
    }

    /**
     * 读取完成后会调用
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    }
}

3、调用实现最终的读取

    //实现excel的读操作
    @Test
    public void readTest(){

        //设置读取文件夹地址和文件名称
        String filename = "C:\\Users\\ada\\Desktop\\writeTest.xlsx";

        //调用方法实现读操作
        EasyExcel.read(filename, DemoData.class, new ExcelListener()).sheet().doRead();
    }

四、课程分类管理接口后端实现

1、service-edu模块配置依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.1</version>
    </dependency>
</dependencies>

上边过程中已经引入

2、代码生成器生成代码

使用Mybatis-plus的代码生成器生成代码mapper,service,controller

参见开发笔记二

创建完成之后有几个地方需要修改:

1)创建和修改时间上加上@TableField注解,用于自动填充时间

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="EduSubject对象", description="课程科目")
public class EduSubject implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "课程类别ID")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "类别名称")
    private String title;

    @ApiModelProperty(value = "父ID")
    private String parentId;

    @ApiModelProperty(value = "排序字段")
    private Integer sort;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

}

2)、Controller上加上@CrossOrigin用于允许跨域访问

@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
	..
}

3、创建与Excel数据对应的实体类

创建包cn.hanzhuang42.eduservice.entity.excel,并在下面创建与excel数据对应的实体类:

package cn.hanzhuang42.eduservice.entity.excel;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class SubjectData {

    //设置表头名称,和它对应的列
    @ExcelProperty( index = 0)
    private String topSubjectName;

    @ExcelProperty(index = 1)
    private String subSubjectName;

}

4、创建读取Excel监听器

package cn.hanzhuang42.eduservice.listener;

import cn.hanzhuang42.eduservice.entity.EduSubject;
import cn.hanzhuang42.eduservice.entity.excel.SubjectData;
import cn.hanzhuang42.eduservice.service.EduSubjectService;
import cn.hanzhuang42.servicebase.exception.CustomException;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {

    //监听器没有交给spring管理,所以不能自动注入
    private EduSubjectService subjectService;

    public SubjectExcelListener() {
    }

    public SubjectExcelListener(EduSubjectService subjectService) {
        this.subjectService = subjectService;
    }

    /**
     * 读取每行数据并存入数据库
     *
     * @param subjectData
     * @param analysisContext
     */
    @Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        if (subjectData == null) {
            throw new CustomException(20001, "文件内容为空");
        }
        //这一行中的一级分类和二级分类的名称
        String topSubjectName = subjectData.getTopSubjectName();
        String subSubjectName = subjectData.getSubSubjectName();

        //判读该行的一级分类是否存在,不存在则插入
        EduSubject topSubject = existTopSubject(topSubjectName);
        if (topSubject == null) { //表中没有相同的一级分类,需要插入
            topSubject = new EduSubject();
            topSubject.setTitle(topSubjectName);
            topSubject.setParentId("0");
            subjectService.save(topSubject);
        }

        //插入数据之后mp会自动将id放入topSubject中
        String parent_id = topSubject.getId();

        //判读该行的一级分类是否存在,不存在则插入
        EduSubject subSubject = existSubSubject(subSubjectName, parent_id);
        if (subSubject == null) { //表中没有相同的一级分类,需要插入
            subSubject = new EduSubject();
            subSubject.setTitle(subSubjectName);
            subSubject.setParentId(parent_id);
            subjectService.save(subSubject);
        }
    }

    //判断一级分类是否存在
    private EduSubject existTopSubject(String name) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name)
                .eq("parent_id", 0);
        EduSubject subject = subjectService.getOne(wrapper);
        return subject;
    }

    //判断二级分类是否存在
    private EduSubject existSubSubject(String name, String parent_id) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name)
                .eq("parent_id", parent_id);
        EduSubject subject = subjectService.getOne(wrapper);
        return subject;
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

5、修改service

修改service接口

package cn.hanzhuang42.eduservice.service;

import cn.hanzhuang42.eduservice.entity.EduSubject;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;

/**
 * <p>
 * 课程科目 服务类
 * </p>
 *
 * @author Miracle42
 * @since 2020-06-25
 */
public interface EduSubjectService extends IService<EduSubject> {

    void saveSubject(MultipartFile file, EduSubjectService subjectService);
}

修改sevice实现类

package cn.hanzhuang42.eduservice.service.impl;

import cn.hanzhuang42.eduservice.entity.EduSubject;
import cn.hanzhuang42.eduservice.entity.excel.SubjectData;
import cn.hanzhuang42.eduservice.listener.SubjectExcelListener;
import cn.hanzhuang42.eduservice.mapper.EduSubjectMapper;
import cn.hanzhuang42.eduservice.service.EduSubjectService;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

/**
 * <p>
 * 课程科目 服务实现类
 * </p>
 *
 * @author Miracle42
 * @since 2020-06-25
 */
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {

    /**
     * 添加课程分类
     * @param file
     * @param subjectService
     */
    @Override
    public void saveSubject(MultipartFile file, EduSubjectService subjectService) {
        try {
            InputStream inputStream = file.getInputStream();
            EasyExcel.read(inputStream, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6、修改controller

package cn.hanzhuang42.eduservice.controller;


import cn.hanzhuang42.commonutils.R;
import cn.hanzhuang42.eduservice.service.EduSubjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * <p>
 * 课程科目 前端控制器
 * </p>
 *
 * @author Miracle42
 * @since 2020-06-25
 */
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {

    @Autowired
    private EduSubjectService subjectService;

    /**
     * 添加课程分类
     * 通过前端上传文件到服务器,
     * 然后通过easyexcel读取并存入数据库
     */
    @PostMapping("addSubject")
    public R addSubject(MultipartFile file){

        subjectService.saveSubject(file, subjectService);

        return R.ok();
    }
}

7、测试

打开swagger进行测试:

在这里插入图片描述

上传文件,点击执行

在这里插入图片描述

数据库中也可以看到信息加入成功
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章