Skip to content

项目整体设计

echisan edited this page Apr 20, 2019 · 2 revisions

需求

描述上说其实很简单,就是模拟登陆获取cookie并使用该cookie请求上传图片的接口,但是做起来很容易就这么干了
写一个伪代码,核心逻辑大概是这个样子,的确就很简单,确实没有必要像我这样写这么多东西。

    public String uploadImageToSina(File file){
        if(!file.exists()){return null;}
        String cookie = getCookieFromCache();
        if(cookie == null){
            // 登陆获取cookie
            cookie = login(username,password);
            // 如果登陆成功
            if(cookie != null){
                setCookieToCache(cookie);
            }else { 
                return "登陆失败"; 
            }
        }
        res = doUpload(file);
        if (res.success){
            return  res.url;
        }
        return "上传失败";
    }

我的想法

上述伪代码完全足以实现上传图片到微博图床的功能,但是,这也就仅仅实现了这样一个功能。提供的库一般是供给外部稳定使用的,外部是无法修改内部的内容的。比如最新的上传接口url改了,或者有可能上传图片需要多加一个请求头等等,如上代码基本上说是没办法改的。还有一个比较重要的功能就是重试了。

重试

重试这个部分需要拿出来讲,毕竟是比较重要的部分。因为上传图片需要有两步动作,第一是登陆获取cookie,其次才是上传图片。假如登陆失败了,目前来说一般是没办法处理的,只能抛出一个LoginFailedException,然而上传图片有一种情况(目前只知道这种情况,不排除之后会增加)是库可以处理的,即是cookie过期,上传图片失败。
上述伪代码中并未对该问题进行处理,假如需要对上述代码进行添加重试要怎么做呢,大概如下

    public String uploadImageToSina(File file){
       // 这里与上述代码一致,为了简短篇幅不贴出来了
        ...
        
        // 后续添加的,重新登陆,重新上传。
        if (res.cookieExpiration){
            clearCookieCache();
            cookie = login(username,password);
            setCookieToCache(cookie);
            // 重新上传
            uploadImageToSina(file);
        }
        return "上传失败";
    }

目前解决方案

如果采用上述这样的伪代码这样的方案,会发现操作缓存(CacheUtils),登陆操作(LoginUtils),上传操作全都柔和在一块了。虽然我一开始的代码就是这么写的,重试也是上述那么写的,甚至递归调用那个上传的方法。在我重构现在这个版本的时候,写着写着发现我又把登陆放在上传接口上了,疯狂查看23种设计模式找灵感,到底该如何实现,最后灵机一动,引入了 拦截器模式

说一下我的解决方案(不过真的是想了好久才想到这么干,可能还会有更好的方案,目前先这样了)
拦截器的内容可以查看这里 先用伪代码表达一下

// 先定义4个关键的拦截器,关键的获取cookie,登陆,重试都需要利用这4个拦截器
// 1. 初始化上传参数的拦截器 2. 检查缓存中的cookie 3. 登陆操作 4. 用于重试机制的二检操作
class InitRequestDataIntercepor implements UploadInterceptor{}
class CookieInterceptor implements UploadInterceptor{}
class LoginInterceptor implements UploadInterceptor{}
class ReCheckCookieInterceptor implements UploadInterceptor{}

public UploadResponse upload(File file){
    assertTrue(file.exists());
    // 跑一遍定义好的拦截器,如果有返回false的就不往下执行了
    for(UploadInterceptor ui:UploadInterceptors){
        if(!ui.processBefore()){
           return failedUploadResponse;
        }
    }
    // 真正的上传操作,doUpload是一个抽象方法,需要在子类中实现
    res = doUpload(file);
    
    // 再跑一遍拦截器,可以对响应的结果进行处理
    for(UploadInterceptor ui:UploadInterceptors){
        ui.processAfter(res);
    }
    // 结束
    return res;
}

换成中文来描述的话,就是上传的方法或者类只应该跟上传相关操作有关,所以别的操作在拦截器中操作,正真的上传操作需要继承上述伪代码的类,因为上传的操作也有可能变化,不变的只有这一套遍历拦截器的逻辑,所以这里引入 模板方法模式 ,具体方法交由子类去实现。

整体逻辑大概是这样

Clone this wiki locally