Spring Boot 整合 OSS 实现文件上传

彭楷淳发布于 2021-01-23
预计阅读时间 8 分钟
总计 2k
浏览

本文主要讲解整合 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


如果你喜欢这个博客或发现它对你有用,欢迎你点击右下角 “OPEN CHAT” 进行评论。也欢迎你分享这个博客,让更多的人参与进来。如果在博客中使用的图片侵犯了您的版权,请联系博主删除它们。谢谢你!