为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案- 阿里云OSS。
(1)申请阿里云账号
(2)实名认证
(3)开通“对象存储OSS”服务
(4)进入管理控制台
选择:标准存储、公共读、不开通
创建文件夹avatar,上传默认的用户头像
com.atguigu
aliyun-oss
com.aliyun.oss
aliyun-sdk-oss
2.8.3
junit
junit
4.12
(1)endpoint
(2)bucketName
(3)accessKeyId
(4)accessKeySecret
package com.atguigu.oss;
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 = "guli-file";
@Test
public void testCreateBucket() {
// 创建OSSClient实例。
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 创建存储空间。
ossClient.createBucket(bucketName);
// 关闭OSSClient。
ossClient.shutdown();
}
}
@Test
public void testExist() {
// 创建OSSClient实例。
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
boolean exists = ossClient.doesBucketExist(bucketName);
System.out.println(exists);
// 关闭OSSClient。
ossClient.shutdown();
}
@Test
public void testAccessControl() {
// 创建OSSClient实例。
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
// 设置存储空间的访问权限为:公共读。
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
// 关闭OSSClient。
ossClient.shutdown();
}
service-oss上级模块service已经引入service的公共依赖,所以service-oss模块只需引入阿里云oss相关依赖即可,
service父模块已经引入了service-base模块,所以Swagger相关默认已经引入
com.aliyun.oss
aliyun-sdk-oss
joda-time
joda-time
#服务端口
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
创建OssApplication.java
package com.guli.oss;
@SpringBootApplication
@ComponentScan({"com.atguigu"})
public class OssApplication {
public static void main(String[] args) {
SpringApplication.run(OssApplication.class, args);
}
}
报错
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)
创建常量读取工具类:ConstantPropertiesUtil.java
使用@Value读取application.properties里的配置内容
用spring的 InitializingBean 的 afterPropertiesSet 来初始化配置信息,这个方法将在所有的属性被初始化后调用。
/**
* 常量类,读取配置文件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.filehost}")
private String fileHost;
@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;
public static String FILE_HOST ;
@Override
public void afterPropertiesSet() throws Exception {
END_POINT = endpoint;
ACCESS_KEY_ID = keyId;
ACCESS_KEY_SECRET = keySecret;
BUCKET_NAME = bucketName;
FILE_HOST = fileHost;
}
}
创建Service接口:FileService.java
public interface FileService {
/**
* 文件上传至阿里云
* @param file
* @return
*/
String upload(MultipartFile file);
}
实现:FileServiceImpl.java
参考SDK中的:Java->上传文件->简单上传->流式上传->上传文件流
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 fileHost = ConstantPropertiesUtil.FILE_HOST;
String uploadUrl = null;
try {
//判断oss实例是否存在:如果不存在则创建,如果存在则获取
OSSClient ossClient = new OSSClient(endPoint, accessKeyId, accessKeySecret);
if (!ossClient.doesBucketExist(bucketName)) {
//创建bucket
ossClient.createBucket(bucketName);
//设置oss实例的访问权限:公共读
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
}
//获取上传文件流
InputStream inputStream = file.getInputStream();
//构建日期路径:avatar/2019/02/26/文件名
String filePath = new DateTime().toString("yyyy/MM/dd");
//文件名:uuid.扩展名
String original = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString();
String fileType = original.substring(original.lastIndexOf("."));
String newName = fileName + fileType;
String fileUrl = fileHost + "/" + filePath + "/" + newName;
//文件上传至阿里云
ossClient.putObject(bucketName, fileUrl, inputStream);
// 关闭OSSClient。
ossClient.shutdown();
//获取url地址
uploadUrl = "http://" + bucketName + "." + endPoint + "/" + fileUrl;
} catch (IOException e) {
throw new GuliException(ResultCodeEnum.FILE_UPLOAD_ERROR);
}
return uploadUrl;
}
}
3、控制层
创建controller:FileUploadController.java
package com.guli.oss.controller;
@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);
}
}
将接口地址加入nginx配置
location ~ /eduoss/ {
proxy_pass http://localhost:8001;
}
一、前端整合图片上传组件
从vue-element-admin复制组件:
vue-element-admin/src/components/ImageCropper
vue-element-admin/src/components/PanThumb
src/views/components-demo/avatarUpload.vue
src/views/edu/teacher/form.vue
template:
更换头像
引入组件模块
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
config/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'
}
export default {
components: { ImageCropper, PanThumb },
data() {
return {
//其它数据模型
......,
BASE_API: process.env.BASE_API, // 接口API地址
imagecropperShow: false, // 是否显示上传组件
imagecropperKey: 0 // 上传组件id
}
},
......,
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
}
}
}
前后端联调
1、数据导入:减轻录入工作量
2、数据导出:统计信息归档
3、数据传输:异构系统之间数据传输
项目名:excel-easydemo
com.alibaba
easyexcel
2.1.1
设置表头和添加的数据字段
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 + '\'' +
'}';
}
}
(1)创建方法循环设置要添加到Excel的数据
//循环设置要添加的数据,最终封装到list集合中
private static List data() {
List list = new ArrayList();
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();
}
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 + '\'' +
'}';
}
}
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
//创建读取excel监听器
public class ExcelListener extends AnalysisEventListener {
//创建list集合封装最终的数据
List list = new ArrayList();
//一行一行去读取excle内容
@Override
public void invoke(ReadData user, AnalysisContext analysisContext) {
System.out.println("***"+user);
list.add(user);
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
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();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ntAEjH0g-1586335226848)(file:///D:/我的文档/Documents/My Knowledge/temp/a45d8977-7752-48af-a0ec-a0b63eb31e5c/128/index_files/673ed30a-365a-42b5-9143-e8b44fd15cfc.png)]
// 课程分类管理
{
path: '/edu/subject',
component: Layout,
redirect: '/edu/subject/list',
name: 'Subject',
meta: { title: '课程分类管理', icon: 'nested' },
children: [
{
path: 'list',
name: 'EduSubjectList',
component: () => import('@/views/edu/subject/list'),
meta: { title: '课程分类列表' }
},
{
path: 'import',
name: 'EduSubjectImport',
component: () => import('@/views/edu/subject/import'),
meta: { title: '导入课程分类' }
}
]
},
三、表单组件import.vue1、js定义数据
excel模版说明
点击下载模版
选取文件
{{ fileUploadBtnText }}
methods: {
submitUpload() {
this.fileUploadBtnText = '正在上传'
this.importBtnDisabled = true
this.loading = true
this.$refs.upload.submit()
},
fileUploadSuccess(response) {
},
fileUploadError(response) {
}
}
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: '导入失败'
})
}
1、service-edu模块****配置依赖
com.alibaba
easyexcel
2.1.1
二、业务处理
package com.guli.edu.controller.admin;
@Api(description="课程分类管理")
@CrossOrigin //跨域
@RestController
@RequestMapping("/eduservice/subject")
public class SubjectAdminController {
@Autowired
private SubjectService subjectService;
//添加课程分类
@ApiOperation(value = "Excel批量导入")
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
//1 获取上传的excel文件 MultipartFile
//返回错误提示信息
subjectService.importSubjectData(file,subjectService);
//判断返回集合是否为空
return R.ok();
}
}
2、创建和Excel对应的实体类
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class ExcelSubjectData {
@ExcelProperty(index = 0)
private int oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
3、SubjectService
(1)接口
void batchImport(MultipartFile file);
(2)实现类
//添加课程分类
//poi读取excel内容
@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 GuliException(20002,"添加课程分类失败");
}
}
4、创建读取Excel监听器
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.atguigu.eduservice.entity.EduSubject;
import com.atguigu.eduservice.entity.vo.ExcelSubjectData;
import com.atguigu.eduservice.service.EduSubjectService;
import com.atguigu.servicebase.handler.GuliException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class SubjectExcelListener extends AnalysisEventListener {
public EduSubjectService subjectService;
public SubjectExcelListener() {}
//创建有参数构造,传递subjectService用于操作数据库
public SubjectExcelListener(EduSubjectService subjectService) {
this.subjectService = subjectService;
}
//一行一行去读取excle内容
@Override
public void invoke(ExcelSubjectData user, AnalysisContext analysisContext) {
if(user == null) {
throw new GuliException(20001,"添加失败");
}
//添加一级分类
EduSubject existOneSubject = this.existOneSubject(subjectService,user.getOneSubjectName());
if(existOneSubject == null) {//没有相同的
existOneSubject = new EduSubject();
existOneSubject.setTitle(user.getOneSubjectName());
existOneSubject.setParentId("0");
subjectService.save(existOneSubject);
}
//获取一级分类id值
String pid = existOneSubject.getId();
//添加二级分类
EduSubject existTwoSubject = this.existTwoSubject(subjectService,user.getTwoSubjectName(), pid);
if(existTwoSubject == null) {
existTwoSubject = new EduSubject();
existTwoSubject.setTitle(user.getTwoSubjectName());
existTwoSubject.setParentId(pid);
subjectService.save(existTwoSubject);
}
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {}
//判断一级分类是否重复
private EduSubject existTwoSubject(EduSubjectService subjectService,String name,String pid) {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",pid);
EduSubject eduSubject = subjectService.getOne(wrapper);
return eduSubject;
}
//判断一级分类是否重复
private EduSubject existOneSubject(EduSubjectService subjectService,String name) {
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id","0");
EduSubject eduSubject = subjectService.getOne(wrapper);
return eduSubject;
}
}
api/edu/subject.js
import request from '@/utils/request'
const api_name = '/admin/edu/subject'
export default {
getNestedTreeList() {
return request({
url: `${api_name}`,
method: 'get'
})
}
}
package com.guli.edu.vo;
@Data
public class SubjectVo {
private String id;
private String title;
}
package com.guli.edu.vo;
@Data
public class SubjectNestedVo {
private String id;
private String title;
private List children = new ArrayList<>();
}
@ApiOperation(value = "嵌套数据列表")
@GetMapping("")
public R nestedList(){
List subjectNestedVoList = subjectService.nestedList();
return R.ok().data("items", subjectNestedVoList);
}
接口
List nestedList();
实现Final
@Override
public List nestedList() {
//最终要的到的数据列表
ArrayList subjectNestedVoArrayList = new ArrayList<>();
//获取一级分类数据记录
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parent_id", 0);
queryWrapper.orderByAsc("sort", "id");
List subjects = baseMapper.selectList(queryWrapper);
//获取二级分类数据记录
QueryWrapper queryWrapper2 = new QueryWrapper<>();
queryWrapper2.ne("parent_id", 0);
queryWrapper2.orderByAsc("sort", "id");
List subSubjects = baseMapper.selectList(queryWrapper2);
//填充一级分类vo数据
int count = subjects.size();
for (int i = 0; i < count; i++) {
Subject subject = subjects.get(i);
//创建一级类别vo对象
SubjectNestedVo subjectNestedVo = new SubjectNestedVo();
BeanUtils.copyProperties(subject, subjectNestedVo);
subjectNestedVoArrayList.add(subjectNestedVo);
//填充二级分类vo数据
ArrayList subjectVoArrayList = new ArrayList<>();
int count2 = subSubjects.size();
for (int j = 0; j < count2; j++) {
Subject subSubject = subSubjects.get(j);
if(subject.getId().equals(subSubject.getParentId())){
//创建二级类别vo对象
SubjectVo subjectVo = new SubjectVo();
BeanUtils.copyProperties(subSubject, subjectVo);
subjectVoArrayList.add(subjectVo);
}
}
subjectNestedVo.setChildren(subjectVoArrayList);
}
return subjectNestedVoArrayList;
}
filterNode(value, data) {
if (!value) return true
return data.title.toLowerCase().indexOf(value.toLowerCase()) !== -1
}