Android 阿里云OSS 上传文件,使公网可以直接访问

1. 背景

有个Android演示项目需要上传图片/视频,并要求上传的图片/视频能在公网访问。
公司的服务器只能提供内网访问,如果公网进行访问,需要申请很繁琐的流程,且需要加上验签等各种条件,相当麻烦。
所以决定让Android App直接对接阿里云OSS,直接将文件上传到阿里云OSS上。

目标 :
实现 Android App上传图片/视频到阿里云OSS上,并使该图片/视频能直接在公网访问。

因为是Demo项目,我们为了快速实现功能,没有使用STS鉴权模式,而是直接将AccessKeyIdAccessKeySecret直接保存在终端用来加签请求,官方建议使用STS鉴权模式或自签名模式来提高安全性。

2. 接入阿里OSS

2.1 OSS信息

首先,我们需要知道OSS相关的信息

val accessKey = "你的AccessKey"
val secretKey = "你的SecretKey"
val endpoint = "oss-cn-hangzhou.aliyuncs.com"
val bucketName = "my-dev-oss"

其中,endpoint 可以从访问域名和数据中心 对照表中获取

2.2 添加权限

<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>                   

2.3 添加阿里云OSS依赖

build.gradle中添加

implementation 'com.liulishuo.filedownloader:library:1.7.7'

2.4 构造OSSCredentialProvider

val accessKey = "你的AccessKey" //TODO 这里输入你的OSS的accessKey
val secretKey = "你的SecretKey" //TODO 这里输入你的OSS的secretKey
val provider: OSSCustomSignerCredentialProvider =
    object : OSSCustomSignerCredentialProvider() {
        override fun signContent(content: String): String {
            // 此处本应该是客户端将contentString发送到自己的业务服务器,然后由业务服务器返回签名后的content。关于在业务服务器实现签名算法
            // 详情请查看http://help.aliyun.com/document_detail/oss/api-reference/access-control/signature-header.html。客户端
            // 的签名算法实现请参考OSSUtils.sign(accessKey,screctKey,content)
            return OSSUtils.sign(
                accessKey,
                secretKey,
                content
            )
        }
    }

2.5 构造OSSClient

val endpoint = "oss-cn-hangzhou.aliyuncs.com"
val oss: OSS = OSSClient(applicationContext, endpoint, provider)

2.6 构造上传请求

val bucketName = "my-dev-oss"
val uploadFile = File(getExternalFilesDir("fodder"), "photo2.jpeg")
val put = PutObjectRequest(bucketName, /*objectKey*/uploadFile.name,/*uploadFilePath*/uploadFile.path)

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

2.7 上传文件

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)
        }

        override fun onFailure(
            request: PutObjectRequest?,
            clientExcepion: ClientException?,
            serviceException: ServiceException?
        ) {
            // 请求异常。
            clientExcepion?.printStackTrace()
            if (serviceException != null) {
                // 服务异常。
                Log.e("ErrorCode", serviceException.getErrorCode())
                Log.e("RequestId", serviceException.getRequestId())
                Log.e("HostId", serviceException.getHostId())
                Log.e("RawMessage", serviceException.getRawMessage())
            }
        }
    })
	// task.cancel(); // 可以取消任务。
	// task.waitUntilFinished(); // 等待上传完成。

3. 访问上传的图片/视频

3.1 对外访问的URL

上传文件成功后,要怎么访问呢,一番搜索,似乎找到了答案。
在这里插入图片描述
文中说,如果文件的读写权限ACL为公共读,那么文件URL的格式为https://BucketName.Endpoint/ObjectName
已知,我们OSSBucketNamemy-dev-ossEndpointoss-cn-hangzhou.aliyuncs.com,由于Bucket下没有文件夹,所以ObjectName直接为文件名,所以URL为http://my-dev-oss.oss-cn-hangzhou.aliyuncs.com/photo2.jpeg

具体详见 【OSS】上传Object后如何获取访问URL?

我们在浏览器中用这个URL进行访问
在这里插入图片描述
结果提示我们You have no right to access this object because of bucket acl.
似乎是缺少权限 ?

3.2 设置上传文件的权限

查阅了文档,才发现,有一个参数是用来设置上传文件的访问权限的
在这里插入图片描述
这就需要我们修改2.6 构造上传请求中的代码了

