前后端项目如何实现完美的OSS文件上传

在网上查了许久前后端分类的项目中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);
        })
    });
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值