设计模式只看不练可不行,写个上传解耦库练练手(中)

简介: 写个上传解耦库练练手

0x3、架构设计


从宏观角度看,文件上传的经历的过程跟车间流水线组装很像,以 袋装薯片 的生产流程为例:


土豆进厂 → 清洗削皮 → 切片烘干 → 350度高温油炸 → 加盐 → 按克分装充入氮气 → 袋装薯片


从土豆经历各种转换,到最后的袋装薯片,类比到我们的单个上传任务:


网络异常,图片无法展示
|


再抽象简化为三个部分:


网络异常,图片无法展示
|


任务构建和任务完成 这种流水线处理任务的方式,就很适合上 责任链模式 了。


传统责任链实现,单向往后传递,一层层拦截,直到有人处理为止。


这里参考下 OkHttp拦截器的实现,双向责任链,大概原理:


  • Interceptor实现类调用 intercept(Chain) 往下传递Chain实例(包含此拦截器处理后的requests);
  • 最后一个拦截器调用 chain.proceed() 返回Response实例,递归往上传递;


具体讲解可见:《把书读薄 | 《设计模式之美》设计模式与范式(行为型-责任链模式)》


这里可以先前后都是Task,后续再来拆,所以单个任务的组合变成了:


请求前的拦截器若干 → 执行上传请求 → 请求后的拦截器若干


执行上传请求,就交给用户自定义了,提供请求构造及发送请求的方法。成功与否,通过回调告知即可。


这是单个任务上传的情况,多个任务还需要:任务队列轮询器线程池


当发起一个上传任务时,把任务加到队列中,轮询器不断从队列里拿任务(直到没任务),从线程池中拿个线程执行任务。


大概的原理很清晰,接着就是具体的代码实现了,代码注释写得很详尽了,就不再一一解释了~


0x4、库的使用


目前还是 写来玩玩状态(一堆坑,菜鸡边踩边改中~),感兴趣可以star下, 仓库地址github.com/coder-pig/C…


添加依赖


allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}
dependencies {
        implementation 'com.github.coder-pig:CpLightUpload:v0.0.3'
}


① 自定义Task


不同场景可能有不同的需求,按需自定义属性:


class CpImageTask : ImageTask() {
    var needRotate: Boolean? = null
    var needCompress: Boolean? = null
    var compressPercent: Int? = 80
}
class CpVideoTask : VideoTask() {
    var limitSize: Int? = -1    // 视频限制大小
    var compressVideoPath: String? = null   // 压缩视频路径
    var compressVideoMD5: String? = null   // 压缩视频MD5
    var firstFramePath: String? = null   // 视频第一帧路径
    var firstFrameMD5: String? = null    // 视频第一帧MD5
}


② 自定义上传配置


就是一个上传的默认配置,当上传Task对应项没有配置时,填充默认配置:


class ImageUploadConfig : LightUploadConfig() {
    var needRotate: Boolean = true  // 是否需要旋转纠正
    var needCompress: Boolean = true   // 是否需要压缩
    var compressPercent: Int = 80   // 压缩比例,默认80
}
class VideoUploadConfig : LightUploadConfig() {
    // 按需自定义
}


③ 自定义前拦截器


继承 Interceptor 接口,实现intercept() 方法:


class PictureRotateInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Task {
        val task = chain.task()
        val config = task.config as? ImageUploadConfig
        if (task is CpImageTask) {
            if(task.needRotate == null) task.needRotate = config?.needRotate
            "============ 判断是否需要图片翻转 ============".logV()
            val degree = FileUtils.readPictureDegree(task.filePath!!)
            if (degree != 0) {
                "图片旋转修正".logV()
                FileUtils.rotateToDegrees(task.filePath!!, degree.toFloat())
                "图片旋转处理完毕".logV()
            } else {
                "不需要旋转修正.".logV()
            }
        }
        // 往下传递
        return chain.proceed(task)
    }
}
class VideoFrameInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Task {
        val task = chain.task()
        if (task is CpVideoTask) {
            "生成视频缩略图...".logV()
            // 获取第一帧文件名
            val tag = task.compressVideoPath!!.substring(task.compressVideoPath!!.lastIndexOf("/")) 
            val frameFile = File(getExternalVideoPath() + tag + ".jpg")
            task.firstFramePath = frameFile.absolutePath
            val mmr = MediaMetadataRetriever()
            mmr.setDataSource(task.compressVideoPath!!)
            val frameBitmap = mmr.frameAtTime
            FileUtils.compressImage(frameBitmap, frameFile, 80)
            task.firstFrameMD5 =  FileUtils.getFileMD5ToString(frameFile)
            LightUpload.upload(task = CpImageTask().apply {
                filePath = task.firstFramePath
                md5 = task.firstFrameMD5
            })
            frameBitmap?.recycle()
        }
        return chain.proceed(task)
    }
}


④ 自定义请求


实现 Upload 抽象类,重写initRequest()和sendRequest()方法,对不同请求结果进行回调:


class HucUpload : Upload() {
    override fun sendRequest() {
        "开始文件上传...".logV()
        var ins: InputStream? = null
        try {
            mTask.reqData?.let { req ->
                val conn = (URL(req.uploadUrl).openConnection() as HttpURLConnection).apply {
                    readTimeout = req.timeout!!
                    connectTimeout = req.timeout!!
                    doInput = true
                    doOutput = true
                    useCaches = false
                    requestMethod = req.requestMethod
                    // 请求头设置
                    val boundary = UUID.randomUUID()
                    req.headers["Content-Type"] = "multipart/form-data;boundary=${boundary}"
                    for ((k, v) in req.headers) setRequestProperty(k, v)
                    val dos = DataOutputStream(outputStream)
                    val sb = StringBuilder().append("--").append(boundary).append("\r\n")
                        .append("Content-Disposition: form-data; name=\"file\"; filename=\"")
                        .append(mTask.md5).append(mTask.fileName).append("\"")
                        .append("\r\n")
                        .append("Content-Type: application/octet-stream; charset=utf-8")
                        .append("\r\n").append("\r\n")
                    dos.write(sb.toString().toByteArray())
                    ins = FileInputStream(File(mTask.filePath!!))
                    val bytes = ByteArray(1024)
                    var len: Int
                    while (ins!!.read(bytes).also { len = it } != -1) {
                        dos.write(bytes, 0, len)
                    }
                    ins!!.close()
                    dos.write("\r\n".toByteArray())
                    val endData: ByteArray = "--$boundary--\r\n".toByteArray()
                    dos.write(endData)
                    dos.flush()
                }
                // 获取响应
                val input = BufferedReader(InputStreamReader(conn.inputStream, "UTF-8"))
                val sb1 = StringBuilder()
                var ss: Int
                while (input.read().also { ss = it } != -1) {
                    sb1.append(ss.toChar())
                }
                val result = sb1.toString()
                "文件上传结束...".logV()
                mTask.response = Response(conn.responseCode, result)
                mTask.status = TaskStatus.DONE
                mCallback?.onSuccess(mTask)
            }
        } catch (e: IOException) {
            e.message?.logE()
            mTask.status = TaskStatus.FAILURE
            mTask.throwable = e
            mCallback?.onFailure(mTask)
            LightUpload.postTask(mTask)
        } finally {
            if (ins != null) {
                try {
                    ins!!.close()
                } catch (e: IOException) {
                    e.message?.logE()
                }
            }
        }
    }
}


相关文章
|
设计模式 搜索推荐 数据库连接
第二篇 创建型设计模式 - 灵活、解耦的创建机制
第二篇 创建型设计模式 - 灵活、解耦的创建机制
167 0
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。
|
设计模式 Java
JAVA设计模式16:职责链模式,轻松解耦发送者和接收者
JAVA设计模式16:职责链模式,轻松解耦发送者和接收者
285 0
|
设计模式
[笔记]设计模式实践_SDLGUI封装系列之开源库SDL2_gui编译
[笔记]设计模式实践_SDLGUI封装系列之开源库SDL2_gui编译
165 0
|
设计模式 Java API
|
设计模式
[笔记]设计模式实践_SDLGUI封装系列之开源库SDL2_gui编译
设计模式实践_SDLGUI封装系列之开源库SDL2_gui编译
263 0
|
设计模式
访问者设计模式(Visitor)的生动案例-ASM字节码修改库
访问者设计模式(Visitor)的生动案例-ASM字节码修改库
172 0
|
Python 设计模式 数据格式
开始慢慢学习这本书了。。Python编程实战:运用设计模式、并发和程序库创建高质量程序
没办法,不到设计模式,算法组合这些,在写大一点程序的时候,总是力不从心。。。:( 一开始可能要花很多时间来慢慢理解吧,,这毕竟和《大话设计模式》用的C#语言有点不太一样。。。 书上代码是3版本的,有些库的用法不一样,还要改回2.7的才可以测试。
1383 0

热门文章

最新文章

下一篇
oss云网关配置