Spring Boot 整合 OSS 实现文件上传

Posted by 暮夏有五 on 2021-01-23
Estimated Reading Time 8 Minutes
Words 2k In Total
Viewed Times

本文主要讲解整合 OSS 实现文件上传的过程,采用的是服务端签名后前端直传的方式。阿里云对象存储服务(Object Storage Service,简称OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。OSS 可用于图片、音视频、日志等海量文件的存储。各种终端设备、Web 网站程序、移动应用可以直接向 OSS 写入或读取数据。

OSS 简介


阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。OSS 可用于图片、音视频、日志等海量文件的存储。各种终端设备、Web网站程序、移动应用可以直接向 OSS 写入或读取数据。

OSS中的相关概念

  • Endpoint:访问域名,通过该域名可以访问OSS服务的API,进行文件上传、下载等操作。
  • Bucket:存储空间,是存储对象的容器,所有存储对象都必须隶属于某个存储空间。
  • Object:对象,对象是 OSS 存储数据的基本单元,也被称为 OSS 的文件。
  • AccessKey:访问密钥,指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret。

OSS的相关设置


开通 OSS 服务

  • 登录阿里云官网;
  • 将鼠标移至产品标签页,单击对象存储 OSS,打开 OSS 产品详情页面;
  • 在 OSS 产品详情页,单击立即开通。

创建存储空间

点击网页右上角控制台按钮进入控制台:

img

选择我的云产品中的对象存储 OSS:

img

点击左侧存储空间的加号新建存储空间:

img

新建存储空间并设置读写权限为公共读:

img

跨域资源共享(CORS)的设置

由于浏览器处于安全考虑,不允许跨域资源访问,所以我们要设置 OSS 的跨域资源共享。

选择一个存储空间,打开其基础设置:

img

点击跨越设置的设置按钮:

img

点击创建规则:

img

进行跨域规则设置:

img

服务端签名后前端直传的相关说明

流程示例图

img

流程介绍

  1. Web前端请求应用服务器,获取上传所需参数(如 OSS 的 accessKeyId、policy、callback 等参数)
  2. 应用服务器返回相关参数
  3. Web前端直接向 OSS 服务发起上传文件请求
  4. 等上传完成后 OSS 服务会回调应用服务器的回调接口
  5. 应用服务器返回响应给 OSS 服务
  6. OSS 服务将应用服务器回调接口的内容返回给 Web 前端

整合 OSS 实现文件上传


引入依赖

在 pom.xml 中添加相关依赖:

1
2
3
4
5
6
<!-- OSS SDK 相关依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>2.5.0</version>
</dependency>

相关配置

修改 application.yml 文件,添加 OSS 相关配置。

注意:endpoint、accessKeyId、accessKeySecret、bucketName、callback、prefix 都要改为你自己帐号 OSS 相关的,callback 需要是公网可以访问的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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,前端直接上传文件时所需参数,从后端返回过来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 获取OSS上传文件授权返回结果
*/
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 上传成功后,会根据该配置参数来回调对应接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* OSS 上传成功后的回调参数
*/
public class OssCallbackParam {
@ApiModelProperty("请求的回调地址")
private String callbackUrl;
@ApiModelProperty("回调是传入request中的参数")
private String callbackBody;
@ApiModelProperty("回调时传入参数的格式,比如表单提交形式")
private String callbackBodyType;

//省略了所有getter,setter方法
}

创建 OSS 回调结果对象

创建 OssCallbackResult 类封装了上传文件的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* oss上传文件的回调结果
*/
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 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* oss上传管理Service
*/
public interface OssService {
/**
* oss上传策略生成
*/
OssPolicyResult policy();

/**
* oss上传成功回调
*/
OssCallbackResult callback(HttpServletRequest request);
}

创建 OSS 业务接口实现类

创建 OssServiceImpl 接口实现类:

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
/**
* oss上传管理Service实现类
*/
@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;
}

}

创建 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
/**
* OSS 相关操作接口
*/
@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);
}

}

进行接口测试


img

img

img

上传会调用两次请求,第一次访问本地接口获取上传的策略:

img

img

第二次调用 OSS 服务的接口进行文件上传:

img

img

可以看到上面接口调用并没有传入回调参数 callback,所以接口返回了 204 no content,这次我们传入回调参数 callback 试试,可以发现 OSS 服务回调了我们自己定义的回调接口,并返回了相应结果:

img

img

img

更多干货请移步:https://antoniopeng.com


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !