英才App中拍照/相册组件实践(二)-图片上传

54 阅读3分钟

一、前言

本篇是英才App中拍照/相册组件实践系列第二篇,在第一篇中主要介绍了如何通过系统相机/相册和自定义相册中获取图片文件,这些文件可以供本地使用和展示。但是需要将文件保存在服务端通过http访问,就涉及到图片的上传功能。58内部提供了wos文件上传服务器,通过wos sdk提供的能力可以很方便的完成本地图片的上传。接下来将介绍如何使用wos进行上传,以及过程中碰到的问题。

二、实现

先简单介绍下wos。wos是58中台为各业务提供通用的文件上传功能。分为服务端和客户端sdk。wos sdk提供小粒度的文件分片上传功能,调用方传入文件路径,SDK将文件上传到WOS存储,上传完成后向调用方回调文件的存储路径。

1、wos接入申请

使用WOS需要通过架构部运营平台申请,申请地址:storage-wos.58corp.com/

wos上传需要wos服务端支持,大致流程如下:

  1. 集成方通过wos提供的管理平台申请开通上传服务,App申请通过之后,管理平台会自动分配AppID、SecretKey和SecretID,其中AppID可以在“我的WOS”中“AppID”一列显示,SecretKey和SecretID会通过邮件发送给申请者。

  2. 集成方申请文件上传存储目录bucketId。

  3. 集成方需要一个自己的服务端接口来和wos平台对接,当客户端请求该接口时,该接口责生成一个唯一的fileId,并根据SecretID、appId、bucketId、expire(token有效时间)等信息向wos服务端申请token,随后将token、fileID、ttl(文件有效期)返回给客户端。

2、文件上传前进行压缩

文件过大,一是会导致上传慢,等待时间长,体验差;二是在移动数据环境下会浪费用户的流量。因此在上传前,我们会对图片文件进行压缩。项目里压缩功能采用的是开源工具Luban。大致实现代码如下:

fun upload(pathName: String) {        val srcFile = File(pathName)        //对于超过 1MB 的图片需要进行压缩        Luban.with(ServiceProvider.getApplication())            .load(srcFile)            .ignoreBy(MAX_UPLOAD_SIZE)            .setTargetDir(                ServiceProvider.getApplication().getExternalFilesDir(Environment.DIRECTORY_PICTURES)                    .toString()            )            .filter(CompressionPredicate { path ->                !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"))            })            .setCompressListener(object : OnCompressListener {                override fun onStart() {                    //压缩开始前调用,可以在方法内启动 loading UI                }                override fun onSuccess(file: File) {
                   //压缩完成后,执行上传任务
                    exeUploadTask(file)                }                override fun onError(e: Throwable) {                    //当压缩过程出现问题时调用                    //上传原文件                    exeUploadTask(srcFile)                }            }).launch()    }

Luban使用起来也很方便,可以传入压缩的尺寸上限,超过该大小即会进行压缩。Luban内部通过降低图片的质量达到减小图片大小的目的。

3、图片上传任务执行

wos只支持单文件上传,并不支持同时传入多个文件并行执行。为了达到此目的,我们只需要开启多个线程并发执行即可。具体实现如下:

selectedMediaList.forEach {            it.localPath?.let { it1 -> upload(it1) }        }

4、上传到wos前获取文件token

为了防止恶意上传,每个文件上传到wos服务器上之前都需要调用业务方token接口获取该文件的实时token(token可以设置有效期),获取到文件token后,才可以正常请求wos服务接口,否则会报错。获取token实现如下:

var fileName =            "${System.currentTimeMillis() + (Math.random() * 1000).toInt()}.jpg"        GetTokenTask(fileName)            .exeForObservable()            .flatMap {                createWosUploadObservable(it, file, fileName)            }            .subscribe(object : SimpleSubscriber<String?>() {                override fun onError(e: Throwable) {                    super.onError(e)                    createWosUpload(null, file, fileName)                }                override fun onNext(s: String?) {                    super.onNext(s)                }            })

这里需要注意一点,我们在申请wos服务接入时,可以设置wos上传的方式为是否允许相同名字文件覆盖。wos对相同名字的文件视为同一文件。在英才wos上传服务中,设置的是不允许覆盖。因此我们需要把我们待上传的图片赋值一个随机名字,否则会上传失败。

5、执行wos上传任务

 private fun exeWosUpload(res: WosAuthRes?,                             file: File,                             fileName: String){        WubaUploader.init(mContext, true, true)        val filePath = file.path        val wosToken: String = res?.token ?: "1"//如果赋值空,通不过校验,不会走网络请求        val fileId = fileName        val uploaderModel = WubaUploaderModel(            WOS_APP_ID, WOS_BUCKET, WOS_API_HOST,            filePath, wosToken, fileId, 0        )        WubaUploader.upload(uploaderModel, uploadListener)    }

wos上传需要的参数包括filePath、wosToken、fileName以及申请接入时的wos服务器数据:appId、bucket、上传host、过期时间wosFileExpire等。这里重点需要注意wosFileExpire这个参数,其表示文件在wos中存放的时间,单位为小时,最小为168小时(7天),不带此参数或者参数值为0表示永久保存。由于是个必传参数,如果没有特殊需要比如日志等文件上传可能需要过期删除;设置为0即可。否则,过期后,wos服务器会自动从服务器上删除该文件,用户拉取到的http图片链接将失效。

6、上传成功后回调

WUploadManager.get().uploadAsync(model, object: WUploadManager.OnUploadListener {            override fun onUploadSuccess(uploadTaskId: String?, wosUrl: WUploadManager.WosUrl?) {                Log.e("fuzc", "onUploadSuccess uploadTaskId=$uploadTaskId, wosUrl=$wosUrl")                listener?.onUploadSucceed(uploadTaskId, wosUrl?.accessUrl,model.filePath)            }            override fun onUploadFailed(uploadTaskId: String?, error: WError?) {                Log.e("fuzc", "onUploadFailed, uploadTaskId=$uploadTaskId, error=$error")                listener?.onUploadFailed(uploadTaskId, error?.errorCode, error?.errorMsg)            }        }        ) { p0, p1, p2 ->            Log.e("fuzc", "p0=$p0, p1=$p1, p2=$p2")        }

上传成功后会回调传给url,失败也会给出错误信息。

三、总结

本文主要记录了使用wos上传的流程以及过程中需要重点关注的一些调用方式。对于新手接入可以参考,避免接入过程掉入坑里。