1.背景

采用服务端签名后直传方案有个问题:大多数情况下,用户上传数据后,应用服务器需要知道用户上传了哪些文件以及文件名;如果上传了图片,还需要知道图片的大小等。为此OSS提供了上传回调方案。OSS回调完成后,应用服务器再将结果返回给客户端,以便服务端实时了解用户上传了什么文件。

2.流程介绍

在这里插入图片描述
流程如下:
1.用户向应用服务器请求上传Policy和回调。
2.应用服务器返回上传Policy和回调设置。
3.用户直接向OSS发送文件上传请求。
4.OSS根据用户的回调设置,发送回调请求给应用服务器。
5.应用服务器返回响应给OSS。
6.OSS将应用服务器返回的内容返回给用户。

当用户要上传一个文件到OSS,而且希望将上传的结果返回给应用服务器,这时就需要设置一个回调函数,将请求告知应用服务器。用户上传完文件后,不会直接得到返回结果,而是先通知应用服务器,再把结果转达给用户。

官方文档地址:服务端签名直传并设置上传回调

3.编码

这里我们只需要在上一篇文章中的代码进行更改就好
阿里oss服务端签名后直传

修改SpringBoot配置文件,添加如下:

#   需要外网地址
   callback: http://tuanzi.natapp1.cc/aliyun/oss/callback

这里必须是外网能访问到的。推荐使用内网穿透!

OssPolicyResult类添加如下:

    @ApiModelProperty("上传成功后的回调设置")
    private String callback;

添加OSS上传成功后的回调参数对象OssCallbackParam

当OSS上传成功后,会根据该配置参数来回调对应接口。
package com.tuanzi.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;


/**
 * oss上传成功后的回调参数
 * @author 团子
 * @date 2019-07-31 11:31
 */
@Data
public class OssCallbackParam {
    @ApiModelProperty("请求的回调地址")
    private String callbackUrl;
    @ApiModelProperty("回调是传入request中的参数")
    private String callbackBody;
    @ApiModelProperty("回调时传入参数的格式,比如表单提交形式")
    private String callbackBodyType;
    
}

OSS上传成功后的回调结果对象OssCallbackResult

回调接口中返回的数据对象,封装了上传文件的信息。
package com.tuanzi.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;


/**
 * oss上传文件的回调结果
 * @author 团子
 * @date 2019-07-31 11:31
 */
@Data
public class OssCallbackResult {
    @ApiModelProperty("文件名称")
    private String filename;
    @ApiModelProperty("文件大小")
    private String size;
    @ApiModelProperty("文件的mimeType")
    private String mimeType;
    @ApiModelProperty("图片文件的宽")
    private String width;
    @ApiModelProperty("图片文件的高")
    private String height;
    
}

添加OSS业务接口OssService

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

添加OSS业务接口OssService的实现类OssServiceImpl

    /**
	 * 上传成功回调
	 */
    @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;
	}

原代码中添加如下:

    @Value("${aliyun.oss.callback}")
	private String ALIYUN_OSS_CALLBACK;
   /**
	 * 签名生成
	 */
	@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;
	}

添加OssController定义接口

 @ApiOperation(value = "oss上传成功回调")
    @RequestMapping(value = "/callback", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult<OssCallbackResult> callback(HttpServletRequest request) {
        logger.info("callback --------------> start");
        OssCallbackResult ossCallbackResult = ossService.callback(request);
        logger.info("callback --------------> end");
        return CommonResult.success(ossCallbackResult);
    }

好了这样就完成了
我们还是一样用postman进行测试

还是会调用两次请求,第一次访问本地接口获取上传的策略
在这里插入图片描述
我们可以看到返回参数多了callback

第二次调用oss服务 的接口进行文件上传
把callback加入进去
在这里插入图片描述
上传成功返回图片的信息

我们再来看看oss管理控制台
在这里插入图片描述

源码地址

代码地址

Q.E.D.