1 整合阿里云OSS
01-阿里云存储OSS
一、对象存储OSS
为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案- 阿里云OSS。
1、开通“对象存储OSS”服务
(1)申请阿里云账号
(2)实名认证
(3)开通“对象存储OSS”服务
(4)进入管理控制台
2、创建Bucket
选择:标准存储、公共读、不开通
3、上传默认头像
创建文件夹avatar,上传默认的用户头像
02-后端集成OSS
一、新建云存储微服务
1、在service模块下创建子模块service-oss
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、配置application.properties
#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置:dev、test、prod
spring.profiles.active=dev
#阿里云OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=your endpoint
aliyun.oss.file.keyid=your accessKeyId
aliyun.oss.file.keysecret=your accessKeySecret
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=guli-file
4、logback-spring.xml
5、创建启动类
创建OssApplication.java
package com.atguigu.oss;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
public class OssApplication {
public static void main(String[] args) {
SpringApplication.run(OssApplication.class,args);
}
}
6、启动项目
报错:
spring boot 会默认加载org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration这个类,
而DataSourceAutoConfiguration类使用了@Configuration注解向spring注入了dataSource bean,又因为项目(oss模块)中并没有关于dataSource相关的配置信息,所以当spring创建dataSource bean时因缺少相关的信息就会报错。
@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)
即可成功:
二、实现文件上传
1、从配置文件读取常量
创建常量读取工具类:ConstantPropertiesUtil.java
使用@Value读取application.properties里的配置内容
用spring的 InitializingBean 的 afterPropertiesSet 来初始化配置信息,这个方法将在所有的属性被初始化后调用。
//当项目已启动,spring接口,spring加载之后,执行接口一个方法
@Component
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;
}
}
2、文件上传
创建Service接口:uploadFileAvatar.java
public interface OssService {
String uploadFileAvatar(MultipartFile file);
}
实现:OssServiceImpl.java
参考SDK中的:Java->上传文件->简单上传->流式上传->上传文件流
@Service
public class OssServiceImpl implements OssService {
//上传头像到oss
@Override
public String uploadFileAvatar(MultipartFile file) {
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint =ConstantPropertiesUtil.END_POINT;
String accessKeyId =ConstantPropertiesUtil.ACCESS_KEY_ID;
String accessKeySecret =ConstantPropertiesUtil.ACCESS_KEY_SECRET;
String bucketName =ConstantPropertiesUtil.BUCKET_NAME;
try {
// 创建OSSClient实例。
OSS ossClient =new OSSClientBuilder().build(endpoint,accessKeyId,accessKeySecret);
// 上传文件流。
InputStream inputStream = file.getInputStream();
//获取文件名称
String fileName = file.getOriginalFilename();
//调用oss方法实现上传
//第一个参数 Bucket名称
//第二个参数 上传到oss文件路径和文件名称 如/aa/bb/1.jpg
//第三个参数 上传文件输入流
ossClient.putObject(bucketName,fileName,inputStream);
// 关闭OSSClient。
ossClient.shutdown();
//把上传之后文件的路径返回
//需要把上传到阿里云oss路径手动拼接出来
//https://guli-file-1010.oss-cn-beijing.aliyuncs.com/11.jpg
String url ="https://"+bucketName+"."+endpoint+"/"+fileName;
return url;
}catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
3、控制层
创建controller:FileUploadController.java
@Api(description="阿里云文件管理")
@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
public class OssController {
@Autowired
private OssService ossService;
//上传头像的方法
@ApiOperation(value ="文件上传")
@PostMapping
public R uploadOssFile(@ApiParam(name ="file", value ="文件", required =true)
@RequestParam("file")MultipartFile file){
//获取上传文件
//返回上传到oss的路径
String url =ossService.uploadFileAvatar(file);
return R.ok().data("url",url);
}
}
4、重启oss服务
5、Swagger中测试文件上传
解决上传文件覆盖问题:
//1 在文件名称里面添加随机唯一的值
String uuid =UUID.randomUUID().toString().replaceAll("-","");
//如qwadsfadeasd01.jpg
fileName =uuid+fileName;
//2 把文件按照日期进行分类
//2020/11/12/01.jpg
//获取当前日期
String datePath =new DateTime().toString("yyyy/MM/dd");
//拼接
//如2020/11/12/qwadsfadeasd01.jpg
fileName =datePath +"/" + fileName;
测试:
6、配置nginx反向代理
配置nginx实现请求转发的功能:
验证:
03-前端整合上传组件
一、前端整合图片上传组件
1、复制头像上传组件
从vue-element-admin复制组件:
vue-element-admin/src/components/ImageCropper
vue-element-admin/src/components/PanThumb
2、前端参考实现
src/views/components-demo/avatarUpload.vue
3、前端添加文件上传组件
src/views/edu/teacher/save.vue
template:
引入组件模块:
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
4、设置默认头像(也可不设置)
onfig/dev.env.js中添加阿里云oss bucket地址
OSS_PATH:'"https://guli-file.oss-cn-beijing.aliyuncs.com"'
组件中初始化头像默认地址
const defaultForm = {
......,
avatar: process.env.OSS_PATH + '/avatar/default.jpg'
}
5、js脚本实现上传和图片回显
export default {
components: { ImageCropper, PanThumb },
data(){
return {
teacher:{
.........
},
//上传弹框组件是否显示
imagecropperShow:false,
imagecropperKey:0,//上传组件key值
BASE_API: process.env.BASE_API, // 接口API地址
saveBtnDisabled: false // 保存按钮是否禁用,
}
},
......,
methods: {
//其他函数
......,
close(){//关闭上传弹框方法
this.imagecropperShow = false;
// 上传失败后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
this.imagecropperKey = this.imagecropperKey + 1
},
cropSuccess(data){//上传成功方法
this.imagecropperShow = false;
//上传之后接口返回图片地址
this.teacher.avatar = data.url
// 上传成功后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
this.imagecropperKey = this.imagecropperKey + 1
},
二、测试文件上传
前后端联调
2 EasyExcel导入课程分类
01-EasyExcel读写Excel的基本使用
一、Excel导入导出的应用场景
1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输
二、EasyExcel简介
1、EasyExcel特点
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。
02-Excel写
一、创建项目,实现EasyExcel对Excel写操作
1、创建一个普通的maven项目
项目名:excel-easydemo
2、pom中引入xml相关依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
3、创建实体类
设置表头和添加的数据字段
@Data
public class DemoData {
//设置excel表头名称
@ExcelProperty("学生编号")
private Integer sno;
@ExcelProperty("学生姓名")
private String sname;
}
4 、实现写操作
TestEasyExcel.java
(1)创建方法循环设置要添加到Excel的数据
//循环设置要添加的数据,最终封装到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)实现最终的添加操作(写法一)
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());
}
(3)实现最终的添加操作(写法二)
public static void main(String[] args) throws Exception {
// 写法2,方法二需要手动关闭流
//实现excel写的操作
//1 设置写入文件夹地址和excel文件名称
String filename = "F:\\write.xlsx";
ExcelWriter excelWriter=EasyExcel.write(fileName,DemoData.class).build();
WriteSheet writeSheet=EasyExcel.writerSheet("写入方法二").build();
excelWriter.write(data(),writeSheet);
/// 千万别忘记finish 会帮忙关闭流
excelWriter.finish();
}
03-Excel读
一、实现EasyExcel对Excel读操作
1、创建实体类
@Data
public class DemoData {
//设置excel表头名称
@ExcelProperty(value = "学生编号",index = 0)
private Integer sno;
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
}
2、创建读取操作的监听器
public class ExcelListener extends AnalysisEventListener<DemoData> {
//一行一行读取excel内容
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("******"+demoData);
}
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头:"+headMap);
}
//读取完成之后
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
3、调用实现最终的读取
public class TestEasyExcel {
public static void main(String[] args) {
//实现excel读操作
// 写法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, DemoData.class, new ExcelListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
04-前端页面的实现
一、Excel模板(本案例中使用静态模板)
1、编辑Excel模板
2、将文件上传至阿里云OSS
二、配置路由
1、添加路由
{
path: '/subject',
component: Layout,
redirect: '/subject/table',
name: '课程分类管理',
meta: { title: '课程分类管理', icon: 'example' },
children: [
{
path: 'table',
name: '课程分类列表',
component: () => import('@/views/edu/subject/list'),
meta: { title: '课程分类列表', icon: 'table' }
},
{
path: 'tree',
name: '添加课程分类',
component: () => import('@/views/edu/subject/save'),
meta: { title: '添加课程分类', icon: 'tree' }
}
]
},
2、添加vue组件
三、表单组件save.vue
1、js定义数据
<script>
export default {
data(){
return {
BASE_API: process.env.BASE_API, // 接口API地址
importBtnDisabled: false, // 按钮是否禁用,
loading: false
}
},
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="'/static/01.xlsx'">点击下载模版</a>
</el-tag>
</el-form-item>
<el-form-item label="选择Excel">
<el-upload
ref="upload" //相当于id='upload'
:auto-upload="false"
:on-success="fileUploadSuccess"
:on-error="fileUploadError"
:disabled="importBtnDisabled"
:limit="1" //限制每次只能上传一个文件
:action="BASE_API+'/eduservice/edu-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>
3、js上传方法
methods:{
//点击按钮上传文件到接口里面
submitUpload(){
this.importBtnDisabled = true
this.loading = true
//js:document.getElementById("upload").submit()
this.$refs.upload.submit()
},
........
}
4、回调函数
//上传成功
fileUploadSuccess(){
//提示信息
this.loading = false
this.$message({
type: 'success',
message:'添加课程分类成功'
})
//跳转课程分类列表
},
//上传失败
fileUploadError(){
this.loading = false
this.$message({
type:'error',
message:'添加课程失败'
})
}
05-课程分类管理接口
一、添加依赖
1、service-edu模块配置依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
二、业务处理
1、EduSubjectController
@Api(description="课程分类管理")
@RestController
@RequestMapping("/eduservice/edu-subject")
@CrossOrigin //跨域
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
//添加课程分类
//获取上传过来的文件把文件内容读取出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file){
//上传过来的excel文件
subjectService.saveSubject(file,subjectService);
return R.ok();
}
}
2、创建和Excel对应的实体类
@Data
public class SubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
3、EduSubjectService
(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 in = file.getInputStream();
//调用方法进行读取
EasyExcel.read(in, SubjectData.class,new SubjectExcelListener(subjectService)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、创建读取Excel监听器
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
//因为SubjectExcelListener不能交给spring进行管理,需要自己new,不能注入其他对象
//不能实现数据库操作
public 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 exitOneSubject = this.exitOneSubject(subjectService, subjectData.getOneSubjectName());
if (exitOneSubject == null){//没有相同一级分类,进行添加
exitOneSubject = new EduSubject();
exitOneSubject.setParentId("0");
exitOneSubject.setTitle(subjectData.getOneSubjectName()); //一级分类名称
subjectService.save(exitOneSubject);
}
String pid = exitOneSubject.getId();
//添加二级分类
//判断二级分类是否重复
EduSubject exitTwoSubject = this.exitTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
if (exitTwoSubject == null){
exitTwoSubject = new EduSubject();
exitTwoSubject.setParentId(pid);
exitTwoSubject.setTitle(subjectData.getTwoSubjectName()); //一级分类名称
subjectService.save(exitTwoSubject);
}
}
//判断一级分类不能重复添加
private EduSubject exitOneSubject(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 exitTwoSubject(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) {
}
}
06-分类列表展示
一、前端实现
1、参考 views/tree/index.vue
2、创建api
api/edu/subject.js
import request from '@/utils/request'
export default{
//1 课程分类
getSubjectList(){
return request({
url:`/eduservice/edu-subject/getAllSubject`,
method:'get'
})
}
}
3、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'
}
}
},
created(){
this.getAllSubjectList()
},
watch: {
filterText(val) {
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>
二、后端实现
1、创建vo
//一级分类
@Data
public class OneSubject {
private String id;
private String title;
//一个一级分类含有多个二级分类
private Listchildren =new ArrayList<>();
}
//二级分类
@Data
public class TwoSubject {
private String id;
private String title;
}
2、创建controller
//课程分类列表(树形)
@ApiOperation(value = "嵌套数据列表")@GetMapping("getAllSubject")
public R getAllSubject(){
//list集合泛型是一级分类
List<OneSubject> list = subjectService.getAllOneTwoSubject();
return R.ok().data("list",list);
}
3、创建service
接口
//课程分类列表(树形)
List<OneSubject> getAllOneTwoSubject();
实现Final
@Override
public List<OneSubject> getAllOneTwoSubject() {
//1 获取一级分类 parent_id=0
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);
//创建list集合用于存储最终封装数据
List<OneSubject> finalSubjectList = new ArrayList<>();
//3 封装一级分类
//查询出来的所有一级分类list集合遍历,得到每一个分类对象,获取每一个一级分类对象值
//封装到要求的list集合里面 List<OneSubject> finalSubjectList
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());
BeanUtils.copyProperties(eduSubject,oneSubject);
//多个oneSubject放到finalSubjectList里面
finalSubjectList.add(oneSubject);
//4 封装二级分类
//在一级分类循环遍历查询所有二级分类
//创建list集合封装每一个一级分类的二级分类
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
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);
}
}
//把一级下面所有的二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
}
return finalSubjectList;
}