谷粒学院——第七章、课程分类管理

EasyExcel 介绍

简介

Excel导入导出的应用场景

1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输

EasyExcel的特点

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

EasyExcel 读写操作案例

1、创建项目引入依赖

在service模块下创建 easy_excel_demo 模块,并在其 pom 文件中引入依赖:

<dependencies>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
  </dependency>
</dependencies>

2、创建实体类

@Data
public class DemoData {
    // 设置表头名称
    @ExcelProperty(value = "学生编号",index = 0)
    private Integer sno;
    @ExcelProperty(value = "学生姓名",index = 1)
    private String sname;
}

3、实现写操作

创建类 TestEasyExcel,创建方法循环设置要添加到 Excel 的数据

public class TestEasyExcel {
    public static void main(String[] args) {
        // 实现Excel写的操作
        // 1、设置写入文件夹地址和excel文件名
        String filename = "/gulixy/write.xlsx";
        // 2、调用easyexcel里面的方法实现写操作
        // write方法两个参数,第一个参数文件路径名称,第二个参数实体类class
        EasyExcel.write(filename, DemoData.class).sheet("学生列表").doWrite(getData());
    }

    // 创建方法返回list集合
    private static List<DemoData> getData() {
        List<DemoData> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setSno(i);
            data.setSname("lucy"+i);
            list.add(data);
        }
        return list;
    }
}

运行main方法,结果:
image.png

4、实现读操作

(1)创建监听器

public class ExcelListener extends AnalysisEventListener<DemoData> {
    // 一行一行读取Excel内容
    @Override
    public void invoke(DemoData data, AnalysisContext analysisContext) {
        System.out.println("****" + data);
    }
    // 读取表头内容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:" + headMap);
    }
    // 读取完成之后
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

(2)读取操作

//main方法里面添加:
EasyExcel.read(filename, DemoData.class, new ExcelListener()).sheet().doRead();

课程分类功能

后端实现

1、引入依赖与代码生成器

在 service_edu 模块中引入依赖

<dependencies>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
  </dependency>
</dependencies>

将之前用过的代码生成器换一下表名为edu_subject即可。

注意添加自动创建时间的注解:
image.png

2、创建实体类

entity下建立excel包,然后创建类:

@Data
public class SubjectData {
    @ExcelProperty(index = 0)
    private String oneSubjectName;
    
    @ExcelProperty(index = 1)
    private String twoSubjectName;
}

3、controller层

@Api(description = "课程分类管理")
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
    @Autowired
    private EduSubjectService subjectService;

    // 添加课程分类
    // 获取上传过来的文件,把文件内容读取出来
    @ApiOperation(value = "Excel导入")
    @PostMapping("addSubject")
    public R addSubject(MultipartFile file) {
        // 上传过来Excel文件
        subjectService.saveSubject(file, subjectService);
        return R.ok();
    }
}

4、service层

(1)接口

public interface EduSubjectService extends IService<EduSubject> {
	// 添加课程分类文件
    void saveSubject(MultipartFile file, EduSubjectService subjectService);
}

(2)实现类

