背景
以前我们一直采用:
后端转传 的方式进行文件上传 发现效率很低 且 后端服务器压力较大
现在我们优化成:
前端直传 后端提供token令牌 前端拿到令牌直接访问OSS上传文件效率提高了 且服务器压力也小了
准备工作
1:登录阿里云控制台创建RAM用户
这两项是OSS权限
这个STS角色访问权限 需要通过这个权限去获取token
创建角色
创建OSS存储桶
取名选择区域按提示完成创建
开放跨域请求
准备工作完毕
建个Spring Boot项目
引入依赖
<!--OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
<!-- JDK9 及以上需要引入下列依赖 应该是8以后移除了很多内容 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
写个接口应该难不倒你
@RestController
@RequestMapping("/manager/auth")
public class AuthController {
@Autowired
private OssService ossService;
@GetMapping(value = "/oss/token")
//这里的R是我自己封装的统一返回对象 你也可以直接返回JSON啥的
public R ossToken() {
return R.data(ossService.token());
}
}
# 文件上传
OSS:
# 区域
endpoint: "https://oss-cn-xxxxx.aliyuncs.com"
# 密钥
accessKeyId: "xxxxxxxxxxxxxxxxxxxxxxxx"
accessKeySecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# roleArn
roleArn: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 桶
bucketName: "xxxxxxxxx"
regionId: "cn-shanghai"
version: "2015-04-01"
@Data
@Configuration
public class OSSParam {
@Value("${OSS.endpoint}")
private String endpoint;
@Value("${OSS.accessKeyId}")
private String accessKeyId;
@Value("${OSS.accessKeySecret}")
private String accessKeySecret;
@Value("${OSS.roleArn}")
private String roleArn;
@Value("${OSS.bucketName}")
private String bucketName;
@Value("${OSS.regionId}")
private String regionId;
@Value("${OSS.version}")
private String version;
}
public interface OssService {
/**
* 获取上传文件的token
* @return
*/
AssumeRoleResponse token();
/**
* 批量获取图片连接
*
* @param filePathList
* @return
*/
Map<String, String> getUrlBatch(Collection<String> filePathList);
}
@Service
public class OssServiceImpl implements OssService {
/**
* 自定义的配置参数的类
*/
@Autowired
private OSSParam ossParam;
@Override
public AssumeRoleResponse token() {
//随便写 参考下列格式即可 不要有特殊符号
String roleSessionName = "session_1001";
//执行角色授权
IClientProfile profile = DefaultProfile.getProfile(
//cn-hangzhou 或者 cn-beijing 或者 cn-shanghai 这几个节点都可以 其他的好像不行
ossParam.getRegionId(),
//开通账号时下发的两个参数
ossParam.getAccessKeyId(),
ossParam.getAccessKeySecret()
);
DefaultAcsClient client = new DefaultAcsClient(profile);
final AssumeRoleRequest request = new AssumeRoleRequest();
//version 这里是固定的 2015-04-01
request.setVersion(ossParam.getVersion());
//开通角色时 角色信息里的 arn 属性
request.setRoleArn(ossParam.getRoleArn());
request.setRoleSessionName(roleSessionName);
//临时授权有效时间,从 900 到 3600 秒 测试的时候建议写时间给长一点,免得报错都不知道为啥
request.setDurationSeconds(900L);
try {
final AssumeRoleResponse response = client.getAcsResponse(request);
return response;
} catch (ClientException e) {
e.printStackTrace();
return null;
}
}
/**
* 文件临时访问 URL 获取
*
* @param filePathList 后端存储的文件路径
* 例如 : web/img/abc.jpg
* 其实就是你存储桶里面的文件夹路径
* 注意最前面不要带斜杠 例如这样 /web/img/abc.jpg 不要问我为什么 自己去试试就知道了
* @return 文件夹链接的 key : value 集合 调用方可以直接通过 map.get(filePath) 直接获取对应的链接
*/
@Override
public Map<String, String> getUrlBatch(Collection<String> filePathList) {
//这里 等于 Map<String, String> resultMap = new HashMap<>(); 只是我引用了工具包看起来舒服一点
Map<String, String> resultMap = Maps.newHashMap();
if (CollectionUtils.isEmpty(filePathList)) {
return resultMap;
}
//这个token可以用redis存起来过期了再重新生成,效率会高一点
//过期时间保持一致 或者redis缓存时间稍微短一点,错开那种刚好过期的时间点就好了
//比如 token 900秒(15分钟)到期,你就设置redis的key 720秒(12分钟)到期,完美错开
AssumeRoleResponse token = token();
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(
params.getStsEndpoint(),
token.getCredentials().getAccessKeyId(),
token.getCredentials().getAccessKeySecret(),
token.getCredentials().getSecurityToken()
);
//过期时间尽量短一点 1-3 分钟即可,
//只要前端能够显示出来就行了 (时间太长,可能会被人恶意访问造成流量泄露,产生大量费用。切记!!!!)
//以前小白的时候直接生成10年有效的链接。。。现在看起来是真的虎
Long expirationTime = System.currentTimeMillis() + params.getStsEffectiveTime();
Date expirationDate = new Date(expirationTime);
filePathList.forEach(filePath -> {
//生成GET请求链接
URL url = ossClient.generatePresignedUrl(params.getStsBucketName(), filePath, expirationDate);
resultMap.put(filePath, url.toString());
});
return resultMap;
}
}
用postman点一下看看
返回一堆东西!说明功夫没白费
来到前端工作
本人是个写后端的 前端没怎么学,但只需要知道大概原理即可
首先去GitHub 下载两个 sdk 文件
再写个HTML试试水 (建个VUE项目太麻烦 索性就写个HTML玩玩儿)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<form>
<input id="file" type="file" value="" onchange="getOssClient()" />
<input id="upload" value="点击上传" type="button" onclick="uploadFile()" />
</form>
</head>
<body>
</body>
<!-- 如果你没有前端代码运行工具 直接就写全路径就好了 不出意外的话是个浏览器就能玩 -->
<script type="text/javascript" src="C:\xxx\xxx\xxx\dist\aliyun-oss-sdk.js"></script>
<script type="text/javascript" src="C:\xxx\xxx\xxx\dist\aliyun-oss-sdk.min.js"></script>
<script type="text/javascript">
var params = {
accessKeyId: "对应上一步接口返回的 {credentials.accessKeyId}",
accessKeySecret: "对应上一步接口返回的 {credentials.accessKeySecret}",
stsToken: "对应上一步接口返回的 {credentials.securityToken}",
region: "你创建桶的区域 例如:oss-cn-chengdu",
bucket: "你创建的桶名称"
}
function getOssClient() {
return new OSS({
...params
})
}
function uploadFile() {
let client = getOssClient();
var file = document.getElementById('file').files;
console.info(file[0].name);
console.info(client);
//这里是两个参数 var1 = 你的文件全路径(包括文件名), var2 文件原件
var rep = client.put("/web_files/test/" + file[0].name, file[0])
console.info(rep);
}
</script>
</html>
好了!试试看,选一个文件,就把这个js文件传上去看看
发现返回了貌似链接的东西 我们只需要把这个链接传给后端保存起来就OK了
再去控制台看看到底有没有
好了赶紧去点赞收藏吧