一、注册阿里云账号并开通OSS服务
1、登录阿里云账号
2、创建一个bucket
3、创建子用户
对自用户分配权限,打开操作OSS的全部权限(也可根据业务需求进行更改)
4、配置上传跨域规则
- 任何来源: *
- 允许方法: POST
- 任何请求头Headers: *
二、文件上传方式
1、服务器直传方式
每个OSS的用户都会用到上传服务。Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS
和数据直传到OSS相比,以上方法有三个缺点:
- 上传慢:用户数据需先上传到到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用于数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度
- 扩展性差:如果后续用户多了,应用服务器会成为瓶颈
- 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器
2、服务端签名后前端直传
Web前端服务端请求签名,然后前端(Vue)直接上传,不会对服务端产生压力,而且安全可靠。
相关资料:服务端签名直传并设置上传回调概述
Java连接实例:Java实践OSS
上传回调流程
- Web前端请求应用服务器,获取上传所需参数(如OSS的accessKeyId、policy、callback等参数)
- 应用服务器返回相关参数
- Web前端直接向OSS服务发起上传文件请求
- 等上传完成后OSS服务会回调应用服务器的回调接口
- 应用服务器返回响应给OSS服务
- OSS服务将应用服务器回调接口的内容返回给Web前端
3、SpringBoot整合OSS实现文件上传
1、在pom.xml中添加相关依赖
<dependency> | |
<groupId>com.aliyun.oss</groupId> | |
<artifactId>aliyun-sdk-oss</artifactId> | |
<version>3.10.2</version> | |
</dependency> |
2、修改SpringBoot配置文件
#操作oss需要的一些参数 | |
aliyun: | |
oss: | |
accessKeyId: xxx # 阿里云的accessKeyId | |
accessKeySecret: xxx # accessKey 密码 | |
endPoint: xxx # Endpoint:在阿里云oss控制台查看自己使用的endpoint,eg: oss-cn-shanghai.aliyuncs.com | |
bucketName: xxx # bucket 名称 | |
policy: | |
expire: 300 # 签名有效期(S) | |
maxSize: 10 # 上传文件大小(M) | |
callback: http://localhost:8080/aliyun/oss/callback # 文件上传成功后的回调地址 | |
dir: | |
prefix: xxx/images/ # 上传文件夹路径前缀 | |
3、添加OSS的相关Java配置
用于配置OSS的连接客户端的OSSClient
/** | |
* TODO 用于配置OSS的连接客户端OSSClient | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 19:04 | |
*/ | |
@Configuration | |
@Component | |
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); | |
} | |
} |
4、封装前端上传策略返回对象
前端直传时所需要的参数
package org.pp.oss.model; | |
/** | |
* TODO 获取OSS上传文件授权返回结果 | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 19:07 | |
*/ | |
public class OssPolicyResult { | |
private String accessKeyId; | |
// @ApiModelProperty("用户表单上传的策略,经过base64编码过的字符串") 13 | |
private String policy; | |
// @ApiModelProperty("对policy签名后的字符串") 15 | |
private String signature; | |
// @ApiModelProperty("上传文件夹路径前缀") 17 | |
private String dir; | |
// @ApiModelProperty("oss对外服务的访问域名") 19 | |
private String host; | |
// @ApiModelProperty("上传成功后的回调设置") | |
private String callback; | |
// 忽略getter、setter方法 | |
} |
5、封装上传成功回调参数对象
当OSS上传成功后,会根据该配置参数来回调对应接口
package org.pp.oss.model; | |
/** | |
* TODO oss上传成功后的回调参数 | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 19:10 | |
*/ | |
public class OssCallbackParam { | |
//请求的回调地址 | |
private String callbackUrl; | |
//回调是传入request中的参数 | |
private String callbackBody; | |
//回调时传入参数的格式,比如表单提交形式 | |
private String callbackBodyType; | |
public String getCallbackUrl() { | |
return callbackUrl; | |
} | |
public void setCallbackUrl(String callbackUrl) { | |
this.callbackUrl = callbackUrl; | |
} | |
public String getCallbackBody() { | |
return callbackBody; | |
} | |
public void setCallbackBody(String callbackBody) { | |
this.callbackBody = callbackBody; | |
} | |
public String getCallbackBodyType() { | |
return callbackBodyType; | |
} | |
public void setCallbackBodyType(String callbackBodyType) { | |
this.callbackBodyType = callbackBodyType; | |
} | |
} | |
6、封装上传成功后回调结果对象
回调接口中返回的数据对象,封装了上传文件的信息
/** | |
* TODO oss上传文件的回调结果 | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 19:14 | |
*/ | |
public class OssCallbackResult { | |
private String filename;// 文件名称 | |
private String size;// 文件大小 | |
private String mimeType;// 文件的mimeType | |
private String width;// 图片文件的宽 | |
private String height;// 图片文件的高 | |
// 忽略getter、setter方法 | |
} |
7、添加OSS业务接口OssService
/** | |
* TODO oss上传管理Service | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 19:16 | |
*/ | |
public interface OssService { | |
/** | |
* oss上传策略生成 | |
* @return | |
*/ | |
OssPolicyResult policy(); | |
/** | |
* oss上传成功回调 | |
* @param request | |
* @return | |
*/ | |
OssCallbackResult callback(HttpServletRequest request); | |
} | |
8、OssService实现类
package org.pp.oss.service.impl;/* | |
package org.pp.oss.service.impl; | |
import org.pp.oss.model.OssCallbackResult; | |
import org.pp.oss.model.OssPolicyResult; | |
import org.pp.oss.service.OssService; | |
import javax.servlet.http.HttpServletRequest; | |
*/ | |
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 org.pp.oss.model.OssCallbackParam; | |
import org.pp.oss.model.OssCallbackResult; | |
import org.pp.oss.model.OssPolicyResult; | |
import org.pp.oss.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; | |
/** | |
* TODO | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 19: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; | |
/** | |
* 签名生成 | |
* | |
* @return | |
*/ | |
@Override | |
public OssPolicyResult policy() { | |
OssPolicyResult result = new OssPolicyResult(); | |
// 存储目录 | |
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); | |
String baseDir = 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.setCallbackBody("application/x-www-form-urlencoded"); | |
// 提交节点 | |
String action = "https://" + 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, baseDir); | |
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(baseDir); | |
result.setCallback(callbackData); | |
result.setHost(action); | |
} catch (Exception e) { | |
LOGGER.error("签名生成失败{e}", 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.setHeight(request.getParameter("height")); | |
result.setWidth(request.getParameter("width")); | |
return result; | |
} | |
} | |
9、定义OssController接口
package org.pp.oss.controller; | |
import cn.hutool.json.JSONObject; | |
import com.aliyun.oss.OSS; | |
import com.aliyun.oss.OSSClientBuilder; | |
import com.aliyun.oss.common.utils.BinaryUtil; | |
import com.aliyun.oss.model.MatchMode; | |
import com.aliyun.oss.model.PolicyConditions; | |
import org.pp.oss.model.OssCallbackResult; | |
import org.pp.oss.model.OssPolicyResult; | |
import org.pp.oss.service.OssService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.web.bind.annotation.CrossOrigin; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
import javax.servlet.http.HttpServletRequest; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
/** | |
* TODO Oss相关操作接口 | |
* | |
* @author ss_419 | |
* @version 1.0 | |
* @date 2023/5/28 20:43 | |
*/ | |
@RestController | |
@RequestMapping("/aliyun/oss") | |
@CrossOrigin | |
public class AliyunOssController { | |
@Autowired | |
private OssService ossService; | |
@CrossOrigin | |
@RequestMapping("/policys") | |
public Map<String,String> policysMap(){ | |
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 | |
String accessId = "xxx"; | |
String accessKey = "xxx"; | |
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。 | |
String endpoint = "oss-cn-shanghai.aliyuncs.com"; | |
// 填写Bucket名称,例如examplebucket。 | |
String bucket = "xxx"; | |
// 填写Host地址,格式为https://bucketname.endpoint。 | |
String host = "https://" + bucket + "."+ endpoint; | |
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。 | |
// String callbackUrl = "https://192.168.0.0:8888"; | |
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。 | |
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); | |
String formatData = dateFormat.format(new Date()); | |
String dir = "osstest/"+formatData+ "/"; | |
// 创建ossClient实例。 | |
OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey); | |
try { | |
long expireTime = 30; | |
long expireEndTime = System.currentTimeMillis() + expireTime * 1000; | |
Date expiration = new Date(expireEndTime); | |
PolicyConditions policyConds = new PolicyConditions(); | |
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); | |
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir); | |
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); | |
byte[] binaryData = postPolicy.getBytes("utf-8"); | |
String encodedPolicy = BinaryUtil.toBase64String(binaryData); | |
String postSignature = ossClient.calculatePostSignature(postPolicy); | |
Map<String, String> respMap = new LinkedHashMap<String, String>(); | |
respMap.put("accessId", accessId); | |
respMap.put("policy", encodedPolicy); | |
respMap.put("signature", postSignature); | |
respMap.put("dir", dir); | |
respMap.put("host", host); | |
respMap.put("expire", String.valueOf(expireEndTime / 1000)); | |
return respMap; | |
// respMap.put("expire", formatISO8601Date(expiration)); | |
// 回调数据 | |
// JSONObject jasonCallback = new JSONObject(); | |
// jasonCallback.put("callbackUrl", callbackUrl); | |
// jasonCallback.put("callbackBody", | |
// "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}"); | |
// jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded"); | |
// String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes()); | |
// respMap.put("callback", base64CallbackBody); | |
// | |
// JSONObject ja1 = JSONObject.fromObject(respMap); | |
// // System.out.println(ja1.toString()); | |
// response.setHeader("Access-Control-Allow-Origin", "*"); | |
// response.setHeader("Access-Control-Allow-Methods", "GET, POST"); | |
// response(request, response, ja1.toString()); | |
} catch (Exception e) { | |
// Assert.fail(e.getMessage()); | |
System.out.println(e.getMessage()); | |
} | |
return null; | |
} | |
/** | |
* oss上传签名生成 | |
* @return | |
*/ | |
@CrossOrigin | |
@RequestMapping("/policy") | |
public OssPolicyResult policy(){ | |
OssPolicyResult result = ossService.policy(); | |
System.out.println("result = " + result); | |
return result; | |
} | |
/** | |
* oss上传成功回调 | |
* @return | |
*/ | |
@RequestMapping("/callback") | |
public OssCallbackResult callback(HttpServletRequest request){ | |
OssCallbackResult callback = ossService.callback(request); | |
System.out.println("callback = " + callback); | |
return callback; | |
} | |
} | |
对接口进行测试,如下图所示,请求返回了oss文件上传时所需的对应参数
4、Vue文件上传测试代码
这里为了更加方便快捷的进行文件上传接口的测试,我选择使用Vue+Element-Ui来搭建一个简单的上传案例
1、创建Vue项目
在控制台中输入vue ui,启动vue项目图形管理界面
访问http://localhost:8000 ,进入如下图操作界面即代表启动成功
找到项目管理器,创建一个新Vue项目
这里选择Vue2版本
创建成功后添加本次案例所需要的依赖:
- axios:用于对后端服务发起Ajax请求
- element-ui:本案例使用到该ui组件库中的Upload,用于文件上传
在Vue项目中的main.js中启用对应依赖
import Vue from 'vue' | |
import App from './App.vue' | |
import ElementUI from 'element-ui'; | |
import 'element-ui/lib/theme-chalk/index.css'; | |
import axios from "axios"; | |
import VueAxios from "vue-axios"; | |
import router from './router' | |
import store from './store' | |
Vue.config.productionTip = false | |
// Vue.use(axios) | |
Vue.use(VueAxios,axios) | |
Vue.use(ElementUI); | |
new Vue({ | |
router, | |
store, | |
render: function (h) { return h(App) } | |
}).$mount('#app') | |
创建OssUpload组件,该组件可以在项目中引用
<template> | |
<el-upload | |
class="upload-demo" | |
:action="objData.host" | |
:before-upload="ossPolicy" | |
:data="objData" | |
:file-list="fileList" | |
list-type="picture"> | |
<el-button size="small" type="primary">点击上传</el-button> | |
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> | |
</el-upload> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
fileList: [], | |
objData: { | |
OSSAccessKeyId: '', | |
policy: '', | |
signature: '', | |
key: '', | |
host: '', | |
dir: '' | |
} | |
}; | |
}, | |
methods: { | |
ossPolicy(file) { | |
let _self = this; | |
// 在上传前 进行服务器签名 | |
return new Promise((resolve, reject) => { | |
this.axios.get("http://localhost:8080/aliyun/oss/policy") | |
.then(response => { | |
console.log(response) | |
_self.objData.OSSAccessKeyId = response.data.accessKeyId | |
_self.objData.policy = response.data.policy | |
_self.objData.signature = response.data.signature | |
_self.objData.dir = response.data.dir | |
_self.objData.host = response.data.host+'' | |
_self.objData.key = response.data.dir + "${filename}" | |
resolve(true) // 继续上传 | |
}) | |
.catch(error => { | |
console.log(error) | |
reject(false) | |
}) | |
} | |
) | |
} | |
} | |
} | |
</script> | |
<style> | |
</style> | |
在HelloWorld.vue中引用文件上传组件 | |
```js | |
<template> | |
<div class="hello"> | |
<h1>{{ msg }}</h1> | |
<OssUpload></OssUpload> | |
</div> | |
</template> | |
<script> | |
// 引用组件 | |
import OssUpload from "@/components/OssUpload.vue"; | |
export default { | |
name: 'HelloWorld', | |
components: {OssUpload}, | |
props: { | |
msg: String | |
} | |
} | |
</script> | |
<style > | |
</style> | |
前后端联调
-
启动后端服务
-
启动前端项目
选择文件进行上传,如下图所示即表示上传成功
查看对应的OSSBucket,图片已成功存储至OSS服务中