在线教育-谷粒学院学习笔记(四)
文章目录
- 1 内容介绍
- 2 阿里云存储OSS
- 3 Java代码操作阿里云oss
- 4 搭建阿里云oss项目环境
- 5 上传头像后端实现
- 6 Nginx使用
- 7 上传头像前端实现
- 8 课程分类管理介绍
- 9 EasyExcel工具
- 10 课程分类添加功能
1 内容介绍
-
添加讲师实现头像上传功能
- 阿里云oss存储服务
-
添加课程分类功能
- 使用EasyExcel读取excel内容,添加数据
-
课程分类的列表功能
- 树形结构显示
-
Nginx使用
2 阿里云存储OSS
-
登录阿里云官网,开通对象存储oss(在自己的账户中充几毛钱就行)
-
管理控制台
3 Java代码操作阿里云oss
- 准备:创建操作阿里云oss许可证(id和密钥)
- 创建AccessKey
- LTAI5tDgYwJnddqgv7eseKUp
dI3LnkakIe70taSwDTw6wspXQHN0xq
- LTAI5tDgYwJnddqgv7eseKUp
- 创建AccessKey
- 示例代码
public static void main(String[] args) throws Exception {
String endpoint = "oss-cn-chengdu.aliyuncs.com";
String accessKeyId = "LTAI5tDgYwJnddqgv7eseKUp";
String accessKeySecret = "dI3LnkakIe70taSwDTw6wspXQHN0xq";
String bucketName = "edu-mys";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建存储空间。
ossClient.createBucket(bucketName);
4 搭建阿里云oss项目环境
-
在service下创建service_oss子模块,并引入相关依赖
<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>
-
创建配置配置文件:application.properties
# 服务端口号 server.port=8002 # 服务名 spring.application.name=service-oss # 环境设置 spring.profiles.active=dev # 阿里云oss aliyun.oss.file.endpoint=oss-cn-chengdu.aliyuncs.com aliyun.oss.file.keyid=LTAI5tDgYwJnddqgv7eseKUp aliyun.oss.file.keysecret=dI3LnkakIe70taSwDTw6wspXQHN0xq # bucket可以在控制台创建,也可以java代码创建 aliyun.oss.file.bucketname=edu-mys
-
创建启动类 com/mys/oss/OssApplication.java
@SpringBootApplication @ComponentScan(basePackages = {"com.mys"}) public class OssApplication { public static void main(String[] args) { SpringApplication.run(OssApplication.class, args); } }
-
启动
-
报错:Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class
-
原因:启动时找数据库配置,但当前模块不需要操作数据库,只是上传到oss功能,没有配置数据库
-
解决
-
添加数据库配置
-
在启动类添加属性,默认不加载数据库配置(推荐)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
-
-
5 上传头像后端实现
1 创建常量类,读取配置文件内容
// 当项目一启动,spring加载之后,执行spring接口的一个方法
@Component
public class ConstantPropertiesUtils 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 创建controller
@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
public class OssController {
@Autowired
private OssService ossService;
// 上传头像方法
@PostMapping
public R uploadOssFile(MultipartFile file) {
// 返回上传文件到oss的路径
String url = ossService.uploadFileAvatar(file);
return R.ok().data("url", url);
}
}
3 service实现上传文件到oss过程
@Override
public String uploadFileAvatar(MultipartFile file) {
// 通过工具类获取值
String endpoint = ConstantPropertiesUtils.END_POINT;
String accessKeyId = ConstantPropertiesUtils.ACCESS_KEY_ID;
String accessKeySecret = ConstantPropertiesUtils.ACCESS_KEY_SECRET;
String bucketName = ConstantPropertiesUtils.BUCKET_NAME;
try {
// 创建OSSClient实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 获取上传文件的输入流
InputStream inputStream = file.getInputStream();
// 获取文件名称
String fileName = file.getOriginalFilename();
// 调用oss方法实现上传
// 参数:bucketName;上传到oss文件路径和文件名称;上传文件输入流
ossClient.putObject(bucketName, fileName, inputStream);
// 关闭OSSClient
ossClient.shutdown();
// 把上传之后文件路径返回,需手动拼接上传到阿里云的oss路径
// https://edu-mys.oss-cn-chengdu.aliyuncs.com/yy.JPG
String url = "https://" + bucketName + "." + endpoint + "/" + fileName;
return url;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
4 问题
-
多次上传相同名称文件,造成最后一次上传把之前的文件覆盖
-
解决:在文件名称添加随机唯一值,让每个文件名称不同
// 在文件名称里添加随机唯一的值 String uuid = UUID.randomUUID().toString().replaceAll("-", ""); fileName = uuid + fileName;
-
-
把文件进行分类管理
-
根据用户分类
-
根据日期分类,实现年月日分类
// 把文件按照日期进行分类 2019/11/12/01.jpg // 获取当前日期 String datePath = new DateTime().toString("yyyy/MM/dd"); // 拼接 fileName = datePath + "/" + fileName;
-
6 Nginx使用
-
反向代理服务器
-
功能
-
请求转发
-
负载均衡:把请求平均分担到不同的服务器
-
动静分离
-
-
特点:多路复用;关闭窗口后nginx不会停止
- 停止命令:nginx -s stop
配置nginx实现请求转发功能
-
下载地址:http://nginx.org/en/download.html
-
在nginx配置文件中进行配置 nginx.conf
-
修改ngix默认端口 80->81
server { listen 81; server_name localhost; .....
-
配置nginx转发规则
在http中配置: server { listen 9001; server_name localhost; location ~ /eduservice/ { proxy_pass http://localhost:8001; } location ~ /eduoss/ { proxy_pass http://localhost:8002; } }
-
-
修改前端请求地址,改为nginx地址
-
conf/dev.env.js
-
BASE_API: '"http://localhost:9001"',
-
-
nginx重启并测试:先停止再启动
7 上传头像前端实现
1 在添加讲师页面,创建上传组件,实现上传
使用element-ui
找源码里的两个组件,复制到前端项目src/components里面 ImageCropper、PanThumb
2 添加讲师页面中写上传组件
<!-- 讲师头像 -->
<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+'/eduoss/fileoss'"
field="file"
@close="close"
@crop-upload-success="cropSuccess"/>
</el-form-item>
3 使用组件
data()定义变量和初始值
imagecropperShow: false, // 上传弹框组件是否显示
imagecropperKey: 0, // 上传组件key值
BASE_API: process.env.BASE_API, // 获取dev.env.js里面的地址
saveBtnDisabled: false // 保存按钮是否禁用
4 引入和声明组件
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
components: { ImageCropper, PanThumb },
5 修改上传接口的地址
:url="BASE_API+'/eduoss/fileoss'"
6 编写close方法和上传成功的方法
// 关闭上传弹框的方法
close() {
this.imagecropperShow = false
// 上传组件初始化
this.imagecropperKey += 1
},
// 上传成功的方法
cropSuccess(data) {
this.imagecropperShow = false
this.imagecropperKey += 1
// 上传之后接口返回图片地址
this.teacher.avatar = data.url
},
8 课程分类管理介绍
- 存储结构
- 功能
- 添加——表格
- 列表
9 EasyExcel工具
-
特点:一行行读取
-
写操作
1 引入依赖
service_edu/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>
还需要poi的依赖,注意版本对应
2 创建实体类
service_edu/test
excel/DemoData.java
@Data public class DemoData { // 设置表头名称 @ExcelProperty("学生编号") private Integer sno; @ExcelProperty("学生姓名") private String name; }
3 写操作
public static void main(String[] args) { // 1 设置写入文件夹地址和excel名称 String filename = "D:\\write.xlsx"; // 2 调用easyexcel里面的方法实现写操作 EasyExcel.write(filename, DemoData.class).sheet("学生列表").doWrite(getData()); }
-
读操作
1 创建和excel对应实体类,标记对应列关系
@Data public class DemoData { // 设置表头名称 @ExcelProperty(value = "学生编号", index = 0) private Integer sno; @ExcelProperty(value = "学生姓名", index = 1) private String name; }
2 创建监听进行excel文件读取
public class ExcelListener extends AnalysisEventListener<DemoData> { // 一行一行读取excel内容 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("***" + demoData); } // 读取表头内容 @Override public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) { System.out.println("表头:" + headMap); } // 读取完成之后 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } }
3 最终方法调用
String filename = "D:\\write.xlsx"; EasyExcel.read(filename, DemoData.class, new ExcelListener()).sheet().doRead();
10 课程分类添加功能
1 引入EasyExcel依赖
2 使用代码生成器,生成课程分类的代码
CodeGenerator.java
3 创建实体类和excel对应关系
entity/excel/SubjectData.java
@Data
public class SubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
**4 controller service **
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
// 添加课程分类,需要获取上传过来的文件,把文件内容读取出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file) {
// 上传过来的Excel文件
subjectService.saveSubject(file, subjectService);
return R.ok();
}
}
@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 (Exception e) {
}
}
}
5 编写监听器SubjectExcelListener,处理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 existOneSubject = this.existOneSubject(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(subjectData.getTwoSubjectName(), pid);
if (existTwoSubject == null) { // 没有相同的一级分类,进行添加
existTwoSubject = new EduSubject();
existTwoSubject.setParentId("0");
existTwoSubject.setTitle(subjectData.getTwoSubjectName());
subjectService.save(existTwoSubject);
}
}
// 判断一级分类不能重复添加
private EduSubject existOneSubject(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(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) {
}
}