SpringMVC返回图片的几种方式


声明:本文转载自https://my.oschina.net/u/566591/blog/1609293,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

SpringMVC返回图片的几种方式

后端提供服务,通常返回的json串,但是某些场景下可能需要直接返回二进制流,如一个图片编辑接口,希望直接将图片流返回给前端,此时可以怎么处理?

I. 返回二进制图片

主要借助的是 HttpServletResponse这个对象,实现case如下

@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"; } 

注意事项

  • 注意ContentType定义了图片类型
  • 将二进制写入 httpServletResponse#getOutputStream
  • 写完之后,flush(), close()请务必执行一次

II. 返回图片的几种方式封装

一般来说,一个后端提供的服务接口,往往是返回json数据的居多,前面提到了直接返回图片的场景,那么常见的返回图片有哪些方式呢?

  • 返回图片的http地址
  • 返回base64格式的图片
  • 直接返回二进制的图片
  • 其他...(我就见过上面三种,别的还真不知道)

那么我们提供的一个Controller,应该如何同时支持上面这三种使用姿势呢?

1. bean定义

因为有几种不同的返回方式,至于该选择哪一个,当然是由前端来指定了,所以,可以定义一个请求参数的bean对象

@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,返回当然也有一个对应的bean

@Data public class BaseResponse {      /**      * 返回图片的相对路径      */     private String path;       /**      * 返回图片的https格式      */     private String url;       /**      * base64格式的图片      */     private String base; } 

说明:

实际的项目环境中,请求参数和返回肯定不会像上面这么简单,所以可以通过继承上面的bean或者自己定义对应的格式来实现

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

本文发表于2018年01月18日 20:32
(c)注:本文转载自https://my.oschina.net/u/566591/blog/1609293,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1703 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1