@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {

    @Override
    public void saveSubject(MultipartFile file, EduSubjectService subjectService) {
        try {
            // 获取文件输入流
            InputStream is = file.getInputStream();
            // 调用方法进行读取
            EasyExcel.read(is, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
        } catch (Exception e) {
            e.printStackTrace();
            throw new GuliException(20001, "添加课程分类失败");
        }
    }
}

5、创建监听器

创建包 listener,然后创建类:SubjectExcelListener

public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {

    // SubjectExcelListener不能交给spring管理,需要手动new
    // 提供有参和无参的构造器
    private EduSubjectService subjectService;
    
    public SubjectExcelListener() {
    }

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

    // 读取Excel文件内容,一行一行的读取
    @Override
    public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
        if (subjectData == null) {
            throw new GuliException(20001, "文件数据为空");
        }
        // 判断一级分类是否重复
        EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
        // 没有相同的一级分类,需要添加
        if (existOneSubject == null) {
            existOneSubject = new EduSubject();
            existOneSubject.setParentId("0");
            existOneSubject.setTitle(subjectData.getOneSubjectName());
            subjectService.save(existOneSubject);
        }
        // 获取一级分类id值
        String pid = existOneSubject.getId();
        // 判断二级分类是否重复
        EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
        // 没有相同的一级分类,需要添加
        if (existTwoSubject == null) {
            existTwoSubject = new EduSubject();
            existTwoSubject.setParentId(pid);
            existTwoSubject.setTitle(subjectData.getTwoSubjectName());
            subjectService.save(existTwoSubject);
        }
    }

    // 判断一级分类不能重复添加
    private EduSubject existOneSubject(EduSubjectService subjectService, String name) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name);
        wrapper.eq("parent_id", "0");
        EduSubject oneSubject = subjectService.getOne(wrapper);
        return oneSubject;
    }

    // 判断二级分类不能重复添加
    private EduSubject existTwoSubject(EduSubjectService subjectService, String name, String pid) {
        QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
        wrapper.eq("title", name);
        wrapper.eq("parent_id", pid);
        EduSubject twoSubject = subjectService.getOne(wrapper);
        return twoSubject;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

6、Swagger 测试

先准备一张表:
image.png
课程分类.xlsx

启动 EduApplication,访问 http://localhost:8001/swagger-ui.html,点击选择文件,添加刚刚制作的表格:
image.png
查看数据库:
image.png

前端实现

1、添加路由

	{
    path: '/subject',
    component: Layout,
    redirect: '/subject/list',
    name: '课程分类管理',
    meta: { title: '课程分类管理', icon: 'example' },
    children: [
      {
        path: 'list',
        name: '课程分类列表',
        component: () => import('@/views/edu/subject/list'),
        meta: { title: '课程分类列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加课程分类',
        component: () => import('@/views/edu/subject/save'),
        meta: { title: '添加课程分类', icon: 'tree' }
      }
    ]
  },

2、添加组件

在 views/edu 目录下新建 subject 目录,然后创建 list.vue 和 save.vue,然后在 save.vue 中添加组件:

<template>
  <div class="app-container">
    <el-form label-width="120px">
      <el-form-item label="信息描述">
        <el-tag type="info">excel模版说明</el-tag>
        <el-tag>
          <i class="el-icon-download"/>
          <a :href="'/static/01.xlsx'">点击下载模版</a>
        </el-tag>
      </el-form-item>
      <el-form-item label="选择Excel">
        <el-upload
          ref="upload"
          :auto-upload="false"
          :on-success="fileUploadSuccess"
          :on-error="fileUploadError"
          :disabled="importBtnDisabled"
          :limit="1"
          :action="BASE_API+'/eduservice/subject/addSubject'"
          name="file"
          accept="xls,xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel">
          <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
          <el-button
            :loading="loading"
            style="margin-left: 10px;"
            size="small"
            type="success"
            @click="submitUpload">上传到服务器</el-button>
        </el-upload>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      BASE_API: process.env.BASE_API, // 接口API地址
      importBtnDisabled: false, // 按钮是否禁用
      loading: false
    }
  },
  created() {

  },
  methods: {
    // 点击按钮上传文件到接口里面
    submitUpload() {
      this.importBtnDisabled = true
      this.loading = true
      // js: document.getElementById('upload').submit()
      this.$refs.upload.submit()
    },
    // 上传成功
    fileUploadSuccess() {
      // 提示信息
      this.loading = false
      this.$message({
        type: 'success',
        message: '添加课程分类成功'
      })
      // 跳转课程分类列表
      // 路由跳转
      this.$router.push({ path: '/subject/list' })
    },
    // 上传失败
    fileUploadError() {
      // 提示信息
      this.loading = false
      this.$message({
        type: 'error',
        message: '添加课程分类失败'
      })
    }
  }
}
</script>

3、测试

先清掉数据表 edu_subject 里面的数据,然后启动前后端项目,先点击选取文件,再点击上传到服务器:
image.png
成功:
image.png

