Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用七牛云存储图片 之 上传图片 #7

Open
JZLeung opened this issue Aug 23, 2017 · 0 comments
Open

使用七牛云存储图片 之 上传图片 #7

JZLeung opened this issue Aug 23, 2017 · 0 comments

Comments

@JZLeung
Copy link
Owner

JZLeung commented Aug 23, 2017

七牛云是国内算是挺不错的图片存储服务器,免费用户能拥有10G空间,每个月10G的下载流量,对于个人用户用于做个小博客,小网站来说,已经够了。

但是,七牛的官方开发文档真心会看得一头雾水。

于是,先记录下来这次传图的过程。

在上传图片之前,需要准备两样东西:

  1. AccessKey
  2. SecretKey

其中, AccessKey 和 SecretKey 能在 个人中心 > 秘钥管理中获得,如图。

  1. 创建空间 并 创建配置文件

    选择 资源主页 ,再选择 立即添加,或者选择 对象存储 ,然后选择 添加

    然后填上空间的名字,选择区域即可。

    其中,空间的名字是我们使用代码上传至七牛服务器的第三个参数 Bucket

    可以将这3个常量存储在配置文件config.qiniu.php

    define('QINIU_SECRET_KEY', 'YOUR_SK');
    define('QINIU_ACCESS_KEY', 'YOUR_AK');
    define('QINIU_DOMAIN', 'YOUR_QINIU_DOMAIN');
    define('QINIU_BUCKET', 'YOUR_BUCKET_NAME');
    
  2. 获取上传凭证

    上传凭证的作用:

    客户端上传前需要先从服务端获取上传凭证,并在上传资源时将上传凭证作为请求内容的一部分。不带凭证或带非法凭证的请求将返回 HTTP 错误码 401,代表认证失败。

    生成规则:

    1. 构造上传策略:

      上传策略是资源上传时附带的一组配置设定。通过这组配置信息,七牛云存储可以了解用户上传的需求:它将上传什么资源,上传到哪个空间,上传结果是回调通知还是使用重定向跳转,是否需要设置反馈信息的内容,以及授权上传的截止时间等等。

      {
      
          "scope":               "<Bucket                   string>",
          "deadline":             <UnixTimestamp            uint32>,
          "insertOnly":           <AllowFileUpdating        int>,
          "endUser":             "<EndUserId                string>",
          "returnUrl":           "<RedirectURL              string>",
          "returnBody":          "<ResponseBodyForAppClient string>",
          "callbackUrl":         "<RequestUrlForAppServer   string>",
          "callbackHost":        "<RequestHostForAppServer  string>",
          "callbackBody":        "<RequestBodyForAppServer  string>",
          "callbackBodyType":    "<RequestBodyTypeForAppServer  string>",
          "callbackFetchKey":     <RequestKeyForApp         int>
          "persistentOps":       "<persistentOpsCmds        string>",
          "persistentNotifyUrl": "<persistentNotifyUrl      string>",
          "persistentPipeline":  "<persistentPipeline       string>",
          "saveKey":             "<SaveKey                  string>",
          "fsizeMin":             <FileSizeMin            int64>,
          "fsizeLimit":           <FileSizeLimit            int64>,
          "detectMime":           <AutoDetectMimeType       int>,
          "mimeLimit":           "<MimeLimit                string>"
          "deleteAfterDays":     "<deleteAfterDays          int>"
      }
      
      字段名称 必填 说明
      scope 指定上传的目标资源空间 Bucket 和资源键 Key(key的长度最大为750字节)。有两种格式: ● ``,表示允许用户上传文件到指定的 bucket。在这种格式下文件只能新增,若已存在同名资源上传则会失败。 ● ``:``,表示只允许用户上传指定 key 的文件。在这种格式下文件默认允许修改,若已存在同名资源则会被覆盖。如果只希望上传指定 key 的文件,并且不允许修改,那么可以将下面的 `insertOnly` 属性值设为 1
      deadline 上传凭证有效截止时间。`Unix时间戳`,单位为秒。该截止时间为上传完成后,在七牛空间生成文件的校验时间,而非上传的开始时间,一般建议设置为上传开始时间 + 3600s,用户可根据具体的业务场景对凭证截止时间进行调整。
      insertOnly 限定为新增语意。如果设置为非 0 值,则无论 `scope` 设置为什么形式,仅能以新增模式上传文件。
      endUser 唯一属主标识。特殊场景下非常有用,例如根据 App-Client 标识给图片或视频打水印。
      returnUrl Web 端文件上传成功后,浏览器执行 303 跳转的 URL。通常用于 HTML Form 上传。文件上传成功后会跳转到 `?upload_ret=`,``包含 `returnBody` 内容。如不设置 returnUrl,则直接将 returnBody 的内容返回给客户端。
      returnBody 上传成功后,自定义七牛云最终返回給上传端(在指定 returnUrl 时是携带在跳转路径参数中)的数据。支持魔法变量和自定义变量。returnBody 要求是合法的 JSON 文本。例如 `{"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}`。
      callbackUrl 上传成功后,七牛云向 App-Server 发送 POST 请求的 URL。必须是公网上可以正常进行 POST 请求并能响应 HTTP/1.1 200 OK 的有效 URL。另外,为了给客户端有一致的体验,我们要求 callbackUrl 返回包 Content-Type 为 "application/json",即返回的内容必须是合法的 JSON 文本。出于高可用的考虑,本字段允许设置多个 callbackUrl(用英文符号 ; 分隔),在前一个 callbackUrl 请求失败的时候会依次重试下一个 callbackUrl。一个典型例子是 `http:///callback;http:///callback`,并同时指定下面的 callbackHost 字段。在 callbackUrl 中使用 ip 的好处是减少了对 dns 解析的依赖,可改善回调的性能和稳定性。
      callbackHost 上传成功后,七牛云向"App-Server"发送回调通知时的 Host 值。 与callbackUrl配合使用,仅当设置了 callbackUrl 时才有效。"callbackHost":"a.example.com",host域名不加http://
      callbackBody 上传成功后,七牛云向"App-Server"发送Content-Type: application/x-www-form-urlencoded 的POST请求。 该字段"App-Server"可以通过直接读取请求的query来获得,支持魔法变量和自定义变量。callbackBody 要求是合法的 url query string。如:`key=$(key)&hash=$(etag)&w=$(imageInfo.width)&h=$(imageInfo.height)`。
      callbackBodyType 上传成功后,七牛云向"App-Server"发送回调通知`callbackBody`的`Content-Type`。 默认为`application/x-www-form-urlencoded`,也可设置为`application/json`。
      callbackFetchKey 是否启用fetchKey上传模式。 0为关闭,1为启用。具体见callbackFetchKey详解。
      persistentOps 资源上传成功后触发执行的预转持久化处理指令列表。 每个指令是一个API规格字符串,多个指令用;分隔。 请参阅persistenOps详解与示例。同时添加persistentPipeline字段,使用专用队列处理,请参阅persistentPipeline。
      persistentNotifyUrl 接收持久化处理结果通知的URL。 必须是公网上可以正常进行POST请求并能响应"HTTP/1.1 200 OK"的有效URL。 该URL获取的内容和持久化处理状态查询(prefop)的处理结果一致。 发送body格式是`Content-Type`为`application/json`的POST请求,需要按照读取流的形式读取请求的body才能获取。
      persistentPipeline 转码队列名。 资源上传成功后,触发转码时指定独立的队列进行转码。为空则表示使用公用队列,处理速度比较慢。建议使用专用队列。
      saveKey 自定义资源名。 支持魔法变量及自定义变量。这个字段仅当用户上传的时候没有主动指定key的时候起作用。
      fsizeMin 限定上传文件大小最小值,单位:字节(Byte)。设置为k,即k及k以上的文件可以上传。
      fsizeLimit 限定上传文件大小最大值,单位:字节(Byte)。 超过限制上传文件大小的最大值会被判为上传失败,返回413状态码。
      detectMime 开启MimeType侦测功能。 设为非0值,则忽略上传端传递的文件MimeType信息,使用七牛服务器侦测内容后的判断结果。 默认设为0值,如上传端指定了MimeType则直接使用该值,否则按如下顺序侦测MimeType值: 1. 检查文件扩展名; 2. 检查Key扩展名; 3. 侦测内容。 如不能侦测出正确的值,会默认使用 `application/octet-stream` 。
      mimeLimit 限定用户上传的文件类型。 指定本字段值,七牛服务器会侦测文件内容以判断MimeType,再用判断值跟指定值进行匹配,匹配成功则允许上传,匹配失败则返回403状态码。 示例: ● `image/*`表示只允许上传图片类型 ● `image/jpeg;image/png`表示只允许上传jpg和png类型的图片 ● `!application/json;text/plain`表示禁止上传json文本和纯文本。注意最前面的感叹号!
      deleteAfterDays 文件在多少天后被删除,七牛将文件上传时间与指定的`deleteAfterDays`天数相加,得到的时间入到后一天的午夜(CST,中国标准时间),从而得到文件删除开始时间。例如文件在2015年1月1日上午10:00 CST上传,指定`deleteAfterDays`为3天,那么会在2015年1月5日00:00 CST之后当天内删除文件。

      如现有如下上传策略:

      $putPolicy = array(
          'scope' => QINIU_BUCKET.':'.$filename ,
      	'deadline' => time()+3600 ,
      	'returnBody' => '{
      		"name": $(fname),
              "file_url": $(x:file_url)
      	}'
      );

      该上传策略定义了:
      1. 指定了图片上传至QINIU_BUCKET存储空间中,同时,该token只允许文件名为 $filename 的文件上传。
      2. token有效时间为1个小时。
      3. 设置返回信息,返回上传的文件的文件名,和自定义参数中的 file_url

    2. 将上传策略序列化成为JSON格式:
      用户可以使用各种语言的 JSON 库,也可以手工拼接字符串。序列化后,应得到如下形式的字符串(字符串值以外部分不含空格或换行):

      $putPolicy = json_encode($putPolicy);

      $putPolicy = '{
          "scope": "my-bucket:sunflower.jpg",
          "deadline":1451491200,
          "returnBody":
              "{
              \"name\":$(fname),
              \"size\":$(fsize),
              \"w\":$(imageInfo.width),
              \"h\":$(imageInfo.height),
              \"hash\":$(etag)
          }"
      }'
    3. 对 JSON 编码的上传策略进行URL安全的Base64编码,得到待签名字符串:
      官方给的demo代码为:

      encodedPutPolicy = urlsafe_base64_encode(putPolicy)

      运行之后,发现 urlsafe_base64_encode 这个函数是自定义的,估计相当于将 +,/号转换为 -,_

      function _urlsafe_base64_encode($str){
      	$find = array('+', '/');
      	$replace = array('-', '_');
      	return str_replace($find, $replace, base64_encode($str));
      }
    4. 使用SecretKey对上一步生成的待签名字符串计算HMAC-SHA1签名:
      官方给的demo代码为:

      sign = hmac_sha1(encodedPutPolicy, QINIU_SECRET_KEY)

      然而, hmac_sha1 这个函数也不是php自带的。经过搜索发现,其实PHP5.1.2之后的版本内置了直接产生的函数,只是名字不一样罢了: hash_hmac,因此需要将这里修改为:

      $sign = hash_hmac('sha1' ,$encodedPutPolicy, QINIU_SECRET_KEY, true);

      第一个参数为哈希算法名(支持md5sha256haval160,4等,具体可到 hash_algos()中查询);第二个参数为需要进行哈希的信息;第三个参数为秘钥;第四个参数为输出格式(true为输出二进制,false为输出16进制)

    5. 对签名进行URL安全的Base64编码:
      官方代码:

      encodedSign = urlsafe_base64_encode(sign)
      

      这里的 urlsafe_base64_encode 依然为上述的自定义函数。

    6. 将AccessKey、encodedSign 和 encodedPutPolicy 用英文符号 : 连接起来:

      $uploadToken = QINIU_ACCESS_KEY . ':' . $encodedSign . ':' . $encodedPutPolicy
      
    7. 返回token至客户端

      echo json_encode(array('token' => $uploadToken, 'key' => $filename, 'fileurl' => QINIU_DOMAIN));
      

      这里返回了文件名和文件url,主要是因为,保证文件在七牛中的唯一性,和可以随时更改七牛的空间访问地址。

  3. 上传图片
    使用js直接上传图片至七牛服务器他的过程为:

    向服务器请求 uploadToken =>

    获取 uploadToken 后上传图片 =>

    上传成功,显示图片。

    这里没有使用 zepto jquery 这种库,所以浏览器的兼容性为兼容 FormData 的现代浏览器

    使用 xhr 和 FormData 进行异步传输数据。

    function ajax(options){
        options.start && options.start.call('start');
        //执行上传操作
        var xhr = new XMLHttpRequest();
        xhr.open("post", options.url, true);
        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4) {
                returnDate = JSON.parse(xhr.responseText);
                options.success && options.success.call('success', returnDate);
            };
        };
        //表单数据
        var fd = new FormData();
        for (k in options.data) {
            fd.append(k, options.data[k]);
        }
        fd.append('file', options.file);
        //执行发送
        result = xhr.send(fd);
    }

    新建一个表单 form

    <form action="" method="post" name="upload_form" hidden>
        <input type="file" name="file">
        <input type="hidden" name="key" value="">
        <input type="hidden" name="x:file_url" value="">
        <input type="hidden" name="token" value="">
    </form>

    给上传文件的按钮绑定一个 change 时间:

    file.addEventListener('change', function(e){
        var selected_file = e.target.files[0];
        // 先请求服务器获取token
        ajax({
            url: '/upload.php',
            data: {
                filename: selected_file.name
            },
            start: function(){
                console.log('start to get uploadToken');
            },
            success: function(data){
                // 给表单中的参数赋值
                form.key.value = data.key;
                form.file_url.value = data.fileurl+data.key;
                form.token.value = data.token;
    
                // 执行上传图片操作
                uploadImage(selected_file)
            }
        })
    })

    开始上传文件至七牛

    function uploadImage(file){
        ajax({
            // 如果
            url: 'http://upload.qiniu.com/',
            // url: 'https://up.qbox.me',
            data: {
                file: file,
                key: form.key.value,
                'x:file_url': form.file_url.value,
                token: form.token.value,
            },
            start: function(){
                console.log('start to upload Image to Qiniu');
            },
            success: function(data){
                // 给表单中的参数赋值
                console.log(data);
                image.src = data.file_url
            }
        })
    }

    最终实现:
    最终实现

    GitHub Demo 欢迎吐槽

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant