热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

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

这篇文章主要介绍了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";
}

注意事项

  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 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 &#63; 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 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 &#63; 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

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


推荐阅读
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 打开文件管理器_【教程】模组管理器3.1食用指南
    文编:byakko最近有部分小伙伴反应还不会使用unity模组管理器,现在我就给大家讲一下unity模组管理器——从下载到使用。完整视频版以下是无WiF ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 学习笔记(34):第三阶段4.2.6:SpringCloud Config配置中心的应用与原理第三阶段4.2.6SpringCloud Config配置中心的应用与原理
    立即学习:https:edu.csdn.netcourseplay29983432482?utm_sourceblogtoedu配置中心得核心逻辑springcloudconfi ... [详细]
  • 禁止程序接收鼠标事件的工具_VNC Viewer for Mac(远程桌面工具)免费版
    VNCViewerforMac是一款运行在Mac平台上的远程桌面工具,vncviewermac版可以帮助您使用Mac的键盘和鼠标来控制远程计算机,操作简 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
author-avatar
荣媛厉4
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有