Android oss policy上传

OSS Policy方式上传

    • 一、 流程对比
      • 1.1 普通上传
      • 1.2 服务端签名后直传
    • 二、获取上传的policy签名配置
    • 三、请求OSS上传文件
      • 方式一 post直传
      • 方式二 采用OSS API上传
        • 同步上传
        • 异步上传
      • 两种方式的优缺点
    • 四、调用应用服务器接口同步文件
    • 五、关于上传OSS报错注意事项
      • 上传报错(返回400,403,405,204)
      • 采用OSS API putObject遇到的一些问题
    • 六、附送链接

一、 流程对比

1.1 普通上传

Android oss policy上传_第1张图片
缺点:

  • 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。
  • 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。
  • 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。

1.2 服务端签名后直传

Android oss policy上传_第2张图片

好处:

  • Web端向服务端请求签名,然后直接上传,不会对服务端产生压力,而且安全可靠。
  • 但服务端无法实时了解用户上传了多少文件,上传了什么文件。如果想实时了解用户上传了什么文件,可以采用服务端签名直传并设置上传回调。
  • 但存在着恶意上传的风险,造成存储空间的浪费

二、获取上传的policy签名配置

根据你们后端提供的get或者post API 请求获取policy直传的签名
接口一般定义为get请求 :/api/Upload/getSignedUrl
后端返回信息:

{
"accessid":"LTAI5tBDFVar1hoq****",
"host":"http://post-test.oss-cn-hangzhou.aliyuncs.com",
"policy":"eyJleHBpcmF0aW9uIjoiMjAxNS0xMS0wNVQyMDoyMzoyM1oiLCJjxb25kaXRpb25zIjpbWyJjcb250ZW50LWxlbmd0aC1yYW5nZSIsMCwxMDQ4NTc2MDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXItZGlyXC8i****",
"signature":"VsxOcOudx******z93CLaXPz+4s=",
"expire":1446727949,
"dir":"user-dirs/"
}
字段 描述
accessid 用户请求的AccessKey ID。
host 用户发送上传请求的域名。
policy 用户表单上传的策略(Policy),Policy为经过Base64编码过的字符串。详情请参见Post Policy。
signature 对Policy签名后的字符串。详情请参见Post Signature。
expire 由服务器端指定的Policy过期时间,格式为Unix时间戳(自UTC时间1970年01月01号开始的秒数)。
dir 限制上传的文件前缀。

注意:dir是你要上传到oss bucket下的子目录名称,也就是你要上传的文件在哪个目录下,这个目录可以自己指定,如果这样,就可以客户端get请求的时候携带给后端,这时,API就可以按照个人需求来修改了
get请求可以在url上携带dir名称,也可以用post请求,body的形式传递json给后端,更容易参数扩展

dir目录很重要,在下面的第三点的post请求上传oss的时候,需要传递一个键值对:
“key” : value(dir+文件名)

三、请求OSS上传文件

方式一 post直传

用户使用Post方法向OSS发送文件上传请求
把上一步请求后端返回给你的那些参数,通过post请求构造body,上传至oss服务器
请求的地址就是服务端返回的host字段:http://post-test.oss-cn-hangzhou.aliyuncs.com
参数就是下面的伪代码:

new_multipart_params = {
     // key表示上传到Bucket内的Object的完整路径,例如exampledir/exampleobject.txtObject,完整路径中不能包含Bucket名称。
     // filename表示待上传的本地文件名称。
     'key' : key + '${filename}',
     'policy': policyBase64,
     'OSSAccessKeyId': accessid,
     // 设置服务端返回状态码为200,不设置则默认返回状态码204。
     'success_action_status' : '200',    
     'signature': signature,
 };

注意:这里每一个参数都不能少,success_action_status不传也行,但是OSS默认会返回204状态码,指定200以后,会返回200

