在线教育业务笔记02- 课程分类管理模块
一、EasyExcel基本使用
1、EasyExcel写操作
package com.atguigu.demo.excel;
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;
public class TestEasyExcel {
public static void main(String[] args) {
// 一、实现excel写的操作
// 1 设置写入文件夹地址和excel文件名称
String filename = "F:\\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;
}
}
2、EasyExcel读操作
2.1、创建读取操作的监听器
package com.atguigu.demo.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.Map;
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.2、调用实现最终读取
package com.atguigu.demo.excel;
import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.List;
public class TestEasyExcel {
public static void main(String[] args) throws Exception {
// 写法1:
String filename = "F:\\write.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(filename, DemoData.class, new ExcelListener()).sheet().doRead();
// 写法2:
InputStream in = new BufferedInputStream(new FileInputStream("F:\\01.xlsx"));
ExcelReader excelReader = EasyExcel.read(in, ReadData.class, new ExcelListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
二、课程分类管理添加课程和课程分类树形显示后端业务
1、添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>service</artifactId>
<groupId>com.atguigu</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service_edu</artifactId>
<dependencies>
<!--电子表格上传-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</project>
2、api接口
2.1、Controller
package com.atguigu.eduservice.controller;
import com.atguigu.commonutils.R;
import com.atguigu.eduservice.entity.subject.OneSubject;
import com.atguigu.eduservice.service.EduSubjectService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 课程科目 前端控制器
* @author Tzc
*/
@Api(description="课程分类管理")
@CrossOrigin //跨域
@RestController
@RequestMapping("/eduservice/subject")
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
/**
* //添加课程分类 使用导入Excel表格的方式
* //获取上传过来文件,把文件内容读取出来
* @param file
* @return
*/
@ApiOperation(value = "Excel批量导入")
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
//1 获取上传的excel文件 MultipartFile
// 上传过来excel文件
subjectService.saveSubject(file, subjectService);
//判断返回集合是否为空
return R.ok();
}
/**
* 查询所有课程分类列表以树形显示(树形)
* @return
*/
@GetMapping("getAllSubject")
public R getAllSubject() {
// list集合泛型是一级分类 因为一级分类中包含二级分类的集合
List<OneSubject> list = subjectService.getAllOneTwoSubject();
return R.ok().data("list",list);
}
}
2.2、创建和Excel对应的实体类(添加课程分类使用)
上传Excel模板阿里云下载地址
https://cor2022314.oss-cn-beijing.aliyuncs.com/mysql.xlsx
package com.atguigu.eduservice.entity.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 对应上传的Excel表格格式模板 用于数据保存
* @author Tzc
*/
@Data
public class SubjectData {
// 一级分类名称 index = 0 代表表格第一列
@ExcelProperty(index = 0)
private String oneSubjectName;
// 二级分类名称 index = 1 代表表格第二列
@ExcelProperty(index = 1)
private String twoSubjectName;
}
2.3、创建用于封装课程分类的vo对象(页面显示树形结构使用)![在这里插入图片描述](https://img-blog.csdnimg.cn/cc5c3861e70b4816b0fece589b7365c3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ2Nfc2hlbGw=,size_18,color_FFFFFF,t_70,g_se,x_16#pic_center)
2.3.1、第一种实现实体类
package com.atguigu.eduservice.entity.subject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author Tzc
* 课程列表一级分类 和一级分类的集合 用于数据前端显示
*/
@Data
public class OneSubject {
// 主键
private String id;
// 节点名称
private String title;
// 存储子节点的集合,初始化是为了避免空指针异常
private List<TwoSubject> children = new ArrayList<>();
}
package com.atguigu.eduservice.entity.subject;
import lombok.Data;
/**
* @author Tzc
* 课程列表二级分类
*/
@Data
public class TwoSubject {
private String id;
private String title;
}
2.3.2、第二种实现实体类
package com.atguigu.eduservice.entity.subject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author Tzc
* 课程列表一级分类 和一级分类的集合 用于数据前端显示
*/
@Data
public class OneSubject {
// 主键
private String id;
// 父节点的 id
private String parentId;
// 节点名称
private String title;
// 存储子节点的集合,初始化是为了避免空指针异常
private List<OneSubject> children = new ArrayList<>();
}
3、Service
3.1、接口
package com.atguigu.eduservice.service;
import com.atguigu.eduservice.entity.EduSubject;
import com.atguigu.eduservice.entity.subject.OneSubject;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 课程科目 服务类
* @author Tzc
*/
public interface EduSubjectService extends IService<EduSubject> {
/**
* 添加课程分类
* @param file 本地文件处理加载类
* @param subjectService 本类提供调用方法
*/
void saveSubject(MultipartFile file, EduSubjectService subjectService);
/**
* 查询所有课程分类列表以树形显示(树形)
* @return
*/
List<OneSubject> getAllOneTwoSubject();
}
3.2、实现类
package com.atguigu.eduservice.service.impl;
import com.alibaba.excel.EasyExcel;
import com.atguigu.eduservice.entity.EduSubject;
import com.atguigu.eduservice.entity.excel.SubjectData;
import com.atguigu.eduservice.entity.subject.OneSubject;
import com.atguigu.eduservice.entity.subject.TwoSubject;
import com.atguigu.eduservice.listener.SubjectExcelListener;
import com.atguigu.eduservice.mapper.EduSubjectMapper;
import com.atguigu.eduservice.service.EduSubjectService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* 课程科目 服务实现类
* @author Tzc
*/
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
/**
* 添加课程分类
* read读取excel内容
*
* @param file
* @param subjectService
*/
@Override
public void saveSubject(MultipartFile file, EduSubjectService subjectService) {
try {
// 文件输入流
InputStream in = file.getInputStream();
// 调用方法进行读取
EasyExcel.read(in, SubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 查询所有课程分类列表以树形显示(树形)
* @return
*/
@Override
public List<OneSubject> getAllOneTwoSubject() {
/* // 1 查询所有一级分类 parentid = 0
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id", "0");
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
// 2 查询所有二级分类 parentid != 0
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperTwo.ne("parent_id", "0");
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
List<EduSubject> eduSubjects = baseMapper.selectList(null);
// 创建list集合,用于存储最终封装数据
List<OneSubject> finalSubjectList = new ArrayList<>();
// 3 封装一级分类
// 查询出来所有的一级分类list集合遍历,得到每个一级分类对象,获取每个一级分类对象值,
// 封装到要求的list集合里面 List<OneSubject> finalSubjectList
// 遍历oneSubjectList集合
for (int i = 0; i < oneSubjectList.size(); i++) {
// 得到oneSubjectList每个eduSubject对象
EduSubject eduSubject = oneSubjectList.get(i);
// 把eduSubject里面值获取出来,放到OneSubject对象里面
OneSubject oneSubject = new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
// eduSubject值复制到对应oneSubject对象里面
BeanUtils.copyProperties(eduSubject, oneSubject);
//多个OneSubject放到finalSubjectList里面
finalSubjectList.add(oneSubject);
// 在一级分类循环遍历查询所有的二级分类
// 创建list集合封装每个一级分类的二级分类
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
// 遍历二级分类list集合
for (int m = 0; m < twoSubjectList.size(); m++) {
// 获取每个二级分类
EduSubject tSubject = twoSubjectList.get(m);
// 判断二级分类parentid和一级分类id是否一样
if (tSubject.getParentId().equals(eduSubject.getId())) {
// 把tSubject值复制到TwoSubject里面,放到twoFinalSubjectList里面
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(tSubject, twoSubject);
twoFinalSubjectList.add(twoSubject);
}
}
// 4.把一级下面所有二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
}*/
// 查询全部的 OneSubject 对象
List<EduSubject> eduSubjects = baseMapper.selectList(null);
ArrayList<OneSubject> oneSubjectsList = new ArrayList<>();
for (EduSubject eduSubject : eduSubjects) {
OneSubject oneSubject = new OneSubject();
BeanUtils.copyProperties(eduSubject,oneSubject);
oneSubjectsList.add(oneSubject);
}
System.out.println("============>>>>>>>"+oneSubjectsList.toString());
// 2.声明一个变量用来存储找到的根节点
// OneSubject root01=null;
ArrayList<OneSubject> root = new ArrayList<>();
// 3.创建 Map 对象用来存储 id 和 OneSubject 对象的对应关系便于查找父节点
Map<String, OneSubject> subjectMap = new HashMap<>();
// 4.遍历 oneSubjectsList 填充 subjectMap
for (OneSubject oneSubject : oneSubjectsList) {
String id = oneSubject.getId();
//将id(键)和对应的对象(值)存入集合
subjectMap.put(id, oneSubject);
}
// 5.再次遍历 oneSubjectsList 查找根节点、组装父子节点
for (OneSubject oneSubject : oneSubjectsList) {
// 6.获取当前 oneSubject 对象的 pid 属性值
String parentId = oneSubject.getParentId();
// 7.如果 parentId 为 0,判定为根节点
if (Objects.equals(parentId, "0")) {
//将根对象赋值个成员变量
//root01 = oneSubject;
root.add(oneSubject);
// 8.如果当前节点是根节点,那么肯定没有父节点,结束本次循环
continue;
}
// 9.如果pid不为 null,说明当前节点有父节点,那么可以根据pid到menuMap中查找对应的Menu对象
OneSubject father = subjectMap.get(parentId);
// 10.将当前节点存入父节点的 children 集合
father.getChildren().add(oneSubject);
}
return root;
}
}
4、创建监听器读取Excel
package com.atguigu.eduservice.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.atguigu.eduservice.entity.EduSubject;
import com.atguigu.eduservice.entity.excel.SubjectData;
import com.atguigu.eduservice.mapper.EduSubjectMapper;
import com.atguigu.eduservice.service.EduSubjectService;
import com.atguigu.servicebase.exceptionhandler.GuliException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 业务 用于读取excel表格内容 并保存到数据库
* @author Tzc
*/
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
// 因为SubjectExcelListener不交给spring进行管理,需要自己new,不能注入其他对象
// 不能实现数据库操作
public EduSubjectService subjectService;
public SubjectExcelListener() {}
// 创建有参数构造,传递subjectService用于操作数据库
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
/**
* 读取excel表格内容,一行一行进行读取 并保存到数据库中
* @param subjectData
* @param analysisContext
*/
@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);
}
}
/**
* 根据parent_id=0 可以判断是一级课程级分类 如果查询结果唯一返回查询的对象 用于于保存
* @param subjectService
* @param name 调用者传入的Excel表格一级分类名称
* @return
*/
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;
}
/**
* 根据二级课程分类的parent_id=一级课程分类的id值 可以判断是二级课程分类 如果查询结果唯一返回查询的对象 用于余保存
* @param subjectService
* @param name 调用者传入的Excel表格二级分类名称
* @param pid 一级分类id值
* @return
*/
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) {
}
}
三、课程分类前端页面显示(目标)
1、课程分类页面显示过程分析(思路)
2、过程分析实现(代码)
2.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.2、创建api调用后端接口
import request from '@/utils/request'
export default {
// 查询所有课程分类列表以树形显示(树形)
getSubjectList() {
return request({
url: '/eduservice/subject/getAllSubject',
method: 'get'
})
}
}
2.3、前端页面
<template>
<div class="app-container">
<!-- 搜索框 -->
<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
<!-- 课程分类列表以树形显示 ref="tree2" 相当于id-->
<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' // 分类名称
}
}
},
// 执行方法 页面加载之前
created() {
this.getAllSubjectList()
},
watch: {
filterText(val) {
// 代码中的this.$refs就是搜集所有的ref的一个对象。通过this.$refs 可以访问到此vue实例中的所有设置了ref属性的DOM元素,并对其进行操作
// 类似于原生js的document.getElementById("#id")
this.$refs.tree2.filter(val)
}
},
// 定义方法
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>
四、添加课程分类前端页面分析
<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="'https://cor2022314.oss-cn-beijing.aliyuncs.com/mysql.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="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(response) {
//提示信息
this.loading = false
this.$message({
type: 'success',
message: '添加课程分类成功'
})
//跳转课程分类列表
//路由跳转
this.$router.push({path:'/subject/list'})
},
//上传失败
fileUploadError() {
this.loading = false
this.$message({
type: 'error',
message: '添加课程分类失败'
})
}
}
}
</script>