阿里云的对象存储 OSS 提供海量、安全、低成本、高可靠的云存储服务,很多公司都会使用它来进行数据存储。因此,我们将讲师头像存储在这里。
进入阿里云官网,注册账号登录,并实名认证。之后找到 OSS 存储:
进入后点击【立即开通】,开通成功后,按钮变为【管理控制台】,点击后进入下列页面。点击【创建 Buket】即可创建一个存储空间:
点击刚创建的 bucket 名称,可以查看存储的东西,或者上传文件:
在主页面,有一个【Access Key】按钮,点击后按照要求获取 key 供后续代码使用:
阿里云 OSS Java 使用官方文档
(1)安装
在pom.xml中添加:
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>
(2)使用代码创建存储空间
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = "" ;
String accessKeySecret = "" ;
String bucketName = "" ;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建存储空间。
ossClient.createBucket(bucketName);
// 关闭OSSClient。
ossClient.shutdown();
(3)上传文件流
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
String accessKeyId = "" ;
String accessKeySecret = "" ;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流。
InputStream inputStream = new FileInputStream("" );
ossClient.putObject("" , "" , inputStream);
// 关闭OSSClient。
ossClient.shutdown();
(1)在 service 下右键选择 New Module… 创建一个子模块 service-oss;
(2)在 pom.xml 中添加 oss 依赖(版本信息在主模块中已经配置了):
<dependencies>
<!--阿里云oss依赖-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
</dependencies>
(3)创建 application.properties 以及启动类文件:
#服务端口号
server.port=8082
#服务名
spring.application.name=service-oss
#环境设置
spring.profiles.active=dev
#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=LTAI4GEMTPi4AYWfHuxkiGA9
aliyun.oss.file.keysecret=ijJ10mCflSnbVc89V3zFIYT3zQqpze
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=guli-edu-avatar-2020
要注意一点,由于上述配置文件中不需要配置数据库相关内容,启动时会报错,因此下面 @SpringBootApplication 注解中要加上 exclude = DataSourceAutoConfiguration.class
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(basePackages = {"com.atguigu"})
public class OssApplication {
public static void main(String[] args) {
SpringApplication.run(OssApplication.class, args);
}
}
(1)获取配置文件中的 OSS 各项值;
配置文件中的值可以通过 @Value(${xxx}) 注解进行获取,创建一个Util类专门用于获取配置文件中的值。但由于此时值是 private 的,所以需要将其赋值给一个public 对象。可以实现 InitializingBean 接口中的 afterPropertiesSet 方法,它在项目启动时,获取到值后即被调用。
注意这里不能直接定义 public 对象,再在它的上面加@Value注解,值会取不到。
@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 KEY_ID;
public static String KEY_SECRET;
public static String BUCKET_NAME;
@Override
public void afterPropertiesSet() throws Exception {
END_POINT = endPoint;
KEY_ID = keyId;
KEY_SECRET = keySecret;
BUCKET_NAME = bucketName;
}
}
(2)创建 controler 和 service 类
我们是先创建了一个 service 接口,再创建了它的一个实现类,
controller中的注入可以注入接口,也可以注入具体的实现类。(当有多个实现类时只能注入实现类,否则会报错有多个 bean 是 OssService 类型):
@Autowired
private OssService ossService;
但是接口中只定义了方法:
public interface OssService {
String uploadAvatar(MultipartFile file);
}
真正实现以及 @Service 注解都是在实现类上:
@Service
public class OssServiceImpl implements OssService {
@Override
public String uploadAvatar(MultipartFile file) {
String endPoint = ConstantPropertiesUtil.END_POINT;
String bucketName = ConstantPropertiesUtil.BUCKET_NAME;
String keyId = ConstantPropertiesUtil.KEY_ID;
String keySecret = ConstantPropertiesUtil.KEY_SECRET;
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);
try {
ossClient.putObject(bucketName, file.getOriginalFilename(), file.getInputStream());
// 关闭OSSClient。
ossClient.shutdown();
return "https://" + bucketName + "." + endPoint + "/" + file.getOriginalFilename();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
上述代码会出现文件重名覆盖的问题,通过修改文件名解决文件重名覆盖问题,并按日期文件夹分类:
String fileName = file.getOriginalFilename();
//添加uuid防止文件重名
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
fileName = uuid + fileName ;
//添加日期,按日期创建文件夹对文件分类
String date = new DateTime().toString("yyyy/MM/dd");
fileName = date + "/" + fileName;
由于现在后端有两个服务(eduservice 和 oss),一个在 8081,一个 8082 端口,那前端发送的请求如何被正确转发到对应端口呢?答案是使用 **nginx **。
nginx 能够实现:
如何使用 nginx 实现请求转发呢?
(1)首先下载 ngnix for windows,然后修改其配置文件 …\nginx-1.18.0\conf\nginx .conf:
http {
server {
listen 9001; //修改监听的端口号防止冲突
server_name localhost;
//添加需要转发的请求地址:~ 表示正则匹配
location ~ /eduservice/ {
proxy_pass http://localhost:8081;
}
location ~ /oss/ {
proxy_pass http://localhost:8082;
}
}
}
(2)修改前端端口号为 nginx 中配置的端口号:
(3)前端重新启动,并启动后端两个服务。再启动 nginx 。注意启动 nginx 前最好先 stop 掉它之前的进程:
nginx -s stop
确保 components 模块下有这两个模块,没有的话下载拷贝过来就行:
前端代码导入模块:
<script>
//导入
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
export default {
components: {ImageCropper, PanThumb} //声明
}
<el-form-item label="头像">
<!--头像缩略图-->
<pan-thumb :image="teacherForm.avatar"/>
<el-button type="primary" icon="el-icon-upload" @click="imageUploadShow=true">更换头像
</el-button>
//点击上传文件后调用的组件
<image-cropper
v-show="imageUploadShow"
:width="300"
:height="300"
:key="imageKey" //用于重新初始化组件
:url="BASE_API + '/oss/fileupload/avatar'" //后端文件上传的url地址
field="file"
@close="close"
@crop-upload-success="cropSuccess" //上传成功后调用的方法
/>
</el-form-item>
data() {
return {
imageUploadShow: false,
BASE_API: process.env.BASE_API, //从dev.env.js文件中读取的内容
imageKey: 0,
}
},
添加方法的定义:
close() {
this.imageUploadShow = false
this.imageKey = this.imageKey + 1 //用于重新初始化组件,防止上次成功后第二次点击上传出现bug
},
//这是上传成功后调用的方法,自动封装data的内容
cropSuccess(data) {
this.imageUploadShow = false
this.teacherForm.avatar = data.url
this.imageKey = this.imageKey + 1
}
后续内容要用到 EasyExcel,这里先介绍下如何使用EasyExcel进行读写。
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
注意:其实还需要引入 poi 依赖,前面已经引入过了。
建立一个实体类,用 @ExcelProperty 注解标识它在 excel 表中的列名
@Data
public class EasyExcelEntity {
@ExcelProperty(value = "学生编号", index = 0)
private Integer sno;
@ExcelProperty(value = "学生姓名", index = 1)
private String sname;
}
写操作:
String fileName = "D:\\stu.xlsx";
List<EasyExcelEntity> stuList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
EasyExcelEntity entity = new EasyExcelEntity();
entity.setSno(i);
entity.setSname("hi" + i);
stuList.add(entity);
}
EasyExcel.write(fileName, EasyExcelEntity.class).sheet("学生列表").doWrite(stuList);
同样需要上述一个实体类对应 excel 表中的列名,此外比 “写操作” 更麻烦一点的是需要建立一个监听器:
public class EasyExcelListener extends AnalysisEventListener<EasyExcelEntity> {
//一行一行读取代码时进行的操作
@Override
public void invoke(EasyExcelEntity easyExcelEntity, AnalysisContext analysisContext) {
System.out.println(easyExcelEntity.getSno() + ": " + easyExcelEntity.getSname());
}
//读取完进行的操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
读操作:
EasyExcel.read(fileName, EasyExcelEntity.class, new EasyExcelListener()).sheet().doRead();