一、前言
本篇是英才App中拍照/相册组件实践系列第二篇,在第一篇中主要介绍了如何通过系统相机/相册和自定义相册中获取图片文件,这些文件可以供本地使用和展示。但是需要将文件保存在服务端通过http访问,就涉及到图片的上传功能。58内部提供了wos文件上传服务器,通过wos sdk提供的能力可以很方便的完成本地图片的上传。接下来将介绍如何使用wos进行上传,以及过程中碰到的问题。
二、实现
先简单介绍下wos。wos是58中台为各业务提供通用的文件上传功能。分为服务端和客户端sdk。wos sdk提供小粒度的文件分片上传功能,调用方传入文件路径,SDK将文件上传到WOS存储,上传完成后向调用方回调文件的存储路径。
1、wos接入申请
使用WOS需要通过架构部运营平台申请,申请地址:storage-wos.58corp.com/
wos上传需要wos服务端支持,大致流程如下:
-
集成方通过wos提供的管理平台申请开通上传服务,App申请通过之后,管理平台会自动分配AppID、SecretKey和SecretID,其中AppID可以在“我的WOS”中“AppID”一列显示,SecretKey和SecretID会通过邮件发送给申请者。
-
集成方申请文件上传存储目录bucketId。
-
集成方需要一个自己的服务端接口来和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上传的流程以及过程中需要重点关注的一些调用方式。对于新手接入可以参考,避免接入过程掉入坑里。