装饰器模式实现在线同步图片处理

1,301 阅读5分钟

温馨提示:学习本文之前,需要对图片处理工具有一定的了解,可以看下我之前写的图片处理专栏

本文要介绍的内容很厉害,系好安全带,发车了~
不知道读者有没有使用过类似阿里云文件URL处理图片的功能 image.png

通过在原图链接后拼接图片处理参数,可以实现图片实时处理!!!听起来很厉害吧,那我们自己能否实现这一功能呢?

其实要实现起来也很简单 ^-^

image.png

在线图片处理的整体流程

在展开今天内容之前,先大概讲下整个接口的处理数据流是怎样的,让大家有个宏观的印象。

1、整体流程

image.png

2、图片服务(image service)接口处理流程

image.png 这里有个下载原图的步骤,由于图片服务并没有源文件,所以必须先将源文件拉取到本地,才能进行后续处理

由于请求会先经过存储网关,所以会解析原图的内网地址,通过内网带宽拉取图片,速度是很快的

分析阶段

图片服务收到请求,处理逻辑为:解析参数 -> 校验参数 -> 拼接处理命令 -> GM执行命令 -> 生成结果文件 -> 返回文件

所以关键步骤就是拼接处理命令

目前实现了:图片缩放、自适应方向、质量变换、格式转换、裁剪、水印等功能

一般最快速的实现方式就是循环将参数转为对应的命令,拼接字符串,但是这不符合“开闭原则”呀,有没有跟优雅的方式,可以实现功能扩展而不需要改动原逻辑呢?今天的主角——装饰器模式隆重登场~

什么是装饰器模式?

在不影响其他对象的情况下,以动态地给一个对象添加一些额外的职责。

该模式一般包含如下的角色:

  • Component抽象构件角色:真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互
  • ConcreteComponent具体构件角色:定义一个具体的对象,也可以给这个对象添加一些职责
  • Decorator装饰角色:持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能
  • ConcreteDecorator具体装饰角色:负责给构件对象增加新的责任

代码实现

本次实现的URL图片处理参考阿里云的链接拼接方式

1、参数解析

假设现在有一个请求,请求链接为: xxx.test.com/test.jpg?s-…

该链接的图片处理参数部分为:s-oss-process=image/resize,m_mfit,w_100,h_100/quality,q_80/format,jpg

s-oss-process=image 主要是用于让网关识别这是图片处理的流量,将流量转发到具体的服务

所以实际获取到需要解析的参数为:resize,m_mfit,w_100,h_100/quality,q_80/format,jpg

观察参数可以发现,每个具体的功能用“/”分隔,功能中第一个逗号前面的是操作名,后面是操作的具体参数,核心代码如下:

// 这里假设imageProcess已经是上面需要解析的部分(resize,m_mfit,w_100,h_100/quality,q_80/format,jpg)
String[] params = imageProcess.split("/");
LinkedHashMap<String, String> paramsMap = new LinkedHashMap<>();
for (String paramsStr : params) {
    String[] kv = paramsStr.split(",", 2);
    if (kv.length != 2) {
        throw new ParamValidException(ErrorCode.COMMON_ILLEGAL_PARAMS, "处理参数拼接有误");
    }
    // 判断功能参数以及格式转换是否合法
    if (!ImageProcessConstants.Action.containsAction(kv[0])) {
        throw new ParamValidException(ErrorCode.COMMON_ILLEGAL_PARAMS, kv[0]+"为不支持的图片处理功能");
    }
    if (StringUtils.equals(kv[0], ImageProcessConstants.Action.FORMAT)) {
        if (!ImageProcessConstants.Format.containsFormat(kv[1])) {
            throw new ParamValidException(ErrorCode.COMMON_ILLEGAL_PARAMS, kv[1]+"为不支持的图片转换格式");
        }
    }
    paramsMap.put(kv[0], kv[1]);
}

我们将URL解析成一个有序的Map:

  1. resize:m_mfit,w_100,h_100
  2. quality:q_80
  3. format:jpg

2、拼接处理命令

这里就用到了装饰器模式,先看看结构图: image.png

1)定义图片处理顶层接口(Component)

/**
 * 图片处理顶层接口,所有被包装类,包装类都继承这个接口
 */
public interface ImageProcess {

    /**
     * 获取图片处理命令
     */
    ImageCommand getImageCommands();

}

2)定义具体的构建角色(ConcreteComponent)

/**
 * 图片处理类的实现类
 */
public class ConvertImageProcess implements ImageProcess {

    @Override
    public ImageCommand getImageCommands() {
        return new ConvertCommand();
    }

}

