基于阿里云OSS的文件管理

最初,项目采用了阿里云OSS来管理文件的上传、下载功能,具体方案如下:

1.创建阿里云OSS的存储bucket,并开启读权限。

2.在Vue.js前端中创建OSS的工具包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
'use strict'
import { dateFormat } from '@/utils/utils'
var OSS = require('ali-oss')
const url = ''

export default {
/**
* 创建随机字符串
* @param num
* @returns {string}
*/
randomString(num) {
const chars = [
'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
]
let res = ''
for (let i = 0; i < num; i++) {
var id = Math.ceil(Math.random() * 35)
res += chars[id]
}
return res
},

/**
* 创建oss客户端对象
* @returns {*}
*/
createOssClient() {
return new Promise((resolve, reject) => {
const client = new OSS({
region: '',
accessKeyId: '',
accessKeySecret: '',
bucket: 'aaronwu-first-bucket',
secure: true // 上传链接返回支持https
})
resolve(client)
})
},
/**
* 文件上传
*/
ossUploadFile(option) {
const file = option.file
const self = this
// var url = '';
return new Promise((resolve, reject) => {
const date = dateFormat(new Date(), 'yyyyMMdd') // 当前时间
const dateTime = dateFormat(new Date(), 'yyyyMMddhhmmss') // 当前时间
const randomStr = self.randomString(4) // 4位随机字符串
const extensionName = file.name.substr(file.name.indexOf('.')) // 文件扩展名
const fileName = 'image/' + date + '/' + dateTime + '_' + randomStr + extensionName // 文件名字(相对于根目录的路径 + 文件名)
// 执行上传
self.createOssClient().then(client => {
// 异步上传,返回数据
resolve({
fileName: file.name,
fileUrl: fileName
})
// 上传处理
// 分片上传文件
client
.multipartUpload(fileName, file, {
progress: function(p) {
const e = {}
e.percent = Math.floor(p * 100)
// console.log('Progress: ' + p)
option.onProgress(e)
}
})
.then(
val => {
window.url = val
console.info('woc', url)
if (val.res.statusCode === 200) {
option.onSuccess(val)
return val
} else {
option.onError('上传失败')
}
},
err => {
option.onError('上传失败')
reject(err)
}
)
})
})
}
}

