【Android】从客户端向阿里云OSS上传文件的实现

4,114 阅读5分钟

需求

项目是一个视频平台,像B站抖音那样,用户创作作品并上传到服务器。由于主服务器带宽较小,架构设计为客户端(Android/iOS)向阿里云OSS上传文件,之后把文件url通过接口传给后台的方式实现上传操作。

阿里云控制台操作

此处假定你已经进行了前置操作,即:创建阿里云账户->开通OSS服务->创建Bucket,没有做这些操作的话可以去度娘谷歌找找,此处不再赘述。

创建阿里云RAM账户

由于阿里云不允许客户端直接使用超管账户登录OSS,我们需要给客户端一个临时的身份用于上传文件。
STS临时授权访问OSS ←这个看看就行了,不用操作,下面有更快的

临时访问授权

快速搭建移动应用直传服务 ←根据这篇文档的指引开通STS服务
滑到下面操作步骤,只做1和2就可以了。 步骤1 步骤2 第2步是由后台来做的,最好的让你家后台写一个接口,你每次请求这个接口,他就使用SDK去找阿里云申请一组数据。这个config.json是关键,前三个参数填写你步骤1创建的时候得到的值。第四个参数不管他。第五个参数指向一个文件。先new在那里,现在我们去控制台。

RAM控制台授予权限

阿里云RAM访问控制←登录这个控制台

①在左侧找到“RAM角色管理”,这里有一个在刚才步骤1的时候系统自动创建的角色,点进去,然后点击“添加权限”

②右侧会弹出一个操作面板,在这个输入框里输入“OSS”,然后点击搜到的结果,会添加到已选择里。最后点下面的确定

③然后在左侧找到“用户”,也有一个自动创建的角色,点进去,然后点击“权限管理”->“添加权限”,还是和刚才一样搜索“OSS”,全部添加

④然后我们在添加好的权限列表里面找到这一条,点蓝字进入

⑤复制策略内容的文本

复制出来内容应该长这样:

{
    "Statement": [
        {
            "Action": "oss:*",
            "Effect": "Allow",
            "Resource": "*"
        }
    ],
    "Version": "1"
}

还记得刚才的config.json吗?第五个参数指向了一个文件,这个json就是那个文件的内容。你可以存成Policy.txt然后把config.json改成下面这样:

{
	"AccessKeyID" : "你的AccessKeyID",
	"AccessKeySecret" : "你的AccessKeySecret",
	"RoleArn" : "你的RoleArn",
	"TokenExpireTime" : "900",
	"PolicyFile": "Policy.txt"
}

OSS控制台授权

①登录 阿里云OSS控制台
②找到你创建好的bucket,点进去,然后点左侧的文件管理,然后再点授权

③右侧会弹出操作面板,点新增授权

④授权用户选子账号,然后下拉选单选择刚才步骤1的时候系统帮你创建的那个RAM用户,因为这里我们要上传文件,所以权限操作选择读/写,最后记得点确定。

Android代码操作

终于搞定了这该死的控制台,现在我们去Android Studio里面。

SDK导包

跟我读:gradle大法好!

	implementation 'com.aliyun.dpa:oss-android-sdk:+'

获取临时鉴权数据

鉴权模式有STSRAM两种,这里以RAM为例
首先请求上面步骤2的时候后台写好的接口。会得到像这样的数据:

{
    "StatusCode":200,
    "AccessKeyId":"STS.NTao********",
    "AccessKeySecret":"**************",
    "Expiration":"2020-12-28T07:58:34Z",
    "SecurityToken":"这个玩意很长很长通常是以等号结尾的="
}

像上面这样的StatusCode == 200就是请求成功了,我们要用的数据有AccessKeyIdAccessKeySecretSecurityToken这三个。
如果请求失败的话,会返回像这样的数据:

{
	"StatusCode":500,
	"ErrorCode":"错误码",
	"ErrorMessage":"错误信息"
}

报错的时候会返回StatusCode == 500,个人建议只要不是200都当错误处理,然后把错误码和错误信息报出来 甩锅给后台

上传文件到OSS

kotlin代码段,此处写法参考官方文档,还在用Java的同学可以参考官方的写法,记住每个地方填什么参数就好。
阿里云官方Android SDK文档-初始化
阿里云官方Android SDK文档-简单上传

        val credentialProvider = OSSStsTokenCredentialProvider(AccessKeyId, AccessKeySecret, SecurityToken)//这仨是刚才接口获取到的
        
//// 如果使用stsAuth鉴权模式,用下面这个构造方法,如果使用RAM鉴权模式,使用上面的构造方法

//		stsServer = "STS应用服务器地址,例如http://abc.com"
//		val credentialProvider = OSSAuthCredentialsProvider(stsServer);

//// 配置类如果不设置,会有默认配置。
//        ClientConfiguration conf = new ClientConfiguration();
//        conf.setConnectionTimeout(15 * 1000); // 连接超时,默认15秒。
//        conf.setSocketTimeout(15 * 1000); // socket超时,默认15秒。
//        conf.setMaxConcurrentRequest(5); // 最大并发请求数,默认5个。
//        conf.setMaxErrorRetry(2); // 失败后最大重试次数,默认2次。

		//endpoint是后台配置好的OSS地址,找你家后台要
        val oss = OSSClient(applicationContext, endpoint, credentialProvider)

        //构造上传请求
        //bucketName 对应bucket的名字
        //objectKey 文件保存在OSS上的路径及其文件名,比如我想存在bucket的image目录里面,文件名叫1.jpg。这里就填"image/1.jpg"
        //注意:如果objectKey包含子目录,要预先在OSS控制台手动新建这个目录,或者让后台用管理员权限去新建目录,客户端的临时用户是没有权限新建目录的。
        //localPath 本地的文件路径,一般的接口传啥这里就传啥
        put = PutObjectRequest(bucketName, objectKey, localPath)

        // 异步上传时可以设置进度回调。可以在这里操作进度条等,注意此处是IO线程,记得加runOnUiThread
        put.progressCallback = OSSProgressCallback { request, currentSize, totalSize ->
                Log.d("PutObject", "currentSize: $currentSize totalSize: $totalSize")
        }

        val task: OSSAsyncTask<*> = oss.asyncPutObject(put, object : OSSCompletedCallback<PutObjectRequest?, PutObjectResult> {
            override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult) {
                Log.d("PutObject", "UploadSuccess")
                Log.d("ETag", result.eTag)
                Log.d("RequestId", result.requestId)
                //走到这里就是上传完成了
                //上传成功后的url不会在这里返回。可以去控制台查看一下,前面的域名是OSS配置的域名,后面直接拼接objectKey即可,是一个普通的地址,可以直接访问和下载
            }

            override fun onFailure(request: PutObjectRequest?, clientExcepion: ClientException?, serviceException: ServiceException?) {
                //走到这里就是报错了
                clientExcepion?.printStackTrace()
                if (serviceException != null) {
                    // 服务异常。
                    Log.e("ErrorCode", serviceException.errorCode)
                    Log.e("RequestId", serviceException.requestId)
                    Log.e("HostId", serviceException.hostId)
                    Log.e("RawMessage", serviceException.rawMessage)
                }
            }
        })
// task.cancel(); // 可以取消任务。
// task.waitUntilFinished(); // 等待任务完成。