一、阿里云OSS概述及开通
1.1 对象存储OSS
为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案- 阿里云OSS。
1.2 开通“对象存储OSS”服务
(1)申请阿里云账号
(2)实名认证
(3)开通“对象存储OSS”服务
(4)进入管理控制台
1.3 创建Bucket
选择:标准存储、公共读、不开通
1.3 上传默认头像
创建文件夹avatar,上传默认的用户头像
1.4 创建RAM子用户
二、阿里云OSS快速入门
2.1 创建Mavaen项目
com.itfxp
aliyun-oss
2.2 引入pom依赖
<dependencies>
<!--aliyunOSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2.3 找到编码时需要用到的常量值
(1)endpoint
(2)bucketName
(3)accessKeyId
(4)accessKeySecret
2.4 测试创建Bucket的连接
public class OSSTest {
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "your endpoint";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = "your accessKeyId";
String accessKeySecret = "your accessKeySecret";
String bucketName = "your bucketName";
@Test
public void testCreateBucket() {
// 创建OSSClient实例。
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 创建存储空间。
ossClient.createBucket(bucketName);
// 关闭OSSClient。
ossClient.shutdown();
}
}
2.5 判断存储空间是否存在
@Test
public void testExist() {
// 创建OSSClient实例。
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
boolean exists = ossClient.doesBucketExist(bucketName);
System.out.println(exists);
// 关闭OSSClient。
ossClient.shutdown();
}
2.6 设置存储空间的访问权限
@Test
public void testAccessControl() {
// 创建OSSClient实例。
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 设置存储空间的访问权限为:公共读。
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
// 关闭OSSClient。
ossClient.shutdown();
}
三、后端集成阿里云OSS
3.1 新建云存储微服务
3.1.1 创建子模块service-oss
在service模块下创建子模块service-oss
3.1.2 配置pom.xml
service-oss上级模块service已经引入service的公共依赖,所以service-oss模块只需引入阿里云oss相关依赖即可,
service父模块已经引入了service-base模块,所以Swagger相关默认已经引入
<dependencies>
<!-- 阿里云oss依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!-- 日期工具栏依赖 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
3.2 配置application.properties
#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置:dev、test、prod
spring.profiles.active=dev
#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=LTAI5tHFXMpivCYnRmXR7MnL
aliyun.oss.file.keysecret=uEi6NdCOdJd7hLNBQZ1ie3W5s54Tmg
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=zjledu
3.3 创建启动类
创建OssApplication.java
package com.itfxp.oss;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({"com.itfxp"})
public class OssApplication {
public static void main(String[] args) {
SpringApplication.run(OssApplication.class, args);
}
}
3.4 启动项目
报错
spring boot 会默认加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration这个类,
而DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了dataSource bean,又因为项目(oss模块)中并没有关于dataSource相关的配置信息,所以当spring创建dataSource bean时因缺少相关的信息就会报错。
解决办法:
方法1、在@SpringBootApplication注解上加上exclude,解除自动加载DataSourceAutoConfiguration
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
四、实现文件上传
4.1 从配置文件读取常量
创建常量读取工具类:ConstantPropertiesUtil.java
使用@Value读取application.properties里的配置内容
用spring的 InitializingBean 的 afterPropertiesSet 来初始化配置信息,这个方法将在所有的属性被初始化后调用。
package com.itfxp.oss.util;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 常量类,读取配置文件application.properties中的配置
*/
@Component
//@PropertySource("classpath:application.properties")
public class ConstantPropertiesUtil implements InitializingBean {
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;
@Value("${aliyun.oss.file.keyid}")
private String keyId;
@Value("${aliyun.oss.file.keysecret}")
private String keySecret;
@Value("${aliyun.oss.file.bucketname}")
private String bucketName;
public static String END_POINT;
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;
public static String BUCKET_NAME;
@Override
public void afterPropertiesSet() throws Exception {
END_POINT = endpoint;
ACCESS_KEY_ID = keyId;
ACCESS_KEY_SECRET = keySecret;
BUCKET_NAME = bucketName;
}
}
4.2 文件上传
创建Service接口:FileService.java
4.2.1 创建FileService接口
public interface FileService {
/**
* 文件上传至阿里云
* @param file
* @return
*/
String upload(MultipartFile file);
}
4.2.2 创建FileService接口实现类
参考SDK中的:Java->上传文件->简单上传->流式上传->上传文件流
package com.itfxp.oss.service.impl;
import com.aliyun.oss.*;
import com.itfxp.oss.service.FileService;
import com.itfxp.oss.util.ConstantPropertiesUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@Service
public class FileServiceImpl implements FileService {
@Override
public String upload(MultipartFile file) {
//获取阿里云存储相关常量
String endPoint = ConstantPropertiesUtil.END_POINT;
String accessKeyId = ConstantPropertiesUtil.ACCESS_KEY_ID;
String accessKeySecret = ConstantPropertiesUtil.ACCESS_KEY_SECRET;
String bucketName = ConstantPropertiesUtil.BUCKET_NAME;
String uploadUrl = null;
OSS ossClient = null;
try {
ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
InputStream inputStream = file.getInputStream();
// 获取文件的名称
String filename = file.getOriginalFilename();
// 创建PutObject请求。
ossClient.putObject(bucketName, filename, inputStream);
// 上传阿里云之后路径返回
uploadUrl = "https://" + bucketName+"."+endPoint+"/"+filename;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return uploadUrl;
}
}
4.2.3 创建OssController
创建controller:FileUploadController.java
@Api(description="阿里云文件管理")
@CrossOrigin //跨域
@RestController
@RequestMapping("/admin/oss/file")
public class FileController {
@Autowired
private FileService fileService;
/**
* 文件上传
*
* @param file
*/
@ApiOperation(value = "文件上传")
@PostMapping("upload")
public R upload(
@ApiParam(name = "file", value = "文件", required = true)
@RequestParam("file") MultipartFile file) {
String uploadUrl = fileService.upload(file);
//返回r对象
return R.ok().message("文件上传成功").data("url", uploadUrl);
}
}
4.3 重启oss服务
查看控制台是否有报错信息。
在service-oss中引入service-base模块
<dependency>
<groupId>com.itfxp</groupId>
<artifactId>service-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
4.5 Swagger中测试文件上传
http://localhost:8002/swagger-ui.html
4.6 文件上传代码完善
package com.itfxp.oss.service.impl;
import com.aliyun.oss.*;
import com.itfxp.oss.service.FileService;
import com.itfxp.oss.util.ConstantPropertiesUtil;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@Service
public class FileServiceImpl implements FileService {
@Override
public String upload(MultipartFile file) {
//获取阿里云存储相关常量
String endPoint = ConstantPropertiesUtil.END_POINT;
String accessKeyId = ConstantPropertiesUtil.ACCESS_KEY_ID;
String accessKeySecret = ConstantPropertiesUtil.ACCESS_KEY_SECRET;
String bucketName = ConstantPropertiesUtil.BUCKET_NAME;
String uploadUrl = null;
OSS ossClient = null;
try {
ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
InputStream inputStream = file.getInputStream();
// 随机文件名唯一
String uuid = UUID.randomUUID().toString().replace("-", "");
// 获取文件的名称
String filename = file.getOriginalFilename();
filename= uuid+filename;
// 把文件按照日期进行分类
String datePath = new DateTime().toString("yyyy-MM-dd");
filename= datePath+"/"+filename;
// 创建PutObject请求。
ossClient.putObject(bucketName, filename, inputStream);
// 上传阿里云之后路径返回
uploadUrl = "https://" + bucketName+"."+endPoint+"/"+filename;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
return uploadUrl;
}
}
五、配置nginx
5.1 配置nginx反向代理
5.1.1 安装window版的nginx
将nginx-1.12.0.zip解压到开发目录中
如:E:\development\nginx-1.12.0-guli-api
双击nginx.exe运行nginx
访问:localhost
5.1.2 配置nginx代理
在Nginx中配置对应的微服务服务器地址即可
注意:最好修改默认的 80端口改成81
http {
server {
listen 81;
......
},
......
server {
listen 9001;
server_name localhost;
location ~ /eduservice/ {
proxy_pass http://localhost:8001;
}
location ~ /eduoss/ {
proxy_pass http://localhost:8002;
}
}
}
5.1.3 重启nginx
nginx -s reload
5.1.4 测试
访问讲师列表接口:http://localhost:8201/admin/edu/teacher
访问获取用户信息接口:http://localhost:8201/user/info?token=admin
5.2 配置开发环境
5.2.1 修改config/dev.env.js
BASE_API: '"http://127.0.0.1:9001"'
5.2.2 重启前端程序
修改配置文件后,需要手动重启前端程序
六、用户头像上传
6.1 前端添加文件上传组件
将资料中的上传组件ImageCropper和PanThumb复制到前端项目components中
6.2 引用上传组件
src/views/edu/teacher/save.vue
<!-- 讲师头像:TODO -->
<el-form-item label="讲师头像">
<!-- 头衔缩略图 -->
<pan-thumb :image="teacher.avatar" />
<!-- 文件上传按钮 -->
<el-button
type="primary"
icon="el-icon-upload"
@click="imagecropperShow = true">更换头像
</el-button>
<!--
v-show:是否显示上传组件
:key:类似于id,如果一个页面多个图片上传控件,可以做区分
:url:后台上传的url地址
@close:关闭上传组件
@crop-upload-success:上传成功后的回调
-->
<image-cropper
v-show="imagecropperShow"
:width="300"
:height="300"
:key="imagecropperKey"
:url="BASE_API + '/admin/oss/file/upload'"
field="file"
@close="close"
@crop-upload-success="cropSuccess"
/>
</el-form-item>
6.3 上传组件相关配置
在data中设置以下三个值:
// 上传弹框组件是否显示
imagecropperShow: false,
// 上传组件key值
imagecropperKey: 0,
// 获取dev.env.js中的地址
BASE_API:process.env.BASE_API ,
在method中定义以下两个方法:
// 关闭上传弹窗的方法
close(){
},
// 上传成功方法
cropSuccess(){
},
引入组件模块
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
在data上面进行声明组件。
components: {ImageCropper,PanThumb},
配置上传接口URL
实现上传和图片回显
methods: {
//其他函数
.......
// 上传成功后的回调函数
cropSuccess(data) {
console.log(data)
this.imagecropperShow = false
this.teacher.avatar = data.url
// 上传成功后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
this.imagecropperKey = this.imagecropperKey + 1
},
// 关闭上传组件
close() {
this.imagecropperShow = false
// 上传失败后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
this.imagecropperKey = this.imagecropperKey + 1
}
}
6.4 测试文件上传
七、EasyExcel基本使用
7.1 EasyExcel简介
7.1.1 Excel导入导出的应用场景
1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输
7.1.2 EasyExcel特点
- Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
- EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
- EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)
7.2 EasyExcel 写操作
7.2.1 创建一个普通的maven项目
项目名:excel-easydemo
7.2.2 pom中引入xml相关依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
7.2.3 创建实体类
设置表头和添加的数据字段
import com.alibaba.excel.annotation.ExcelProperty;
//设置表头和添加的数据字段
public class DemoData {
//设置表头名称
@ExcelProperty("学生编号")
private int sno;
//设置表头名称
@ExcelProperty("学生姓名")
private String sname;
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public String toString() {
return "DemoData{" +
"sno=" + sno +
", sname='" + sname + '\'' +
'}';
}
}
7.2.4 实现写操作
(1)创建方法循环设置要添加到Excel的数据
//循环设置要添加的数据,最终封装到list集合中
private static List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
(2)实现最终的添加操作(写法一)
public static void main(String[] args) throws Exception {
// 写法1
String fileName = "F:\\11.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("写入方法一").doWrite(data());
}
(3)实现最终的添加操作(写法二)
public static void main(String[] args) throws Exception {
// 写法2,方法二需要手动关闭流
String fileName = "F:\\112.xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("写入方法二").build();
excelWriter.write(data(), writeSheet);
/// 千万别忘记finish 会帮忙关闭流
excelWriter.finish();
}
7.3 EasyExcel 读操作
7.3.1 创建实体类
import com.alibaba.excel.annotation.ExcelProperty;
public class ReadData {
//设置列对应的属性
@ExcelProperty(index = 0)
private int sid;
//设置列对应的属性
@ExcelProperty(index = 1)
private String sname;
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public String toString() {
return "ReadData{" +
"sid=" + sid +
", sname='" + sname + '\'' +
'}';
}
}
7.3.2 创建读取操作的监听器
//创建读取excel监听器
public class ExcelListener extends AnalysisEventListener<ReadData> {
//创建list集合封装最终的数据
List<ReadData> list = new ArrayList<ReadData>();
//一行一行去读取excle内容
@Override
public void invoke(ReadData user, AnalysisContext analysisContext) {
System.out.println("***"+user);
list.add(user);
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
7.3.3 调用实现最终的读取
public static void main(String[] args) throws Exception {
// 写法1:
String fileName = "F:\\01.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ReadData.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();
}
八、课程分类管理接口
8.0 生成subject相关代码
使用代码生成器,生成subject相关代码
配置mapper扫描路径以及驼峰命名方式
# 该配置就是将带有下划线的表字段映射为驼峰格式的实体类属性
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 配置mapper扫描路径
mybatis-plus.mapper-locations= classpath*:/mapper/**/*.xml
8.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>
8.2 业务处理
8.2.1 EduSubjectController
/**
* <p>
* 讲师 前端控制器
* </p>
*
* @author zjl
* @since 2022-06-26
*/
@Api("课程分类管理")
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
EduSubjectService eduSubjectService;
// 添加课程分类
@ApiOperation(value = "Excel批量导入")
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
//1 获取上传的excel文件 MultipartFile
//返回错误提示信息
eduSubjectService.importSubjectData(file,eduSubjectService);
//判断返回集合是否为空
return R.ok();
}
}
8.2.2 创建和Excel对应的实体类
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class ExcelSubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
8.2.3 编写SubjectService
(1)SubjectService接口
void importSubjectData(MultipartFile file,EduSubjectService subjectService);
(2)SubjectService接口实现类
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject>
implements EduSubjectService{
@Override
public void importSubjectData(MultipartFile file,EduSubjectService subjectService) {
try {
//1 获取文件输入流
InputStream inputStream = file.getInputStream();
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(inputStream, ExcelSubjectData.class, new SubjectExcelListener(subjectService)).sheet().doRead();
}catch(Exception e) {
e.printStackTrace();
throw new EduException(20002,"添加课程分类失败");
}
}
}
8.2.4 创建读取Excel监听器
package com.itfxp.eduservice.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itfxp.eduservice.entity.EduSubject;
import com.itfxp.eduservice.entity.excel.ExcelSubjectData;
import com.itfxp.eduservice.service.EduSubjectService;
import com.itfxp.servicebase.exceptionhandler.EduException;
public class SubjectExcelListener extends AnalysisEventListener<ExcelSubjectData> {
private EduSubjectService subjectService;
public SubjectExcelListener() {
}
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
@Override
public void invoke(ExcelSubjectData excelSubjectData, AnalysisContext analysisContext) {
if (excelSubjectData == null) {
throw new EduException(20001, "文件数据为空!");
}
// 一行一行的读
EduSubject eduOneSubject = this.existOneSubject(subjectService, excelSubjectData.getOneSubjectName());
if (eduOneSubject == null) { // 没有相同的一级分类
eduOneSubject = new EduSubject();
eduOneSubject.setParentId("0");
eduOneSubject.setTitle(excelSubjectData.getOneSubjectName());
subjectService.save(eduOneSubject);
}
// 判断二级分类
String pid = eduOneSubject.getId();
EduSubject eduTwoSubject = this.existTwoSubject(subjectService, excelSubjectData.getTwoSubjectName(), pid);
if (eduTwoSubject == null) { // 没有相同的一级分类
eduTwoSubject = new EduSubject();
eduTwoSubject.setParentId(pid);
eduTwoSubject.setTitle(excelSubjectData.getTwoSubjectName());
subjectService.save(eduTwoSubject);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
// 判断一级分类不能重复添加
private EduSubject existOneSubject(EduSubjectService subjectService, String name) {
// 查询数据库判断一级分类是否存在
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title", name);
wrapper.eq("parent_id", "0");
EduSubject eduSubjectOne = subjectService.getOne(wrapper);
return eduSubjectOne;
}
// 判断二级分类不能重复添加
private EduSubject existTwoSubject(EduSubjectService subjectService, String name, String pid) {
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title", name);
wrapper.eq("parent_id", pid);
EduSubject eduSubjectTwo = subjectService.getOne(wrapper);
return eduSubjectTwo;
}
}
8.2.5 修改实体类
在EduSubject实体类中,添加时间和主键策略
/**
* 课程类别ID
*/
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
8.2.6 swagger 测试
九、课程分类管理前端实现
9.1 配置路由
9.1.1 添加路由
在src/router/index.js中添加课程分类管理路由
// 课程分类管理
{
path: '/subject',
component: Layout,
redirect: '/subject/list',
name: '课程分类管理',
meta: { title: '课程分类管理', icon: 'nested' },
children: [
{
path: 'list',
name: '课程分类列表',
component: () => import('@/views/edu/subject/list'),
meta: { title: '课程分类列表', icon: 'table' }
},
{
path: 'import',
name: '导入课程分类',
component: () => import('@/views/edu/subject/import'),
meta: { title: '导入课程分类', icon: 'tree' }
}
]
},
9.1.2 添加vue组件
9.2 表单组件import.vue
8.3.1 js定义数据
<script>
export default {
data() {
return {
BASE_API: process.env.BASE_API, // 接口API地址
OSS_PATH: process.env.OSS_PATH, // 阿里云OSS地址
fileUploadBtnText: '上传到服务器', // 按钮文字
importBtnDisabled: false, // 按钮是否禁用,
loading: false
}
}
}
</script>
8.3.2 template
<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://zjledu.oss-cn-beijing.aliyuncs.com/excel%E6%A8%A1%E7%89%88/%E8%AF%BE%E7%A8%8B%E5%88%86%E7%B1%BB%E6%A8%A1%E7%89%88.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"
>{{ fileUploadBtnText }}</el-button
>
</el-upload>
</el-form-item>
</el-form>
</div>
</template>
8.3.3 js上传方法
methods: {
submitUpload() {
this.fileUploadBtnText = '正在上传'
this.importBtnDisabled = true
this.loading = true
this.$refs.upload.submit()
},
fileUploadSuccess(response) {
},
fileUploadError(response) {
}
}
8.3.4 回调函数
fileUploadSuccess(response) {
if (response.success === true) {
this.fileUploadBtnText = '导入成功'
this.loading = false
this.$message({
type: 'success',
message: response.message
})
}
},
fileUploadError(response) {
this.fileUploadBtnText = '导入失败'
this.loading = false
this.$message({
type: 'error',
message: '导入失败'
})
}
十、课程分类列表展示
10.1 前端实现
10.1.1 参考 views/tree/index.vue
直接把tree/index.vue文件中的代码复制到subject/list.vue文件中,运行看效果。
10.1.2 创建api
在api/edu/下创建subject.js
import request from '@/utils/request'
export default {
// 此参数的名称 要与后台接口的参数名称一致
getSubjectList() {
return request({
// 推荐方式
url: `/eduservice/subject/getAllSubject`,
method: 'get'
// params 后台使用RequestBody接收数据,是要的json数据,所以不能使用params进行传参
})
}
}
10.1.3 导入subject.js
在views/edu/subject/list.vue中引入 subject.js文件
<template>
<div class="app-container">
<el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
<el-tree
ref="subjectTree"
:data="subjectList"
:props="defaultProps"
:filter-node-method="filterNode"
class="filter-tree"
default-expand-all
/>
</div>
</template>
<script>
import subject from '@/api/edu/teacher/subject.js'
export default {
data() {
return {
filterText: '',
subjectList: [],
defaultProps: {
children: 'children',
label: 'title'
}
}
},
watch: {
filterText(val) {
this.$refs.subjectTree.filter(val)
}
},
created() {
this.fetchNodeList()
},
methods: {
fetchNodeList() {
subject.getNestedTreeList().then(response => {
if (response.success === true) {
this.subjectList = response.data.items
}
})
},
filterNode(value, data) {
if (!value) return true
return data.title.indexOf(value) !== -1
}
}
}
</script>
10.2 后端实现
10.2.1 创建vo
创建两个实体类,用于封装一级分类和二级分类
@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;
}
10.2.2 创建controller
// 课程分类列表显示(树形)
@GetMapping("/getAllSubject")
public R getAllSubject() {
List<OneSubject> list = eduSubjectService.getAllOneTwoSubject();
return R.ok().data("list",list);
}
10.2.3 创建service
service接口
List<OneSubject> getAllOneTwoSubject();
service接口实现类
// 课程分类
@Override
public List<OneSubject> getAllOneTwoSubject() {
// 查询出所有的一级分类 parentid = 0
QueryWrapper<EduSubject> oneSubjectQueryWrapper = new QueryWrapper<>();
oneSubjectQueryWrapper.eq("parent_id", 0);
List<EduSubject> oneSubjectsList = this.baseMapper.selectList(oneSubjectQueryWrapper);
// 查询出所有的二级分类 parentid != 0
QueryWrapper<EduSubject> twoSubjectQueryWrapper = new QueryWrapper<>();
twoSubjectQueryWrapper.ne("parent_id", 0);
List<EduSubject> twoSubjectsList = this.baseMapper.selectList(twoSubjectQueryWrapper);
// 用于封装最终分类的集合
List<OneSubject> finalSubjectList = new ArrayList<>();
// 封装一级分类
// 查询出来所有的一级分类list集合遍历,得到每个一级分类对象,获取每一个一级分类对象值
// 封装到要求的集合里面 List<OneSubject> finalSubjectList = new ArrayList<>();
oneSubjectsList.forEach((oneEduSubject -> {
// 获取每一个eduSubject对象,把eduSubject对象里面的内容放到OneSubject里面
OneSubject oneSubject = new OneSubject();
BeanUtils.copyProperties(oneEduSubject, oneSubject);
// 将多个OneSubject存放到finalSubjectList集合中
finalSubjectList.add(oneSubject);
// 封装二级分类
// 创建集合封装每个一级分类的二级分类
List<TwoSubject> finalTwoSubjectList = new ArrayList<>();
twoSubjectsList.forEach(twoEduSubject -> {
// 判断二级分类的parent_id与一级分类的id是否一样
if (twoEduSubject.getParentId().equals(oneEduSubject.getId())) {
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(twoEduSubject,twoSubject);
finalTwoSubjectList.add(twoSubject);
}
// 将二级分类添加封装到oneSubject分类中
oneSubject.setChildren(finalTwoSubjectList);
});
}));
return finalSubjectList;
}
10.3 优化前端过滤功能
修改filterNode方法,在搜索时,不区分大小写
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}
10.4 修改import.vue
修改上传课程分类成功后,跳转list.vue页面
// 跳转到课程分类列表
this.$router.push({path:'/subject/list'})