在网上查了许久前后端分类的项目中oss上传的方法,都不是特别的满意,自己踩了很多坑,终于写了一个简单的,涉及前后端一块儿的文件上传功能,
在读这篇文章之前,我先假设你已经开通了oss服务,并且也已经了解了简单的使用操作,
我们开通阿里云之后肯定会有一个AccessKey ID和AccessKey Secret,现在阿里云不建议我们所有的服务都要使用这一对钥匙,我们可以通过这一对钥匙申请获取一个短暂的钥匙,这个钥匙在指定时间内可以对我们的bucket有一定的权限,如下指定时间到期,就无法再操作,这就是我们前后端都要写的关键。
我们可以选择图片上传是在前端进行还是后端进行,
前端进行的话:像oss服务发送请求,附带图片,然后取到返回图片的url地址,在发送给后端,上传图片需要钥匙或者临时钥匙,不太安全
后端进行的话:前端发送请求,附带图片,后端获取图片,发送到oss里面,
图片需要先发送到后端,图片一般都是很大的,会给服务器造成很大的压力
这里就得到了一个较为使用的解决方法,前端在上传图片之前,先向后端发送一个请求,后端根据请求返回一个相应的临时钥匙,前端在拿着这个临时的钥匙,去向oss发送请求,得到url之后传给后端,这个后端服务器压力变小了,并且也较为安全了,
后端maven依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
后端主要代码
原来的那一对钥匙我放在了配置文件里面
@RestController
@RequestMapping("/manage/img")
public class OssController extends BaseController {
@Value("${spring.access-key}")
private String accessKey;
@Value("${spring.secret-key}")
private String secretKey;
@Value("${spring.oss.endpoint}")
private String endPoint;
@Value("${spring.oss.bucket}")
private String bucket;
@Value("${spring.oss.arn}")
private String arn;
@RequestMapping(value = "/token", method = RequestMethod.GET)
public ModelAndView findAll(@RequestParam(name = "userId", required = true) String userId) {
Result result = new Result();
try {
AssumeRoleResponse response = OSSUtils.buildAliyunSTSCredentials(accessKey, secretKey, endPoint, arn, userId);
result.setStatus("0");
result.setData(response);
} catch (ClientException e) {
result.setStatus("1");
result.setData("获取token失败");
e.printStackTrace();
}
return feedback(result);
}
}
public class OSSUtils {
public static AssumeRoleResponse buildAliyunSTSCredentials(String key, String secret, String endpoint, String arn, String userId) throws ClientException {
// STS
DefaultProfile.addEndpoint(endpoint, "cn-beijing", "Sts", "sts.cn-hangzhou.aliyuncs.com");
IClientProfile profile = DefaultProfile.getProfile("cn-beijing", key, secret);
DefaultAcsClient client = new DefaultAcsClient(profile);
final AssumeRoleRequest request = new AssumeRoleRequest();
request.setMethod(MethodType.POST);
request.setProtocol(ProtocolType.HTTPS);
request.setDurationSeconds(60 * 60 * 1L);
request.setRoleArn(arn); // 要扮演的角色ID
request.setRoleSessionName("user" + userId);
// request.setPolicy(policy);
// 生成临时授权凭证
final AssumeRoleResponse response = client.getAcsResponse(request);
return response;
}
}
前端使用Antd的Upload组件完成上传功能,前端也需要引入oss依赖,
在组件初始化的时候就向后端去取值token,并且覆盖调默认的上传方法,使用我们的custmoerUpload,
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {Upload, Icon, Modal, message} from 'antd';
import {reqToken, requUpLoadImg} from "../../api";
import memoryUtils from "../../utils/memoryUtils";
/*
用于图片上传的组件
*/
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
export default class PicturesWall extends Component {
static propTypes = {
imgs: PropTypes.string
};
async componentDidMount() {
await this.init();
}
init = async () => {
try {
let {id} = memoryUtils.user;
const data = await reqToken(id);
this.setState({
token: data.data.credentials,
});
} catch (error) {
message.error('获取token失败');
}
};
constructor(props) {
super(props);
let fileList = [];
//如果传入imgs
const {imgs} = this.props;
let imgList = imgs ? JSON.parse(imgs) : [];
if (imgList.length > 0) {
fileList = imgList.map((img, index) => ({
uid: -index,
name: img,
status: 'done',
url: img
}))
}
//初始化状态
this.state = {
previewVisible: false, //标识是否显示大图预览Modal
previewImage: '', //大图的url地址
previewTitle: '',
token: {},
fileList //所有已上传图片的数组
}
}
/*
获取所有已经上传图片文件名的url数组
*/
getImgs = () => {
return this.state.fileList.map(file => file.url);
};
//隐藏Modal
handleCancel = () => this.setState({previewVisible: false});
handlePreview = (file) => {
if (!file.url && !file.preview) {
file.preview = getBase64(file.originFileObj);
}
//显示指定file对应的大图
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
previewTitle: file.name || file.url.substring(file.url.lastIndexOf('/') + 1),
});
};
handleWillUpload = async () => {
const {token} = this.state;
if (token.expiration < Date.now()) {
await this.init();
}
/*let reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
// 使用ossupload覆盖默认的上传方法
requUpLoadImg(this.token, file).then(data => {
console.log('上传file',file)
console.log('上传之后的结果',data);
console.log('xx',data.name)
})
};*/
return true; // 不调用默认的上传方法
};
imgUpload = (options) => {
const {file} = options;
const {token} = this.state;
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
// 使用ossupload覆盖默认的上传方法
requUpLoadImg(token, file).then(data => {
let imgItem = {
uid: file.uid, // 注意,这个uid一定不能少,否则上传失败
name: file.name,
status: 'done',
url: data.res.requestUrls[0].split('?')[0], // url 是展示在页面上的绝对链接
imgUrl: data.res.requestUrls[0].split('?')[0], // imgUrl 是存到 db 里的相对链接
};
//imgItem = fileList[fileList.length - 1];
const {fileList} = this.state;
fileList[fileList.length - 1] = imgItem;
this.setState({
fileList
});
})
};
};
/*
file:当前操作的图片文件(上传/删除) 这是一个监视的请求,所以一次改变会请求多次
fileList:所有已经上传图片文件对象的数组
*/
handleChange = ({file, fileList}) => {
if (file.status === 'remove') {
fileList.splice(fileList.find(item => item.uid === file.uid), 1);
}
this.setState({fileList});
};
render() {
const {previewVisible, previewImage, fileList, previewTitle} = this.state;
const uploadButton = (
<div>
<Icon type='plus'/>
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<div className="clearfix">
<Upload
accept="image/*" /*只接收图片格式*/
name='image' //请求参数名
listType="picture-card" //卡片样式
fileList={fileList} //所有已上传文件对象的数组
onPreview={this.handlePreview}
beforeUpload={this.handleWillUpload} //上传之前
customRequest={this.imgUpload} //自定义上传方法
onChange={this.handleChange} //改变时
>
{fileList.length >= 4 ? null : uploadButton}
</Upload>
<Modal
visible={previewVisible}
title={previewTitle}
footer={null}
onCancel={this.handleCancel}
>
<img alt="example" style={{width: '100%'}} src={previewImage}/>
</Modal>
</div>
);
}
}
调用Ajax发送请求
/*
上传图片到oss上
*/
export const requUpLoadImg = (token, file) => {
const client = new oss({
accessKeyId: token.accessKeyId,
accessKeySecret: token.accessKeySecret,
stsToken: token.securityToken,
endpoint: 'https://' + OSS_REGION,
region: OSS_REGION,
bucket: OSS_BUCKET,
});
const url = `${moment().format('YYYYMMDD')}/${file.name.split(".")[0]}-${file.uid}.${file.type.split("/")[1]}`;
return new Promise((resolve, reject) => {
client.multipartUpload(url, file).then((data) => {
resolve(data);
}).catch(err => {
resolve(err);
})
});
};