您好,登錄后才能下訂單哦!
后端提供服務(wù),通常返回的json串,但是某些場景下可能需要直接返回二進(jìn)制流,如一個(gè)圖片編輯接口,希望直接將圖片流返回給前端,此時(shí)可以怎么處理?
主要借助的是 HttpServletResponse這個(gè)對象,實(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)
一般來說,一個(gè)后端提供的服務(wù)接口,往往是返回json數(shù)據(jù)的居多,前面提到了直接返回圖片的場景,那么常見的返回圖片有哪些方式呢?
那么我們提供的一個(gè)Controller,應(yīng)該如何同時(shí)支持上面這三種使用姿勢呢?
因?yàn)橛袔追N不同的返回方式,至于該選擇哪一個(gè),當(dāng)然是由前端來指定了,所以,可以定義一個(gè)請求參數(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); } }
為了簡化判斷,定義了兩個(gè)注解,一個(gè)ReturnTypeEnum, 一個(gè) 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)然也有一個(gè)對應(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)
既然目標(biāo)明確,封裝可算是這個(gè)里面最清晰的一個(gè)步驟了
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); } } }
說明:
請無視上面的幾個(gè)自定義異常方式,需要使用時(shí),完全可以干掉這些自定義異常即可;這里簡單說一下,為什么會在實(shí)際項(xiàng)目中使用這種自定義異常的方式,主要是有以下幾個(gè)優(yōu)點(diǎn)
配合全局異常捕獲(ControllerAdvie),使用起來非常方便簡單
所有的異常集中處理,方便信息統(tǒng)計(jì)和報(bào)警
如,在統(tǒng)一的地方進(jìn)行異常計(jì)數(shù),然后超過某個(gè)閥值之后,報(bào)警給負(fù)責(zé)人,這樣就不需要在每個(gè)出現(xiàn)異常case的地方來主動埋點(diǎn)了
避免錯誤狀態(tài)碼的層層傳遞
- 這個(gè)主要針對web服務(wù),一般是在返回的json串中,會包含對應(yīng)的錯誤狀態(tài)碼,錯誤信息
- 而異常case是可能出現(xiàn)在任何地方的,為了保持這個(gè)異常信息,要么將這些數(shù)據(jù)層層傳遞到controller;要么就是存在ThreadLocal中;顯然這兩種方式都沒有拋異常的使用方便
有優(yōu)點(diǎn)當(dāng)然就有缺點(diǎn)了:
異常方式,額外的性能開銷,所以在自定義異常中,我都覆蓋了下面這個(gè)方法,不要完整的堆棧
@Override public synchronized Throwable fillInStackTrace() { return this; }
編碼習(xí)慣問題,有些人可能就非常不喜歡這種使用方式
只說不練好像沒什么意思,上面的這個(gè)設(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í)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。