val bucketName = "devops-dev-oss"
val put = PutObjectRequest(bucketName, /*objectKey*/uploadFile.name,/*uploadFilePath*/uploadFile.path)
val metaData = ObjectMetadata()
metaData.setHeader("x-oss-object-acl", "public-read")
put.metadata = metaData

我们将上传文件的访问权限设置为public-read公共读,然后再进行一次上传

3.3 再次访问URL

我们修改好代码,重新上传文件后,再次在浏览器中使用3.1 对外访问的URL中的URL http://my-dev-oss.oss-cn-hangzhou.aliyuncs.com/photo2.jpeg 进行访问
在这里插入图片描述
可以看到,文件直接就被下载下来了,这样我们就可以直接公网访问上传的文件了

4. 上传多个文件

4.1 阿里云OSS上传工具类

我们会有同时上传多个文件的需求,对此进行了封装

object AliOssUtils {
	val accessKey = "你的AccessKey"
	val secretKey = "你的SecretKey"
	val endpoint = "oss-cn-hangzhou.aliyuncs.com"
	val bucketName = "my-dev-oss"

    init {
        OSSLog.enableLog() //调用此方法开启日志
    }

    /**
     * 上传多个文件
     */
    fun uploadFiles(
        application: Context,
        uploadFiles: List<File>,
        callBack: (List<String>) -> Unit
    ) {
        if (uploadFiles.isEmpty()) return
        var uploadResultList = ArrayList<String>()
        var uploadFileCount = uploadFiles.size
        var uploadResultCount = AtomicInteger(0)
        uploadFiles.forEach {
            uploadFile(application, it) { url ->
                val count = uploadResultCount.addAndGet(1)
                if (url != null && !TextUtils.isEmpty(url)) {
                    uploadResultList.add(url)
                }
                if (count == uploadFileCount) {
                    callBack.invoke(uploadResultList)
                }
            }
        }
    }

    /**
     * 上传单个文件
     */
    fun uploadFile(
        application: Context,
        uploadFile: File,
        callBack: ((String?) -> Unit)? = null
    ) {
        val provider: OSSCustomSignerCredentialProvider =
            object : OSSCustomSignerCredentialProvider() {
                override fun signContent(content: String): String {
                    // 此处本应该是客户端将contentString发送到自己的业务服务器,然后由业务服务器返回签名后的content。关于在业务服务器实现签名算法
                    // 详情请查看http://help.aliyun.com/document_detail/oss/api-reference/access-control/signature-header.html。客户端
                    // 的签名算法实现请参考OSSUtils.sign(accessKey,screctKey,content)
                    return OSSUtils.sign(
                        accessKey,
                        secretKey,
                        content
                    )
                }
            }

        val oss: OSS = OSSClient(application, endpoint, provider)



        // 构造上传请求。
        val put = PutObjectRequest(
            bucketName, /*objectKey*/
            uploadFile.name,/*uploadFilePath*/
            uploadFile.path
        )
        val metaData = ObjectMetadata()
        //设置为公共读资源
        metaData.setHeader("x-oss-object-acl", "public-read")
        put.metadata = metaData

        // 异步上传时可以设置进度回调。
        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)

                    val publicUrl =
                        "http://${bucketName}.${endpoint}/${uploadFile.name}"
                    Log.d("PutObject", "publicUrl:$publicUrl")
                    Log.d("ZZZZ", "publicUrl:$publicUrl")
                    callBack?.invoke(publicUrl)
                }

                override fun onFailure(
                    request: PutObjectRequest?,
                    clientExcepion: ClientException?,
                    serviceException: ServiceException?
                ) {
                    // 请求异常。
                    clientExcepion?.printStackTrace()
                    if (serviceException != null) {
                        // 服务异常。
                        Log.e("ErrorCode", serviceException.getErrorCode())
                        Log.e("RequestId", serviceException.getRequestId())
                        Log.e("HostId", serviceException.getHostId())
                        Log.e("RawMessage", serviceException.getRawMessage())
                    }
                    callBack?.invoke(null)
                }
            })
        // task.cancel(); // 可以取消任务。
        // task.waitUntilFinished(); // 等待上传完成。
    }
}

4.2 使用工具类

使用起来也很简单

val selectList = ArrayList<File>()
selectList.add(File(getExternalFilesDir("fodder"), "photo2.jpeg"))
selectList.add(File(getExternalFilesDir("fodder"), "GOPR0045.JPG"))
selectList.add(File(getExternalFilesDir("fodder"), "GX010114.MP4"))