这里通过apiPost进行上传测试:
Android oss policy上传_第3张图片
红框内是必传字段,我通过测试得出两个结论:
1.policy签名直传方式中,这个key一定要传递,指定好上面说的那个dir名称,否则会报错,类似policy策略失败问题
2.这种方式不会像我们正常post请求后端那样支持批量上传文件了,因为有key的限制
我尝试了多个file配置多个key,以失败告终

方式二 采用OSS API上传

同步上传

fun uploadOssFile(
        context: Context,
        signature:String,
        endpoint: String,
        bucketName: String,
        objectName: String,
        uploadFilePath: String
    ) {
        credentialProvider = object : OSSCustomSignerCredentialProvider() {
            override fun signContent(content: String): String {
                // 按照OSS规定的签名算法加签字符串,并将得到的加签字符串拼接AccessKeyId后返回。
                // 将加签的字符串传给您的服务器,然后返回签名。如果因某种原因加签失败,服务器描述错误信息后返回null。
                Logger.d("signature=$signature") //这里直接用后端返回的签名
                return signature
            }
        }
        conf = ClientConfiguration()
        conf!!.connectionTimeout = 15 * 1000 // 连接超时,默认15秒
        conf!!.socketTimeout = 15 * 1000 // socket超时,默认15秒
        conf!!.maxConcurrentRequest = 5 // 最大并发请求数,默认5个
        conf!!.maxErrorRetry = 2 // 失败后最大重试次数,默认2次
        oss = OSSClient(context, endpoint, credentialProvider, conf)

        // 构造上传请求
        val putObjectRequest = PutObjectRequest(bucketName, objectName, uploadFilePath)

        //同步
        putObjectSync(putObjectRequest, bucketName, objectName)

异步上传

 // 异步上传时可以设置进度回调
        putObjectRequest.progressCallback = OSSProgressCallback { request, currentSize, totalSize ->
            d(
                "PutObject, currentSize: $currentSize totalSize: $totalSize"
            )
        }

        val ossAsyncTask: OSSAsyncTask<*> = oss!!.asyncPutObject(putObjectRequest,
            object : OSSCompletedCallback<PutObjectRequest, PutObjectResult> {
                override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult?) {
                    Log.e("url=: "+oss!!.presignPublicObjectURL(bucketName,objectName) )
                    Log.d("PutObject,UploadSuccess" + "")
                    Log.d("ETag:" + result?.eTag)
                    Log.d("RequestId:" + result?.requestId)
                    aliyunUploadCallBack.UploadSuccess(
                        oss!!.presignPublicObjectURL(
                            bucketName,
                            objectName
                        )
                    )
                }

                override fun onFailure(
                    request: PutObjectRequest?,
                    clientException: ClientException?,
                    serviceException: ServiceException?
                ) {
                    //注:ClientException指客户端尝试向OSS发送请求以及数据传输时遇到的异常。例如,当发送请求时网络连接不可用,则会抛出ClientException。当上传文件时发生IO异常,也会抛出ClientException。
                    // 请求异常。
                    Logger.d("onFailure----")
                    if (clientException != null) {
                        // 本地异常,如网络异常等。
                        clientException.printStackTrace()
                        e("网络异常,$clientException")
                        aliyunUploadCallBack.UploadFail("网络异常")
                    }
                    //注:ServiceException指服务器端错误,来源于对服务器端错误信息的解析。OSSException包含OSS返回的错误码和错误信息
                    if (serviceException != null) {
                        // 服务异常。
                        Log.e("服务异常,$serviceException") //OSS返回的错误码。
                        Log.e("ErrorCode:" + serviceException.errorCode)
                        Log.e("message:" + serviceException.message) //OSS返回的详细错误信息。
                        Log.e("RequestId:" + serviceException.requestId) //用于唯一标识该请求的UUID。您可以凭借此RequestId请求协助,排查并解决您遇到的问题。
                        Log.e("HostId:" + serviceException.hostId) //用于标识访问的OSS集群,与请求时使用的Host一致。'
                        Log.e("RawMessage:" + serviceException.rawMessage) //HTTP响应的原始Body文本
                        aliyunUploadCallBack.UploadFail("服务异常")
                    }
                }
            })
   interface AliyunUploadCallBack {
        fun UploadSuccess(url: String?)
        fun UploadFail(error: String?)
    }