3.在前端需要上传文件的位置,调用工具包上传文件,并将生成的文件url使用axio传递到后端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
async fnUploadRequest(option) {
oss.ossUploadFile(option);
},
getVideoUrl(response, file, fileList) {
let urls = []
urls = window.url.res.requestUrls; //获取当前页面的url
if (urls[0].indexOf("?") != -1) { //判断是否存在参数
this.url = urls[0].replace(/(\?|#)[^'"]*/, ''); //去除参数
window.history.pushState({}, 0, this.url);
this.user.upicture = this.url
}
console.log('打印下标', this.url);
},
// 提交简历
pushResume(){

this.resume='姓名:'+this.user.uname+' '+'性别:'+this.user.ugender+' '+'生日:'+
this.user.ubirthday+' '+'身份:'+this.user.uidentity+' '+'邮箱:'+this.user.uemail+' '
+'手机号:'+this.user.uphone+' '+'学历:'+this.user.uEducationBgd+' '+'院校:'+this.user.uschool+' '
+'专业:'+this.user.umajor+' '+'专业排名:'+this.user.umajorTop+' '+'在校时间:'+this.user.uschoolTime+' '
+'实习公司:'+this.user.sxCompany+' '+'部门:'+this.user.sxDepartment+' '+'职位:'+this.user.sxPosition+' '
+'实习时间:'+this.user.sxTime+' '+'工作内容:'+this.user.sxContent+' '+'获奖经历:'+this.user.award+' '
+'自我评价:'+this.user.selfEvalute,

console.log(this.uEmail)
console.log(this.resume)
console.log(this.url)
console.log(this.job.jid)
console.log(this.company.cemail)
axios.post('addResume', {
uEmail:this.uEmail,
rInfo:this.resume,
rFile:this.url,
jId:this.job.jid,
cEmail:this.company.cemail,
}).then((response) => {
console.log(response.data)
this.$message({
showClose: true,
message: '恭喜你,投递成功',
type: 'success'
});
},
)
},

},

经过上面的处理,所有的文件在后端都仅仅以url字符串的方式存储,而文件则从前端直接使用js发送到了OSS服务器。这样的处理减轻了本地服务器网络带宽的压力,并且由于阿里云OSS是相当成熟的服务,所以用其存储文件是相当可靠的。

本博客站点也使用了阿里云OSS来进行文件管理,我对其的介绍链接如下:
https://aaronwu.fun/cn/aliyun-oss/

不过,考虑到本方法需要付费维持,并且相当于将文件的管理任务托付给了第三方,这并不能锻炼到我的能力,所以还是决定尝试在本地自己来管理文件,由此引入了MinIO。


MinIO简介

MinIO 是一款基于Go语言的高性能对象存储服务,它采用了Apache License v2.0开源协议,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。

下载MinIO在Windows下的安装包,下载地址:https://dl.min.io/server/minio/release/windows-amd64/minio.exe

下载完成后创建MinIO的数据存储目录,并使用如下启动命令MinIO服务;

1
minio.exe server D:\developer\env\minio\data --console-address ":9001"

MinIO服务运行成功后就可访问MinIO Console的管理界面了,输入账号密码minioadmin:minioadmin即可登录,访问地址:http://localhost:9001

MinIO的应用

下面是一张标准的使用MinIO作为存储的文件上传流程示意图。

1.在pom.xml中添加MinIO Java SDK的相关依赖:

1
2
3
4
5
6
<!--MinIO Java SDK依赖-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>

2.修改application.yml配置文件,在SpringBoot中开启文件上传功能,并添加MinIO客户端配置。

1
2
3
4
5
6
7
8
9
10
spring:
servlet:
multipart:
enabled: true #开启文件上传
max-file-size: 10MB # 限制文件上传大小为10M
minio:
endpoint: http://localhost:9001 # MinIO服务所在地址
bucketName: bler # 存储桶名称
accessKey: minioadmin # 访问的key
secretKey: minioadmin # 访问的秘钥

3.在Controller层控制文件的上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@Controller
@Api(tags = "MinioController")
@Tag(name = "MinioController", description = "MinIO对象存储管理")
@RequestMapping("/minio")
public class MinioController {

private static final Logger LOGGER = LoggerFactory.getLogger(MinioController.class);
@Value("${minio.endpoint}")
private String ENDPOINT;
@Value("${minio.bucketName}")
private String BUCKET_NAME;
@Value("${minio.accessKey}")
private String ACCESS_KEY;
@Value("${minio.secretKey}")
private String SECRET_KEY;

@ApiOperation("文件上传")
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public CommonResult upload(@RequestPart("file") MultipartFile file) {
try {
//创建一个MinIO的Java客户端
MinioClient minioClient =MinioClient.builder()
.endpoint(ENDPOINT)
.credentials(ACCESS_KEY,SECRET_KEY)
.build();
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET_NAME).build());
if (isExist) {
LOGGER.info("存储桶已经存在!");
} else {
//创建存储桶并设置只读权限
minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET_NAME).build());
BucketPolicyConfigDto bucketPolicyConfigDto = createBucketPolicyConfigDto(BUCKET_NAME);
SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder()
.bucket(BUCKET_NAME)
.config(JSONUtil.toJsonStr(bucketPolicyConfigDto))
.build();
minioClient.setBucketPolicy(setBucketPolicyArgs);
}
String filename = file.getOriginalFilename();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
// 设置存储对象名称
String objectName = sdf.format(new Date()) + "/" + filename;
// 使用putObject上传一个文件到存储桶中
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(BUCKET_NAME)
.object(objectName)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
minioClient.putObject(putObjectArgs);
LOGGER.info("文件上传成功!");
MinioUploadDto minioUploadDto = new MinioUploadDto();
minioUploadDto.setName(filename);
minioUploadDto.setUrl(ENDPOINT + "/" + BUCKET_NAME + "/" + objectName);
return CommonResult.success(minioUploadDto);
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("上传发生错误: {}!", e.getMessage());
}
return CommonResult.failed();
}

/**
* 创建存储桶的访问策略,设置为只读权限
*/
private BucketPolicyConfigDto createBucketPolicyConfigDto(String bucketName) {
BucketPolicyConfigDto.Statement statement = BucketPolicyConfigDto.Statement.builder()
.Effect("Allow")
.Principal("*")
.Action("s3:GetObject")
.Resource("arn:aws:s3:::"+bucketName+"/*.**").build();
return BucketPolicyConfigDto.builder()
.Version("2012-10-17")
.Statement(CollUtil.toList(statement))
.build();
}

@ApiOperation("文件删除")
@RequestMapping(value = "/delete", method = RequestMethod.POST)
@ResponseBody
public CommonResult delete(@RequestParam("objectName") String objectName) {
try {
MinioClient minioClient = MinioClient.builder()
.endpoint(ENDPOINT)
.credentials(ACCESS_KEY,SECRET_KEY)
.build();
minioClient.removeObject(RemoveObjectArgs.builder().bucket(BUCKET_NAME).object(objectName).build());
return CommonResult.success(null);
} catch (Exception e) {
e.printStackTrace();
}
return CommonResult.failed();
}
}

4.配置跨域请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class GlobalCorsConfig {

/**
* 允许跨域调用的过滤器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允许所有域名进行跨域调用
config.addAllowedOriginPattern("*");
//允许跨越发送cookie
config.setAllowCredentials(true);
//放行全部原始头信息
config.addAllowedHeader("*");
//允许所有请求方法跨域调用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

总结

相比较而言,在本地实现的MinIO需要将文件完整地从前端传递到后端,再进行存储,实际上增加了服务器的压力,并且增加了对其进行维护的工作量。但是,仅仅学会使用第三方的接口是不够的,终究要将数据存储在自己的上,而不是让第三方进行托管。