AliOssUtils.uploadFiles(this, selectList) {
    for (url in it) {
        Log.i("ZZZZ", "url:$url")
    }
}

5.其他

参考
阿里云OSS Android 接入文档
阿里云OSS Android Demo
【OSS】上传Object后如何获取访问URL?
Android 快速集成阿里云OSS服务2020
Android 阿里云oss sdk接入 优化并增加多文件上传

public class GetAndUploadFileDemo { private static String TAG = "GetAndUploadFileDemo"; private OSSService ossService; private OSSBucket bucket; public void show() { ossService = OSSServiceProvider.getService(); bucket = ossService.getOssBucket("youcaidao"); // 文件的常规操作如普通上传、下载、拷贝、删除等,与Data类一致,故这里只给出断点下载和断点上传的demo resumableDownloadWithSpecConfig(); // delay(); // resumableUpload(); // delay(); // resumableDownload(); // delay(); } public void delay() { try { Thread.sleep(30 * 1000); } catch (Exception e) { e.printStackTrace(); } } // 断点上传 public void resumableUpload() { // OSSData ossData = ossService.getOssData(sampleBucket, "sample-data"); // ossData.setData(data, "raw"); // 指定需要上传的数据和它的类型 // ossData.enableUploadCheckMd5sum(); // 开启上传MD5校验 // ossData.upload(); // 上传失败将会抛出异常 OSSFile bigfFile = ossService.getOssFile(bucket, "de.jpg"); try { bigfFile.setUploadFilePath( "/storage/emulated/0/Android/data/com.qd.videorecorder/video/VMS_1439866564822.jpg", "image/jpg"); bigfFile.ResumableUploadInBackground(new SaveCallback() { @Override public void onSuccess(String objectKey) { Log.d(TAG, "[onSuccess] - " + objectKey + " upload success!"); } @Override public void onProgress(String objectKey, int byteCount, int totalSize) { Log.d(TAG, "[onProgress] - current upload " + objectKey + " bytes: " + byteCount + " in total: " + totalSize); } @Override public void onFailure(String objectKey, OSSException ossException) { Log.e(TAG, "[onFailure] - upload " + objectKey + " failed!\n" + ossException.toString()); ossException.printStackTrace(); ossException.getException().printStackTrace(); } }); } catch (FileNotFoundException e) { e.printStackTrace(); } } // 断点下载 public void resumableDownload() { OSSFile bigFile = ossService.getOssFile(bucket, "bigFile.dat"); bigFile.ResumableDownloadToInBackground( "/storage/sdcard0/src_file/bigFile.dat", new GetFileCallback() { @Override public void onSuccess(String objectKey, String filePath) { Log.d(TAG, "[onSuccess] - " + objectKey + " storage path: " + filePath); } @Override public void onProgress(String objectKey, int byteCount, int totalSize) { Log.d(TAG, "[onProgress] - current download: " + objectKey + " bytes:" + byteCount + " in total:" + totalSize); } @Override public void onFailure(String objectKey, OSSException ossException) { Log.e(TAG, "[onFailure] - download " + objectKey + " failed!\n" + ossException.toString()); ossException.printStackTrace(); } }); } // 设置相关参数的断点续传 public void resumableDownloadWithSpecConfig() { OSSFile bigFile = ossService .getOssFile(bucket, "VMS_1439866564822.jpg"); ResumableTaskOption option = new ResumableTaskOption(); option.setAutoRetryTime(2); // 默认为2次,最大3次 option.setThreadNum(2); // 默认并发3个线程,最大5个 bigFile.ResumableDownloadToInBackground( "/storage/emulated/0/Android/data/com.qd.videorecorder/video/VMS_1439866564822.jpg", new GetFileCallback() { // /storage/emulated/0/DCIM/Camera/VID_20150803_173350.mp4 @Override public void onSuccess(String objectKey, String filePath) { System.out.println("[onSuccess] - " + objectKey + " storage path: " + filePath); } @Override public void onProgress(String objectKey, int byteCount, int totalSize) { System.out.println("[onProgress] - current download: " + objectKey + " bytes:" + byteCount + " in total:" + totalSize); } @Override public void onFailure(String objectKey, OSSException ossException) { System.out.println("[onFailure] - download " + objectKey + " failed!\n" + ossException.toString()); ossException.printStackTrace(); } }); } }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

氦客

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值