3)定义图片处理的装饰角色(Decorator)

该对象持有ImageProcess具体构建角色对象的引用,同时自己也继承ImageProcess接口,通过构造方法接收具体的处理参数,重写接口的方法,实现对具体构件对象的功能增强。

/**
 * 装饰器都抽象类,Decorator角色
 */
public class ImageProcessDecorator implements ImageProcess {

    /**
     * 被装饰对象
     */
    private ImageProcess imageProcess;

    /**
     * 各具体装饰器对应的参数
     */
    protected String params;

    public ImageProcessDecorator(ImageProcess imageProcess, String params) {
        this.imageProcess = imageProcess;
        this.params = params;
    }

    @Override
    public ImageCommand getImageCommands() {
        return this.imageProcess.getImageCommands();
    }
}

4)实现具体的装饰器角色(ConcreteDecorator)

这里只列举一个例子,其他实现也很类似

/**
 * 质量转换装饰器
 */
public class QualityProcessDecorator extends ImageProcessDecorator {

    public QualityProcessDecorator(ImageProcess imageProcess, String params) {
        super(imageProcess, params);
    }

    @Override
    public ImageCommand getImageCommands() {
        int quality = this.parseQualityParams(params);
        ImageCommand imageCommand = super.getImageCommands();
        imageCommand.quality(quality);
        return imageCommand;
    }

    /**
     * 质量转换参数解析
     *
     * @param params 如:q_100
     */
    private int parseQualityParams(String params) {
        if (StringUtils.isBlank(params)) {
            throw new ParamValidException(ErrorCode.COMMON_ILLEGAL_PARAMS, "quality params is empty");
        }
        if (params.length() <= 2 || !params.startsWith("q_")) {
            throw new ParamValidException(ErrorCode.COMMON_ILLEGAL_PARAMS, "quality参数错误");
        }
        String value = params.substring(2);
        int quality = NumberUtils.toInt(value, 0);
        if (quality < 1 || quality > 100) {
            throw new ParamValidException(ErrorCode.COMMON_ILLEGAL_PARAMS, "quality的取值范围是 [1,100]");
        }
        return quality;
    }
    
}

外层通过第1步解析参数得到的Map,调用装饰器,生成具体的执行命令:

/**
 * 获取图片组合命令
 *
 * @param params        参数
 * @param srcPath       源文件路径
 * @param distPath      目标文件路径
 */
public ImageCommand getImageComposeCommand(Map<String, String> params, String srcPath, String distPath) {
    ImageProcess imageProcess = new ConvertImageProcess();
    imageProcess = new AddImageProcessDecorator(imageProcess, srcPath);
    for (Map.Entry<String, String> entry : params.entrySet()) {
        switch (entry.getKey()) {
            case ImageProcessConstants.Action.RESIZE:
                imageProcess = new ResizeProcessDecorator(imageProcess, entry.getValue()); break;
            case ImageProcessConstants.Action.AUTO_ORIENT:
                imageProcess = new AutoOrientProcessDecorator(imageProcess, entry.getValue()); break;
            case ImageProcessConstants.Action.CROP:
                imageProcess = new CropProcessDecorator(imageProcess, entry.getValue()); break;
            case ImageProcessConstants.Action.QUALITY:
                imageProcess = new QualityProcessDecorator(imageProcess, entry.getValue()); break;
        }
    }
    imageProcess = new AddImageProcessDecorator(imageProcess, distPath);
    return imageProcess.getImageCommands();
}

至此,我们将URL传递的参数,解析为GraphicsMagick的处理命令:convert ${srcPath} -resize 100x100^ -quality 80 ${disPath}.jpg

如果对上面的命令感觉很懵的话,可以看下:GraphicsMagick实现云服务商基础图片处理

3、生成结果文件

将第2步生成的图片处理命令丢给GM进程进行处理,就能得到结果文件啦~

这里引入了gm4java这个工具提供的进程池控制GM进程数(代码中注入即可:PooledGMService)

<dependency>
    <groupId>com.sharneng</groupId>
    <artifactId>gm4java</artifactId>
    <version>1.1.1</version>
</dependency>

可以参考看下:Springboot集成GraphicsMagick

End

以上就是在线图片处理的整个处理过程,是不是发现逻辑其实很简单,使用装饰器模式很方便的让我们后续增加更多的功能。这就是具体设计模式在实际项目中的应用!

后续对服务压测结束后,可能会输出关于结果文件采用 普通方式 和 “零拷贝”方式 响应给调用方的文章,大家有兴趣可以点个关注留意下~