当前位置: 首页 > 新闻动态 > 软件编程

SpringMVC返回图片的几种方式(小结)

作者:用户投稿 浏览: 发布日期:2026-01-12
[导读]:这篇文章主要介绍了SpringMVC返回图片的几种方式(小结),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
目录
  • I. 返回二进制图片
  • II. 返回图片的几种方式封装
    • 1. bean定义
    • 2. 返回的封装方式
  • III. 项目相关

    后端提供服务,通常返回的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";
    }
    

    注意事项

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

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

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

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

    那么我们提供的一个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);
        }
      }
    }
    

    说明:

    请无视上面的几个自定义异常方式,需要使用时,完全可以干掉这些自定义异常即可;这里简单说一下,为什么会在实际项目中使用这种自定义异常的方式,主要是有以下几个优点

    配合全局异常捕获(ControllerAdvie),使用起来非常方便简单

    所有的异常集中处理,方便信息统计和报警

    如,在统一的地方进行异常计数,然后超过某个阀值之后,报警给负责人,这样就不需要在每个出现异常case的地方来主动埋点了

    避免错误状态码的层层传递

    - 这个主要针对web服务,一般是在返回的json串中,会包含对应的错误状态码,错误信息
    - 而异常case是可能出现在任何地方的,为了保持这个异常信息,要么将这些数据层层传递到controller;要么就是存在ThreadLocal中;显然这两种方式都没有抛异常的使用方便

    有优点当然就有缺点了:

    异常方式,额外的性能开销,所以在自定义异常中,我都覆盖了下面这个方法,不要完整的堆栈

    @Override
    public synchronized Throwable fillInStackTrace() {
      return this;
    }

    编码习惯问题,有些人可能就非常不喜欢这种使用方式

    III. 项目相关

    只说不练好像没什么意思,上面的这个设计,完全体现在了我一直维护的开源项目 Quick-Media中,当然实际和上面有一些不同,毕竟与业务相关较大,有兴趣的可以参考

    QuickMedia: https://github.com/liuyueyi/quick-media :

    BaseAction: com.hust.hui.quickmedia.web.wxapi.WxBaseAction#buildReturn

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

    免责声明:转载请注明出处:http://m.lexweb.cn/news/519312.html

    扫一扫高效沟通

    多一份参考总有益处

    免费领取网站策划SEO优化策划方案

    请填写下方表单,我们会尽快与您联系
    感谢您的咨询,我们会尽快给您回复!