对象存储OSS,整合图片上传,nginx反向代理配置
对象存储OSS
在service模块下创建子模块service_oss
依赖
service-oss上级模块service已经引入service的公共依赖,所以service-oss模块只需引入阿里云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>
配置文件
#服务端口
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=your accessKeyId
aliyun.oss.file.keysecret=your accessKeySecret
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=gulischool-dyk
启动报错
启动时,会自动找数据库的配置,但是当前模块不用操作数据库,只是上传功能,没有配置数据库
解决启动报错
在@SpringBootApplication注解上加上exclude,解除自动加载DataSourceAutoConfiguration
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = {
"com.atguigu"})
public class OssApplication {
public static void main(String[] args) {
SpringApplication.run(OssApplication.class,args);
}
}
读取配置文件工具类
创建常量读取工具类:ConstantPropertiesUtil.java
使用@Value读取application.properties里的配置内容
用spring的 InitializingBean 的 afterPropertiesSet 来初始化配置信息,这个方法将在所有的属性被初始化后调用。
package com.atguigu.oss.utils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
@Component
// 常量类,读取配置文件application.properties中的配置
public class AliyunPropertiesUtils 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;
}
}
文件上传
创建Service接口:OssService
public interface OssService {
//文件上传至阿里云
String uploadFileAvatar(MultipartFile file);
}
OssServiceImpl
package com.atguigu.oss.service.impl;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.atguigu.oss.service.OssService;
import com.atguigu.oss.utils.AliyunPropertiesUtils;
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.util.UUID;
@Service
public class OssServiceImpl implements OssService {
//上传头像到oss
@Override
public String uploadFileAvatar(MultipartFile file) {
//工具类获取值
String endpoint = AliyunPropertiesUtils.END_POINT;
String accessKeyId = AliyunPropertiesUtils.ACCESS_KEY_ID;
String accessKeySecret = AliyunPropertiesUtils.ACCESS_KEY_SECRET;
String backetName = AliyunPropertiesUtils.BUCKET_NAME;
try {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
InputStream inputStream = file.getInputStream();
//获取文件名
String filename= file.getOriginalFilename();
//在文件名称里添加随机唯一的值
String uuid= UUID.randomUUID().toString().replace("-","");
filename=uuid+filename;
//把文件按日期分类
String datePath = new DateTime().toString("yyyy/MM/dd");
//第一个参数 Backet名称
//第二个参数 上传到oss文件路径和文件名称
//第三个参数 上传文件输入流
//拼接路径
filename=datePath+"/"+filename;
ossClient.putObject(backetName, filename, inputStream);
// 关闭OSSClient。
ossClient.shutdown();
//需要把上传文件到阿里云的路径手动拼接出来
// https://gulischool-dyk.oss-cn-beijing.aliyuncs.com/1.png
String url="https://"+backetName+"."+endpoint+"/"+filename;
return url;
} catch (IOException ioException) {
ioException.printStackTrace();
return null;
}
}
}
controller
@RestController
@RequestMapping("/eduoss/fileoss")
@CrossOrigin
@Api(tags="阿里云文件管理")
public class OssController {
@Autowired
private OssService ossService;
//上传头像的方法
@ApiOperation(value = "文件上传")
@PostMapping("/upload")
public ResultVo uploadOssFile(@ApiParam(name = "file", value = "文件", required = true)MultipartFile file){
//获取上传文件 MultipartFile
//返回上传路径
String url= ossService.uploadFileAvatar(file);
return ResultVo.ok().message("文件上传成功").data("url",url);
}
}
测试
配置nginx反向代理
config/dev.env.js,只有一个api地址的配置位置,而我们实际的后端有很多微服务,所以接口地址有很多,
我们可以使用nginx反向代理让不同的api路径分发到不同的api服务器中
在Nginx中配置对应的微服务服务器地址即可
注意应该放在http块里面
server{
listen 9001;
server_name localhost;
location ~ /eduservice/{
proxy_pass http://localhost:8001;
}
location ~ /eduoss/ {
proxy_pass http://localhost:8002;
}
location ~ /eduvod/ {
proxy_pass http://localhost:8003;
}
location ~ /cmsservice/ {
proxy_pass http://localhost:8004;
}
location ~ /edumsm/ {
proxy_pass http://localhost:8005;
}
location ~ /ucenterservice/ {
proxy_pass http://localhost:8006;
}
location ~ /orderservice/ {
proxy_pass http://localhost:8007;
}
location ~ /staservice/ {
proxy_pass http://localhost:8001;
}
}
重启nginx
进入nginx目录
nginx -s reload
修改前端的BASE_API
config里的dev.env.js
BASE_API: '"http://localhost:9001"'
前端整合图片上传组件
复制头像上传组件
从vue-element-admin复制组件:
vue-element-admin/src/components/ImageCropper
vue-element-admin/src/components/PanThumb
前端添加文件上传组件
<!-- 讲师头像 -->
<el-form-item label="讲师头像">
<!-- 头衔缩略图 -->
<pan-thumb :image="String(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:上传成功后的回调
这里field的值必须和后端接口MultipartFile file的形参名相同
<input type="file" name="file"/>
-->
<image-cropper
v-show="imagecropperShow"
:width="300"
:height="300"
:key="imagecropperKey"
:url="BASE_API + '/eduoss/fileoss/upload'"
field="file"
@close="close"
@crop-upload-success="cropSuccess"
/>
</el-form-item>
引入组件模块,声明初始变量
<script>
import teacherApi from "@/api/edu/teacher";
import ImageCropper from "@/components/ImageCropper";
import PanThumb from "@/components/PanThumb";
export default {
components: {
ImageCropper, PanThumb },
data() {
return {
teacher: {
name: "",
sort: 0,
level: 1,
career: "",
intro: "",
avatar: "",
},
//上传弹框组件是否显示
imagecropperShow: false,
imagecropperKey: 0, //上传组件key值
BASE_API: process.env.BASE_API, //获取dev.env.js里面地址
saveBtnDisabled: false, // 保存按钮是否禁用,
};
},
close() {
//关闭上传弹框的办法
this.imagecropperShow = false;
// 上传失败后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
this.imagecropperKey = this.imagecropperKey + 1;
},
//上传成功方法
cropSuccess(data) {
//这个方法封装好了返回值
this.imagecropperShow = false;
//上传之后接口返回图片地址
this.teacher.avatar = data.url;
this.imagecropperKey = this.imagecropperKey + 1;
},
完整版
<template>
<div class="app-container">
讲师添加
<el-form label-width="120px">
<el-form-item label="讲师名称">
<el-input v-model="teacher.name" />
</el-form-item>
<el-form-item label="讲师排序">
<el-input-number
v-model="teacher.sort"
controls-position="right"
:min="0"
/>
</el-form-item>
<el-form-item label="讲师头衔">
<el-select v-model="teacher.level" clearable placeholder="请选择">
<el-option :value="1" label="高级讲师" />
<el-option :value="2" label="首席讲师" />
</el-select>
</el-form-item>
<el-form-item label="讲师资历">
<el-input v-model="teacher.career" />
</el-form-item>
<el-form-item label="讲师简介">
<el-input v-model="teacher.intro" :rows="10" type="textarea" />
</el-form-item>
<!-- 讲师头像: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:上传成功后的回调
这里field的值必须和后端接口MultipartFile file的形参名相同
<input type="file" name="file"/>
-->
<image-cropper
v-show="imagecropperShow"
:width="300"
:height="300"
:key="imagecropperKey"
:url="BASE_API + '/eduoss/fileoss/upload'"
field="file"
@close="close"
@crop-upload-success="cropSuccess"
/>
</el-form-item>
<el-form-item>
<el-button
:disabled="saveBtnDisabled"
type="primary"
@click="saveOrUpdate"
>保存</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
import teacherApi from "@/api/edu/teacher";
import ImageCropper from "@/components/ImageCropper";
import PanThumb from "@/components/PanThumb";
export default {
components: {
ImageCropper, PanThumb },
data() {
return {
teacher: {
name: "",
sort: 0,
level: 1,
career: "",
intro: "",
avatar:
"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif",
},
//上传弹框组件是否显示
imagecropperShow: false,
imagecropperKey: 0, //上传组件key值
BASE_API: process.env.BASE_API, //获取dev.env.js里面地址
saveBtnDisabled: false, // 保存按钮是否禁用,
};
},
watch: {
$route(to, from) {
this.init();
},
},
created() {
this.init();
},
methods: {
init() {
//判断路径有id值,做修改
if (this.$route.params && this.$route.params.id) {
//从路径获取id值
const id = this.$route.params.id;
this.getInfo(id);
} else {
//路径没有id值,做添加
//清空表单
this.teacher = {
};
}
},
//根据讲师id查询方法
getInfo(id) {
teacherApi.getTeacherInfoById(id).then((response) => {
this.teacher = response.data.teacher;
});
},
saveOrUpdate() {
//判断修改还是添加
//根据teacher是否有id
if (!this.teacher.id) {
//添加
this.saveTeacher();
} else {
//修改
this.updateTeacherInfo();
}
},
//添加讲师的方法
saveTeacher() {
teacherApi
.addTeacher(this.teacher)
.then((response) => {
//提示信息
this.$message({
type: "success",
message: "添加成功!",
});
//回到列表页面 ,路由跳转
this.$router.push({
path: "/teacher/list" });
})
.catch((err) => {
});
},
updateTeacherInfo() {
teacherApi
.updateTeacher(this.teacher)
.then((response) => {
//提示信息
this.$message({
type: "success",
message: "修改成功!",
});
//回到列表页面 ,路由跳转
this.$router.push({
path: "/teacher/list" });
})
.catch((err) => {
});
},
close() {
//关闭上传弹框的办法
this.imagecropperShow = false;
// 上传失败后,重新打开上传组件时初始化组件,否则显示上一次的上传结果
this.imagecropperKey = this.imagecropperKey + 1;
},
//上传成功方法
cropSuccess(data) {
//这个方法封装好了返回值
this.imagecropperShow = false;
//上传之后接口返回图片地址
this.teacher.avatar = data.url;
this.imagecropperKey = this.imagecropperKey + 1;
},
},
};
</script>
注意
这一段我卡了很久如果有报错可能是nginx配置错误,或者前端里面路径有错误仔细检查几遍
ps如果觉得前端那个上传图片旋转不好想去掉只需
/src/components/PanThumb/index.vue 代码最后面的三个 hover 注释掉就没有旋转效果了