SpringMVC返回圖片的幾種方式

SpringMVC返回圖片的幾種方式

後端提供服務,一般返回的json串,可是某些場景下可能須要直接返回二進制流,如一個圖片編輯接口,但願直接將圖片流返回給前端,此時能夠怎麼處理?前端

I. 返回二進制圖片

主要藉助的是 HttpServletResponse這個對象,實現case以下java

@RequestMapping(value = {"/img/render"}, method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
@CrossOrigin(origins = "*")
@ResponseBody
public String execute(HttpServletRequest httpServletRequest,
             HttpServletResponse httpServletResponse) {
    // img爲圖片的二進制流
    byte[] img = xxx;
    httpServletResponse.setContentType("image/png");
    OutputStream os = httpServletResponse.getOutputStream();
    os.write(img);
    os.flush();
    os.close();
    return "success";
}

注意事項git

  • 注意ContentType定義了圖片類型
  • 將二進制寫入 httpServletResponse#getOutputStream
  • 寫完以後,flush(), close()請務必執行一次

II. 返回圖片的幾種方式封裝

通常來講,一個後端提供的服務接口,每每是返回json數據的居多,前面提到了直接返回圖片的場景,那麼常見的返回圖片有哪些方式呢?github

  • 返回圖片的http地址
  • 返回base64格式的圖片
  • 直接返回二進制的圖片
  • 其餘...(我就見過上面三種,別的還真不知道)

那麼咱們提供的一個Controller,應該如何同時支持上面這三種使用姿式呢?web

1. bean定義

由於有幾種不一樣的返回方式,至於該選擇哪個,固然是由前端來指定了,因此,能夠定義一個請求參數的bean對象json

@Data
public class BaseRequest {
    private static final long serialVersionUID = 1146303518394712013L;

    /**
     * 輸出圖片方式:
     *
     *  url : http地址 (默認方式)
     *  base64 : base64編碼
     *  stream : 直接返回圖片
     *
     */
    private String outType;

    /**
     * 返回圖片的類型
     * jpg | png | webp | gif
     */ 
    private String mediaType;
    

    public ReturnTypeEnum returnType() {
        return ReturnTypeEnum.getEnum(outType);
    }


    public MediaTypeEnum mediaType() {
        return MediaTypeEnum.getEnum(mediaType);
    }
}

爲了簡化判斷,定義了兩個註解,一個ReturnTypeEnum, 一個 MediaTypeEnum, 固然必要性不是特別大,下面是二者的定義後端

public enum ReturnTypeEnum {

    URL("url"),
    STREAM("stream"),
    BASE64("base");

    private String type;

    ReturnTypeEnum(String type) {
        this.type = type;
    }


    private static Map<String, ReturnTypeEnum> map;

    static {
        map = new HashMap<>(3);
        for(ReturnTypeEnum e: ReturnTypeEnum.values()) {
            map.put(e.type, e);
        }
    }

    public static ReturnTypeEnum getEnum(String type) {
        if (type == null) {
            return URL;
        }

        ReturnTypeEnum e = map.get(type.toLowerCase());
        return e == null ? URL : e;
    }
}
@Data
public enum MediaTypeEnum {
    ImageJpg("jpg", "image/jpeg", "FFD8FF"),
    ImageGif("gif", "image/gif", "47494638"),
    ImagePng("png", "image/png", "89504E47"),
    ImageWebp("webp", "image/webp", "52494646"),

    private final String ext;

    private final String mime;

    private final String magic;

    MediaTypeEnum(String ext, String mime, String magic) {
        this.ext = ext;
        this.mime = mime;
        this.magic = magic;
    }

    private static Map<String, MediaTypeEnum> map;

    static {
        map = new HashMap<>(4);
        for (MediaTypeEnum e: values()) {
            map.put(e.getExt(), e);
        }
    }

    public static MediaTypeEnum getEnum(String type) {
        if (type == null) {
            return ImageJpg;
        }

        MediaTypeEnum e = map.get(type.toLowerCase());
        return e == null ? ImageJpg : e;
    }
}

上面是請求參數封裝的bean,返回固然也有一個對應的beanapi

@Data
public class BaseResponse {

    /**
     * 返回圖片的相對路徑
     */
    private String path;


    /**
     * 返回圖片的https格式
     */
    private String url;


    /**
     * base64格式的圖片
     */
    private String base;
}

說明:服務器

實際的項目環境中,請求參數和返回確定不會像上面這麼簡單,因此能夠經過繼承上面的bean或者本身定義對應的格式來實現app

2. 返回的封裝方式

既然目標明確,封裝可算是這個裏面最清晰的一個步驟了

protected void buildResponse(BaseRequest request,
                             BaseResponse response,
                             byte[] bytes) throws SelfError {
    switch (request.returnType()) {
        case URL:
            upload(bytes, response);
            break;
        case BASE64:
            base64(bytes, response);
            break;
        case STREAM:
            stream(bytes, request);
    }
}


private void upload(byte[] bytes, BaseResponse response) throws SelfError {
    try {
        // 上傳到圖片服務器,根據各自的實際狀況進行替換
        String path = UploadUtil.upload(bytes);

        if (StringUtils.isBlank(path)) { // 上傳失敗
            throw new InternalError(null);
        }

        response.setPath(path);
        response.setUrl(CdnUtil.img(path));
    } catch (IOException e) { // cdn異常
        log.error("upload to cdn error! e:{}", e);
        throw new CDNUploadError(e.getMessage());
    }
}

// 返回base64
private void base64(byte[] bytes, BaseResponse response) {
    String base = Base64.getEncoder().encodeToString(bytes);
    response.setBase(base);
}

// 返回二進制圖片
private void stream(byte[] bytes, BaseRequest request) throws SelfError {
    try {
        MediaTypeEnum mediaType = request.mediaType();
        HttpServletResponse servletResponse = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        servletResponse.setContentType(mediaType.getMime());
        OutputStream os = servletResponse.getOutputStream();
        os.write(bytes);
        os.flush();
        os.close();
    } catch (Exception e) {
        log.error("general return stream img error! req: {}, e:{}", request, e);
        if (StringUtils.isNotBlank(e.getMessage())) {
            throw new InternalError(e.getMessage());
        } else {
            throw new InternalError(null);
        }
    }
}

說明:

請無視上面的幾個自定義異常方式,須要使用時,徹底能夠幹掉這些自定義異常便可;這裏簡單說一下,爲何會在實際項目中使用這種自定義異常的方式,主要是有如下幾個優勢

  1. 配合全局異常捕獲(ControllerAdvie),使用起來很是方便簡單

  2. 全部的異常集中處理,方便信息統計和報警

    如,在統一的地方進行異常計數,而後超過某個閥值以後,報警給負責人,這樣就不須要在每一個出現異常case的地方來主動埋點了
  3. 避免錯誤狀態碼的層層傳遞

    - 這個主要針對web服務,通常是在返回的json串中,會包含對應的錯誤狀態碼,錯誤信息
    - 而異常case是可能出如今任何地方的,爲了保持這個異常信息,要麼將這些數據層層傳遞到controller;要麼就是存在ThreadLocal中;顯然這兩種方式都沒有拋異常的使用方便

有優勢固然就有缺點了:

  1. 異常方式,額外的性能開銷,因此在自定義異常中,我都覆蓋了下面這個方法,不要完整的堆棧

    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
  2. 編碼習慣問題,有些人可能就很是不喜歡這種使用方式

III. 項目相關

只說不練好像沒什麼意思,上面的這個設計,徹底體如今了我一直維護的開源項目 Quick-Media中,固然實際和上面有一些不一樣,畢竟與業務相關較大,有興趣的能夠參考

IV. 其餘

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正

掃描關注,java分享

QrCode

相關文章
相關標籤/搜索