课程列表功能

后端实现

image.png

1、创建实体类

在 entity 包下建立 subject 包,然后创建两个类:

// 一级分类
@Data
public class OneSubject {
    private String id;
    private String title;

    // 一个一级分类有多个二级分类
    private List<TwoSubject> children = new ArrayList<>();
}

// 二级分类
@Data
public class TwoSubject {
    private String id;
    private String title;
}

2、controller 层

在 EduSubjectController 类中添加方法:

// 课程分类列表(树形)
@ApiOperation(value = "获取课程分类列表")
@GetMapping("getAllSubject")
public R getAllSubject() {
    // list集合泛型是一级分类
    List<OneSubject> list = subjectService.getAllOneTwoSubject();
    return R.ok().data("list", list);
}

3、service 层

在EduSubjectService接口中增加方法:

// 课程分类列表(树形)
List<OneSubject> getAllOneTwoSubject();

在EduSubjectServiceImpl类中增加方法:

// 课程分类列表(树形)
@Override
public List<OneSubject> getAllOneTwoSubject() {
    // 1、查询所有一级分类
    QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
    wrapperOne.eq("parent_id", "0");
    List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);

    // 2、查询所有二级分类
    QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
    wrapperTwo.ne("parent_id", "0");
    List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);

    // 3、创建list集合,用于存储最终封装数据
    List<OneSubject> finalSubjectList = new ArrayList<>();

    // 4、封装一级分类
    for (int i = 0; i < oneSubjectList.size(); i++) {
        EduSubject eduSubject = oneSubjectList.get(i);
        OneSubject oneSubject = new OneSubject();
        // oneSubject.setId(eduSubject.getId());
        // oneSubject.setTitle(eduSubject.getTitle());
        // 快速封装属性,相当于上面两行的内容
        BeanUtils.copyProperties(eduSubject, oneSubject);//注意这里是Spring包下的BeanUtils工具类
        finalSubjectList.add(oneSubject);

        // 5、在一级分类循环遍历查询所有的二级分类
        List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
        for (int j = 0; j < twoSubjectList.size(); j++) {
            EduSubject subject = twoSubjectList.get(j);
            if (subject.getParentId().equals(eduSubject.getId())) {
                TwoSubject twoSubject = new TwoSubject();
                BeanUtils.copyProperties(subject, twoSubject);
                twoFinalSubjectList.add(twoSubject);
            }
        }
        // 把一级下面所有二级分类放到一级分类里面
        oneSubject.setChildren(twoFinalSubjectList);
    }
    return finalSubjectList;
}

4、Swagger 测试

启动项目,访问:http://localhost:8001/swagger-ui.html
image.png
点击 Try it out!,成功:
image.png

前端实现

1、js

在src/api/edu目录下建立subject.js:

import request from '@/utils/request'

export default {
  // 课程分类列表
  getSubjectList() {
    return request({
      url: '/eduservice/subject/getAllSubject',
      method: 'get'
    })
  }
}

2、vue

views/edu/subject/list.vue文件:

<template>
  <div class="app-container">
    <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
    <el-tree
      ref="tree2"
      :data="data2"
      :props="defaultProps"
      :filter-node-method="filterNode"
      class="filter-tree"
      default-expand-all
    />
  </div>
</template>

<script>
import subject from '@/api/edu/subject'

export default {
  data() {
    return {
      filterText: '',
      data2: [], // 返回所有分类数据
      defaultProps: {
        children: 'children',
        label: 'title'
      }
    }
  },
  watch: {
    filterText(val) {
      this.$refs.tree2.filter(val)
    }
  },
  created() {
    this.getAllSubjectList()
  },
  methods: {
    getAllSubjectList() {
      subject
        .getSubjectList()
        .then(response => {
          this.data2 = response.data.list
        })
    },
    filterNode(value, data) {
      if (!value) return true
      return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
    }
  }
}
</script>

效果:
image.png
输入数据还可以自动检索:
image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肉丝不切片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值