SpringMVC返回圖片的幾種方式(小結(jié))
后端提供服務(wù),通常返回的json串,但是某些場景下可能需要直接返回二進(jìn)制流,如一個圖片編輯接口,希望直接將圖片流返回給前端,此時可以怎么處理?
I. 返回二進(jìn)制圖片
主要借助的是 HttpServletResponse這個對象,實(shí)現(xiàn)case如下
@RequestMapping(value = {"/img/render"}, method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
@CrossOrigin(origins = "*")
@ResponseBody
public String execute(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
// img為圖片的二進(jìn)制流
byte[] img = xxx;
httpServletResponse.setContentType("image/png");
OutputStream os = httpServletResponse.getOutputStream();
os.write(img);
os.flush();
os.close();
return "success";
}
注意事項(xiàng)
- 注意ContentType定義了圖片類型
- 將二進(jìn)制寫入 httpServletResponse#getOutputStream
- 寫完之后,flush(), close()請務(wù)必執(zhí)行一次
II. 返回圖片的幾種方式封裝
一般來說,一個后端提供的服務(wù)接口,往往是返回json數(shù)據(jù)的居多,前面提到了直接返回圖片的場景,那么常見的返回圖片有哪些方式呢?
- 返回圖片的http地址
- 返回base64格式的圖片
- 直接返回二進(jìn)制的圖片
- 其他...(我就見過上面三種,別的還真不知道)
那么我們提供的一個Controller,應(yīng)該如何同時支持上面這三種使用姿勢呢?
1. bean定義
因?yàn)橛袔追N不同的返回方式,至于該選擇哪一個,當(dāng)然是由前端來指定了,所以,可以定義一個請求參數(shù)的bean對象
@Data
public class BaseRequest {
private static final long serialVersionUID = 1146303518394712013L;
/**
* 輸出圖片方式:
*
* url : http地址 (默認(rèn)方式)
* 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, 當(dāng)然必要性不是特別大,下面是兩者的定義
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;
}
}
上面是請求參數(shù)封裝的bean,返回當(dāng)然也有一個對應(yīng)的bean
@Data
public class BaseResponse {
/**
* 返回圖片的相對路徑
*/
private String path;
/**
* 返回圖片的https格式
*/
private String url;
/**
* base64格式的圖片
*/
private String base;
}
說明:
實(shí)際的項(xiàng)目環(huán)境中,請求參數(shù)和返回肯定不會像上面這么簡單,所以可以通過繼承上面的bean或者自己定義對應(yīng)的格式來實(shí)現(xiàn)
2. 返回的封裝方式
既然目標(biāo)明確,封裝可算是這個里面最清晰的一個步驟了
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 {
// 上傳到圖片服務(wù)器,根據(jù)各自的實(shí)際情況進(jìn)行替換
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);
}
// 返回二進(jìn)制圖片
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);
}
}
}
說明:
請無視上面的幾個自定義異常方式,需要使用時,完全可以干掉這些自定義異常即可;這里簡單說一下,為什么會在實(shí)際項(xiàng)目中使用這種自定義異常的方式,主要是有以下幾個優(yōu)點(diǎn)
配合全局異常捕獲(ControllerAdvie),使用起來非常方便簡單
所有的異常集中處理,方便信息統(tǒng)計(jì)和報(bào)警
如,在統(tǒng)一的地方進(jìn)行異常計(jì)數(shù),然后超過某個閥值之后,報(bào)警給負(fù)責(zé)人,這樣就不需要在每個出現(xiàn)異常case的地方來主動埋點(diǎn)了
避免錯誤狀態(tài)碼的層層傳遞
- 這個主要針對web服務(wù),一般是在返回的json串中,會包含對應(yīng)的錯誤狀態(tài)碼,錯誤信息
- 而異常case是可能出現(xiàn)在任何地方的,為了保持這個異常信息,要么將這些數(shù)據(jù)層層傳遞到controller;要么就是存在ThreadLocal中;顯然這兩種方式都沒有拋異常的使用方便
有優(yōu)點(diǎn)當(dāng)然就有缺點(diǎn)了:
異常方式,額外的性能開銷,所以在自定義異常中,我都覆蓋了下面這個方法,不要完整的堆棧
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
編碼習(xí)慣問題,有些人可能就非常不喜歡這種使用方式
III. 項(xiàng)目相關(guān)
只說不練好像沒什么意思,上面的這個設(shè)計(jì),完全體現(xiàn)在了我一直維護(hù)的開源項(xiàng)目 Quick-Media中,當(dāng)然實(shí)際和上面有一些不同,畢竟與業(yè)務(wù)相關(guān)較大,有興趣的可以參考
QuickMedia: https://github.com/liuyueyi/quick-media :
BaseAction: com.hust.hui.quickmedia.web.wxapi.WxBaseAction#buildReturn
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java使用ExecutorService來停止線程服務(wù)
這篇文章主要介紹了Java使用ExecutorService來停止線程服務(wù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
詳解SpringBoot讀取resource目錄下properties文件的常見方式
這篇文章主要介紹了SpringBoot讀取resource目錄下properties文件的常見方式,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
基于Java實(shí)現(xiàn)獲取本地IP地址和主機(jī)名
這篇文章主要介紹了基于Java實(shí)現(xiàn)獲取本地IP地址和主機(jī)名,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-05-05
mybatis多層嵌套resultMap及返回自定義參數(shù)詳解
這篇文章主要介紹了mybatis多層嵌套resultMap及返回自定義參數(shù)詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Java將對象寫入文件讀出_序列化與反序列化的實(shí)例
下面小編就為大家?guī)硪黄狫ava將對象寫入文件讀出_序列化與反序列化的實(shí)例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
Jboss Marshalling服務(wù)端無法接受消息
這篇文章主要介紹了Jboss Marshalling服務(wù)端無法接受消息,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03
使用SpringMVC返回json字符串的實(shí)例講解
下面小編就為大家分享一篇使用SpringMVC返回json字符串的實(shí)例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03

