Posts 前端使用Ant design Upload组件上传文件到阿里云OSS的坑
Post
Cancel

前端使用Ant design Upload组件上传文件到阿里云OSS的坑

  业务需求需要上传文件,那就想办法整呗,一顿操作过后选择了阿里云OSS.

1.发现的问题

  跟着阿里云文档一步步操作过来的,但是还是出现了一个403错误

1
2
3
4
<Error>
  <Code>SignatureDoesNotMatch</Code>
  <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
</Error>

  当时就想,这个问题很明显嘛,签名生成错误呗,应该是哪里写错了吧,回去对比下文档和代码吧.en?怎么回事,明明是完全按照文档的来啊,文档的附录写着签名方法是Signature = base64(hmac-sha1(base64(policy), AccessKeySecret))这样生成的啊.我的确也是这样做的啊const Signature = CryptoJS.HmacSHA1(policyBase64, secret) 为啥我还是报错.Google找了半个小时资料愣是没找到答案.想骂人了

  后来我发现在附录的下面有一个更多参考JavaScript客户端签名直传 在这个页面里面我找到了一份参考代码—浏览器客户端代码打开一看,好家伙这里面的签名的生成方式竟然是这样的

1
2
3
// 完整代码可以点击浏览器客户端代码下载
var bytes = Crypto.HMAC(Crypto.SHA1, message, accesskey, { asBytes: true }); 
var signature = Crypto.util.bytesToBase64(bytes);

果然我一修改后,就上传成功了.完整上传组件代码请查看附录

2.附录

自己封装了上传文件到阿里云OSS的 Ant design Upload组件的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import React, { useState, useEffect } from 'react';
import { Upload, Button, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import _get from 'lodash/get';
import CryptoJS from 'crypto-js';
import { encrypt } from '@/utils/crypto';
import OSS_CONFIG from '@/config/OSSConfig';
import { getUploadAccess } from '@/services/uploadFile';

const AliOSSUpload = (props) => {
  const { 
    ButtonName = "点击上传文件", 
    maxFileListLength = 4, 
    onPreview, 
    listType, 
    uploadButton, 
    accept, 
    uploadAccessInfo: propsUploadAccessInfo,
  } = props;
  const [OSSData, setOSSData] =  useState({});
  const [fileList, setFileList] = useState([]);

  const onChange = (fileInfo) => {
    if (fileInfo?.file?.status === "error") {
      message.error('上传失败,请重新上传');
    }
    const { onChange: formCallback } = props;
    if (typeof formCallback === 'function') {
      formCallback(fileInfo);
    }
    setFileList(fileInfo.fileList);
  };

  const onRemove = (file) => {
    const files = fileList.filter((v) => v.url !== file.url);
  };

  const handleOSSData = (uploadAccessInfo) => {
    try {
      const expire = _get(uploadAccessInfo, 'sts_token.expiration', new Date());
      const secret = _get(uploadAccessInfo, 'sts_token.access_key_secret', '');
      const uuid = _get(uploadAccessInfo, 'script_uuid', '');
      const accessId = _get(uploadAccessInfo, 'sts_token.access_key_id', '');
      const token = _get(uploadAccessInfo, 'sts_token.security_token', '');

      const policyText = {
        expiration: expire,
        conditions: [
          { bucket: OSS_CONFIG.bucket },
        ],
      };

      const policyBase64 = encrypt(JSON.stringify(policyText));
      // must add { asBytes: true }, otherwise oss will return 403: SignatureDoesNotMatch
      const bytes = CryptoJS.HmacSHA1(policyBase64, secret, { asBytes: true });
      const signature = bytes.toString(CryptoJS.enc.Base64);

      const data = {
        dir: `${OSS_CONFIG.dir}/${uuid}/`,
        expire,
        host: OSS_CONFIG.host,
        accessId,
        policy: policyBase64,
        signature,
        token,
        successActionStatus: 200,
      };
      return data;
    } catch (error) {
      console.log(error);
    }
  };

  const init = (uploadAccessInfo) => {
    const initOSSData = handleOSSData(uploadAccessInfo);
    setOSSData(initOSSData);
  };

  const fetchUploadAccess = () => {
    getUploadAccess().then((response) => {
      if (response.success) {
        init(response.data);
      }
    }).catch((error) => {
      console.error(error);
      message.error('获取上传权限失败');
    });
  }
  
  const beforeUpload = async (file) => {
    if (OSSData.expire < Date.now()) {
      fetchUploadAccess();
    }

    const suffix = file.name.slice(file.name.lastIndexOf('.'));
    const filename = Date.now() + suffix;
    file.key = `${OSSData.dir}${filename}`;
    file.url = `${OSSData.host}/${OSSData.dir}${filename}`;

    return file;
  };

  const getExtraData = (file) => {
    return {
      key: file.key,
      OSSAccessKeyId: OSSData.accessId,
      policy: OSSData.policy,
      Signature: OSSData.signature,
      'x-oss-security-token': OSSData.token,
      success_action_status: OSSData.successActionStatus,
    };
  };

  useEffect(() => {
    if (propsUploadAccessInfo === undefined) {
      fetchUploadAccess();
    } else {
      init(propsUploadAccessInfo);
    }
  }, [propsUploadAccessInfo]);

  const uploadProps = {
    name: "file",
    fileList,
    action: OSSData.host,
    listType,
    onPreview,
    accept,
    onChange,
    onRemove,
    data: getExtraData,
    beforeUpload,
  };

  return (
    <>
      <Upload {...uploadProps} >
        {  
          listType ? (fileList.length >= maxFileListLength ? null : uploadButton) : (<Button icon={<UploadOutlined />}>{ButtonName}</Button>)
        }
      </Upload>
    </>
  );
};

export default AliOSSUpload;

This post is licensed under CC BY 4.0 by the author.

我的2020年终总结

使用Ant design Form组件获取Upload组件的fileList信息