两种方式的优缺点

post自行请求上传方式,这种方式没有通过阿里云API调用,所以要提前在阿里云管理平台bucket下面创建文件夹
我们当然希望后续根据不同的业务去创建不同的目录了,所以后来又采用OSS提供API方式,put Object到OSS,这种方式OSS会先判断有没有这个目录,有就不管了直接上传文件,没有就给我们创建一个指定的目录

四、调用应用服务器接口同步文件

我们上传到OSS的文件,没设置callback的话,后端并不知道,有两种方法可以同步后端;
方案一:在请求OSS的时候,设置callback,估计oss上传完成会通知后端?
方案二:也是我现在项目做的方式,我们拿到OSS返回200以后,跟后端重新定义接口同步文件信息到后端

五、关于上传OSS报错注意事项

上传报错(返回400,403,405,204)

报错信息 解决方案
204 在formData: {}中添加 success_action_status:“200”
400 添加上传的 Key 值,路径也要写正确,/images/test.png 为错误写法(最前面多了个斜杠),正确写法success images/test.png ;另外,阿里云OSS一次只允许上传一张照片。
403 检测 OSSAccessKeyId,policy,signature 是否填写正确; expiration已过期
405 URL中添加服务器地址 http://post-test.oss-cn-hangzhou.aliyuncs.com 而不能写成 http://post-test.oss-cn-hangzhou.aliyuncs.com/images/test.png这种形式

采用OSS API putObject遇到的一些问题

1.om.alibaba.sdk.android.oss.ClientException: This task is cancelled!

问题解决:任务可能提前取消了,删除任务取消的代码即可解决

类似这种代码注释掉:

    // ossAsyncTask.cancel(); // 可以取消任务
    // ossAsyncTask.waitUntilFinished(); // 等待任务完成

2.oss安卓自签名上传文件 oss!!.asyncPutObject 无结果,不调用成功,也不调用失败回调?

问题解决:查看代码用的是不是kotlin,如果是,会有跟java 不匹配的问题,结果参数加上?来匹配java返回的空

3.PutObjectRequest上传文件遇到的错误,报错信息:

服务异常,[StatusCode]: 400, [Code]: InvalidBucketName, [Message]: The specified bucket is not valid., [Requestid]: 64B4AF843218A13939FAA184, [HostId]: xxx.xxx.oss-cn-xxx.aliyuncs.com, [RawMessage]: <?xml version="1.0" encoding="UTF-8"?>
                                                                                                    <Error>                                                                                                 <Code>InvalidBucketName</Code>                                                                                     <Message>The specified bucket is not valid.</Message>                                                                                                     <RequestId>64B4AF843218A13939FAA184</RequestId>                                                                                               <HostId>xxx.xxx.oss-cn-xxx.aliyuncs.com</HostId>                                                                                                 <BucketName>xxx.xxx</BucketName>                                                                                                <EC>0015-00000001</EC>                                                                                                  </Error>

问题解决:
排查了一下自己的putObject需要传的参数bucketName没错啊,为什么报bucketName无效呢?
最终的答案是putObject的传入参数endpoint传错了,误认为后端给返回的host参数是endpoint,
要么在获取OSS配置接口让后端给你返回这个参数,要嚒,查看OSS平台配置看你的endpoint是什么,客户端写死就行

PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, uploadFilePath);

六、附送链接

阿里云服务端签名直传文档
https://help.aliyun.com/document_detail/31926.html?spm=a2c4g.31920.0.0

你可能感兴趣的:(android,OSS,Policy方式上传)