前言
阿里云对于对象存储服务的定义是:对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
选择方向
- 自有服务器(Linux或Windows)并在自身硬件达标的情况下,可以选择Minio对象存储服务。
- 无服务器或硬件不达标的情况下,可以选择阿里云OSS存储上云。
MinIO
介绍
Minio 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。
Minio 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。Minio 不仅提供了服务器、Web 访问、客户端,还提供了 Docker 安装,各种语言的 SDK、实例、实战秘籍等等,支持分布式部署。
基础概念
- Object: 存储到Minio的基本对象,如文件、字节流。
- Bucket:中文翻译叫桶,顾名思义就是用于存储。它是用来存储Object的逻辑空间,每个Bucket之间的数据是相互隔离的。
- Drive:即存储数据的磁盘,在MinIO启动时,以参数的方式传入。Minio中所有的对象数据都会存储在Drive里。
- Set:即一组Drive的集合,分布式部署根据集群规模自动划分一个或多个Set,每个Set中的Drive分布在不同位置。一个对象存储在一个Set上。
下载与启动
一、Windows环境
- 下载地址:dl.min.io/server/mini…
- 启动方式:(不要双击运行exe!)进入到minio.exe所在的目录,使用minio.exe server D:\minIO\data 命令启动minio服务,将
D:\minIO\data替换为希望 MinIO 存储数据的驱动器或目录的路径。 - 启动后状态如图所示:
- 使用方法:MinIO控制台是一个内置在MinIO Server中的嵌入式基于Web的对象浏览系统。将主机上运行的Web浏览器指向http://127.0.0.1:9000 并使用账号登录,账号密码初始都为minioadmin。可以使用浏览器创建存储桶、上传对象和浏览 MinIO 服务器的内容。
- 控制台效果如图所示:
二、Linux环境
- 下载地址:
$wget http://dl.minio.org.cn/server/minio/release/linux-amd64/minio - 赋予权限:
chmod +x minio - 启动方式:
./minio server /mnt/data - 使用方法:同上
代码实现部分
- pom引入
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.1</version>
</dependency>
- yml中配置参数
minio:
endpoint: http://localhost:9000 #MinIO服务所在地址
bucketName: mall #存储桶名称
accessKey: minioadmin #访问的key
secretKey: minioadmin #访问的秘钥
- Minio Bucket访问策略配置
/**
* Minio Bucket访问策略配置
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class BucketPolicyConfigDto {
private String Version;
private List<Statement> Statement;
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public static class Statement {
private String Effect;
private String Principal;
private String Action;
private String Resource;
}
}
- 文件上传返回结果
/**
* 文件上传返回结果
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class MinioUploadDto {
@ApiModelProperty("文件访问URL")
private String url;
@ApiModelProperty("文件名称")
private String name;
}
- MinioController实现
/**
* MinIO对象存储管理Controller
*/
@Api(tags = "MinioController", description = "MinIO对象存储管理")
@Controller
@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();
}
}
阿里OSS云储存
介绍
1.方便、快捷的使用方式
- 提供标准的RESTful API接口、丰富的SDK包、客户端工具、控制台。可以像使用文件一样方便地上传、下载、检索、管理用于Web网站或者移动应用的海量数据。
- 不限文件数量和大小。可以根据所需存储量无限扩展存储空间,解决了传统硬件存储扩容问题。
- 支持流式写入和读出。特别适合视频等大文件的边写边读业务场景。
- 支持数据生命周期管理。可以自定义将到期数据批量删除或者转入到低成本的归档服务。
2.强大、灵活的安全机制
- 灵活的鉴权,授权机制。提供STS和URL鉴权和授权机制,以及白名单、防盗链、主子账号功能。
- 提供用户级别资源隔离机制和多集群同步机制(可选)。
3.丰富、强大的增值服务
- 图片处理:支持jpg、png、bmp、gif、webp、tiff等多种图片格式的转换,以及缩略图、剪裁、水印、缩放等多种操作。
- 音视频转码:提供高质量、高速并行的音视频转码能力,让音视频文件轻松应对各种终端设备。
- 内容加速分发:OSS作为源站,搭配CDN进行加速分发,具有稳定、无回源带宽限制、性价比高、一键配置的特点。
基础概念
- Endpoint:访问域名,通过该域名可以访问OSS服务的API,进行文件上传、下载等操作。
- Bucket:存储空间,是存储对象的容器,所有存储对象都必须隶属于某个存储空间。
- Object:对象,对象是 OSS 存储数据的基本单元,也被称为 OSS 的文件。
- AccessKey:访问密钥,指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret。
相关设置
开通OSS服务
- 登录阿里云官网;
- 将鼠标移至产品标签页,单击对象存储 OSS,打开OSS 产品详情页面;
- 在OSS产品详情页,单击立即开通。
创建存储空间
- 点击网页右上角控制台按钮进入控制台
- 选择我的云产品中的对象存储OSS
- 点击左侧存储空间的加号新建存储空间
- 新建存储空间并设置读写权限为公共读
跨域资源共享(CORS)的设置
由于浏览器处于安全考虑,不允许跨域资源访问,所以我们要设置OSS的跨域资源共享。
- 选择一个存储空间,打开其基础设置
- 点击跨越设置的设置按钮
- 点击创建规则
- 进行跨域规则设置
服务端签名后前端直传的相关说明
流程示例图
流程介绍
- Web前端请求应用服务器,获取上传所需参数(如OSS的accessKeyId、policy、callback等参数)
- 应用服务器返回相关参数
- Web前端直接向OSS服务发起上传文件请求
- 等上传完成后OSS服务会回调应用服务器的回调接口
- 应用服务器返回响应给OSS服务
- OSS服务将应用服务器回调接口的内容返回给Web前端
整合OSS实现文件上传
在pom.xml中添加相关依赖
<!-- OSS SDK 相关依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.5.0</version>
</dependency>
修改SpringBoot配置文件
修改application.yml文件,添加OSS相关配置。
注意:endpoint、accessKeyId、accessKeySecret、bucketName、callback、prefix都要改为你自己帐号OSS相关的,callback需要是公网可以访问的地址。
# OSS相关配置信息
aliyun:
oss:
endpoint: oss-cn-shenzhen.aliyuncs.com # oss对外服务的访问域名
accessKeyId: test # 访问身份验证中用到用户标识
accessKeySecret: test # 用户用于加密签名字符串和oss用来验证签名字符串的密钥
bucketName: macro-oss # oss的存储空间
policy:
expire: 300 # 签名有效期(S)
maxSize: 10 # 上传文件大小(M)
callback: http://localhost:8080/aliyun/oss/callback # 文件上传成功后的回调地址
dir:
prefix: mall/images/ # 上传文件夹路径前缀
添加OSS的相关Java配置
用于配置OSS的连接客户端OSSClient。
package com.macro.mall.tiny.config;
import com.aliyun.oss.OSSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by macro on 2018/5/17.
*/
@Configuration
public class OssConfig {
@Value("${aliyun.oss.endpoint}")
private String ALIYUN_OSS_ENDPOINT;
@Value("${aliyun.oss.accessKeyId}")
private String ALIYUN_OSS_ACCESSKEYID;
@Value("${aliyun.oss.accessKeySecret}")
private String ALIYUN_OSS_ACCESSKEYSECRET;
@Bean
public OSSClient ossClient(){
return new OSSClient(ALIYUN_OSS_ENDPOINT,ALIYUN_OSS_ACCESSKEYID,ALIYUN_OSS_ACCESSKEYSECRET);
}
}
添加OSS上传策略封装对象OssPolicyResult
前端直接上传文件时所需参数,从后端返回过来。
package com.macro.mall.tiny.dto;
import io.swagger.annotations.ApiModelProperty;
/**
* 获取OSS上传文件授权返回结果
* Created by macro on 2018/5/17.
*/
public class OssPolicyResult {
@ApiModelProperty("访问身份验证中用到用户标识")
private String accessKeyId;
@ApiModelProperty("用户表单上传的策略,经过base64编码过的字符串")
private String policy;
@ApiModelProperty("对policy签名后的字符串")
private String signature;
@ApiModelProperty("上传文件夹路径前缀")
private String dir;
@ApiModelProperty("oss对外服务的访问域名")
private String host;
@ApiModelProperty("上传成功后的回调设置")
private String callback;
//省略了所有getter,setter方法
}
添加OSS上传成功后的回调参数对象OssCallbackParam
当OSS上传成功后,会根据该配置参数来回调对应接口。
package com.macro.mall.tiny.dto;
import io.swagger.annotations.ApiModelProperty;
/**
* oss上传成功后的回调参数
* Created by macro on 2018/5/17.
*/
public class OssCallbackParam {
@ApiModelProperty("请求的回调地址")
private String callbackUrl;
@ApiModelProperty("回调是传入request中的参数")
private String callbackBody;
@ApiModelProperty("回调时传入参数的格式,比如表单提交形式")
private String callbackBodyType;
//省略了所有getter,setter方法
}
OSS上传成功后的回调结果对象OssCallbackResult
回调接口中返回的数据对象,封装了上传文件的信息。
package com.macro.mall.tiny.dto;
import io.swagger.annotations.ApiModelProperty;
/**
* oss上传文件的回调结果
* Created by macro on 2018/5/17.
*/
public class OssCallbackResult {
@ApiModelProperty("文件名称")
private String filename;
@ApiModelProperty("文件大小")
private String size;
@ApiModelProperty("文件的mimeType")
private String mimeType;
@ApiModelProperty("图片文件的宽")
private String width;
@ApiModelProperty("图片文件的高")
private String height;
//省略了所有getter,setter方法
}
添加OSS业务接口OssService
package com.macro.mall.tiny.service;
import com.macro.mall.tiny.dto.OssCallbackResult;
import com.macro.mall.tiny.dto.OssPolicyResult;
import javax.servlet.http.HttpServletRequest;
/**
* oss上传管理Service
* Created by macro on 2018/5/17.
*/
public interface OssService {
/**
* oss上传策略生成
*/
OssPolicyResult policy();
/**
* oss上传成功回调
*/
OssCallbackResult callback(HttpServletRequest request);
}
添加OSS业务接口OssService的实现类OssServiceImpl
package com.macro.mall.tiny.service.impl;
import cn.hutool.json.JSONUtil;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.macro.mall.tiny.dto.OssCallbackParam;
import com.macro.mall.tiny.dto.OssCallbackResult;
import com.macro.mall.tiny.dto.OssPolicyResult;
import com.macro.mall.tiny.service.OssService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* oss上传管理Service实现类
* Created by macro on 2018/5/17.
*/
@Service
public class OssServiceImpl implements OssService {
private static final Logger LOGGER = LoggerFactory.getLogger(OssServiceImpl.class);
@Value("${aliyun.oss.policy.expire}")
private int ALIYUN_OSS_EXPIRE;
@Value("${aliyun.oss.maxSize}")
private int ALIYUN_OSS_MAX_SIZE;
@Value("${aliyun.oss.callback}")
private String ALIYUN_OSS_CALLBACK;
@Value("${aliyun.oss.bucketName}")
private String ALIYUN_OSS_BUCKET_NAME;
@Value("${aliyun.oss.endpoint}")
private String ALIYUN_OSS_ENDPOINT;
@Value("${aliyun.oss.dir.prefix}")
private String ALIYUN_OSS_DIR_PREFIX;
@Autowired
private OSSClient ossClient;
/**
* 签名生成
*/
@Override
public OssPolicyResult policy() {
OssPolicyResult result = new OssPolicyResult();
// 存储目录
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dir = ALIYUN_OSS_DIR_PREFIX+sdf.format(new Date());
// 签名有效期
long expireEndTime = System.currentTimeMillis() + ALIYUN_OSS_EXPIRE * 1000;
Date expiration = new Date(expireEndTime);
// 文件大小
long maxSize = ALIYUN_OSS_MAX_SIZE * 1024 * 1024;
// 回调
OssCallbackParam callback = new OssCallbackParam();
callback.setCallbackUrl(ALIYUN_OSS_CALLBACK);
callback.setCallbackBody("filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
callback.setCallbackBodyType("application/x-www-form-urlencoded");
// 提交节点
String action = "http://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT;
try {
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String policy = BinaryUtil.toBase64String(binaryData);
String signature = ossClient.calculatePostSignature(postPolicy);
String callbackData = BinaryUtil.toBase64String(JSONUtil.parse(callback).toString().getBytes("utf-8"));
// 返回结果
result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
result.setPolicy(policy);
result.setSignature(signature);
result.setDir(dir);
result.setCallback(callbackData);
result.setHost(action);
} catch (Exception e) {
LOGGER.error("签名生成失败", e);
}
return result;
}
@Override
public OssCallbackResult callback(HttpServletRequest request) {
OssCallbackResult result= new OssCallbackResult();
String filename = request.getParameter("filename");
filename = "http://".concat(ALIYUN_OSS_BUCKET_NAME).concat(".").concat(ALIYUN_OSS_ENDPOINT).concat("/").concat(filename);
result.setFilename(filename);
result.setSize(request.getParameter("size"));
result.setMimeType(request.getParameter("mimeType"));
result.setWidth(request.getParameter("width"));
result.setHeight(request.getParameter("height"));
return result;
}
}
添加OssController定义接口
package com.macro.mall.tiny.controller;
import com.macro.mall.tiny.common.api.CommonResult;
import com.macro.mall.tiny.dto.OssCallbackResult;
import com.macro.mall.tiny.dto.OssPolicyResult;
import com.macro.mall.tiny.service.impl.OssServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* Oss相关操作接口
* Created by macro on 2018/4/26.
*/
@Controller
@Api(tags = "OssController", description = "Oss管理")
@RequestMapping("/aliyun/oss")
public class OssController {
@Autowired
private OssServiceImpl ossService;
@ApiOperation(value = "oss上传签名生成")
@RequestMapping(value = "/policy", method = RequestMethod.GET)
@ResponseBody
public CommonResult<OssPolicyResult> policy() {
OssPolicyResult result = ossService.policy();
return CommonResult.success(result);
}
@ApiOperation(value = "oss上传成功回调")
@RequestMapping(value = "callback", method = RequestMethod.POST)
@ResponseBody
public CommonResult<OssCallbackResult> callback(HttpServletRequest request) {
OssCallbackResult ossCallbackResult = ossService.callback(request);
return CommonResult.success(ossCallbackResult);
}
}