The backend provides services, usually returns the json string, but in some scenarios, it may be necessary to directly return the binary stream, such as an image editing interface, hoping to directly return the image stream to the frontend. What can be done at this time?
The main use is the HttpServletResponse object, and the case is implemented as follows
@RequestMapping(value = {"/img/render"}, method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})@CrossOrigin(origins = "*")@ResponseBodypublic String execute(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { // img is the binary stream of the image byte[] img = xxx; httpServletResponse.setContentType("image/png"); OutputStream os = httpServletResponse.getOutputStream(); os.write(img); os.flush(); os.close(); return "success";}Things to note
Generally speaking, the service interface provided by a backend often returns json data. As mentioned earlier, the scene of directly returning pictures, so what are the common ways to return pictures?
So how should a Controller we provide support for the above three usage postures at the same time?
Because there are several different ways to return, as for which one to choose, of course it is specified by the front-end, so you can define a bean object that requests parameters.
@Datapublic class BaseRequest { private static final long serialVersionUID = 1146303518394712013L; /** * Output image method: * * url : http address (default method) * base64 : base64 encoding * stream : Direct return image * */ private String outType; /** * Return image type* jpg | png | webp | gif */ private String mediaType; public ReturnTypeEnum returnType() { return ReturnTypeEnum.getEnum(outType); } public MediaTypeEnum mediaType() { return MediaTypeEnum.getEnum(mediaType); }}In order to simplify judgment, two annotations were defined, one ReturnTypeEnum and the other MediaTypeEnum. Of course, the necessity is not particularly great. The following is the definition of both.
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; }} @Datapublic 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; }}The above is the bean encapsulated with the request parameter. Of course, there is also a corresponding bean to return.
@Datapublic class BaseResponse { /** * Return the relative path of the image*/ private String path; /** * Return the https format of the image*/ private String url; /** * Image in base64 format*/ private String base;}illustrate:
In the actual project environment, request parameters and return will definitely not be as simple as above, so you can implement it by inheriting the above bean or defining the corresponding format yourself.
Since the goal is clear, packaging is the clearest step in this
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 { // Upload to the image server and replace String path = UploadUtil.upload(bytes); if (StringUtils.isBlank(path)) { // Upload failed throw new InternalError(null); } response.setPath(path); response.setUrl(CdnUtil.img(path)); } catch (IOException e) { // cdn exception log.error("upload to cdn error! e:{}", e); throw new CDNUploadError(e.getMessage()); }}// Return base64private void base64(byte[] bytes, BaseResponse response) { String base = Base64.getEncoder().encodeToString(bytes); response.setBase(base);}// Return the binary image 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); } }}illustrate:
Please ignore the above custom exception methods. When you need to use them, you can completely eliminate these custom exceptions; here I briefly talk about why this custom exception method is used in actual projects, mainly because of the following advantages
In conjunction with global exception capture (ControllerAdvie), it is very convenient and simple to use
All exceptions are handled centrally to facilitate information statistics and alarm
For example, after performing an exception count in a unified place, and then exceeding a certain threshold, call the person in charge, so there is no need to actively bury the spot in each place where an exception case occurs.
Avoid layer-by-layer transmission of error status codes
- This is mainly for web services. Generally, it contains the corresponding error status code and error message in the returned json string.
- Exception case may appear anywhere. In order to maintain this exception information, either the data is passed to the controller layer by layer; or it is found in ThreadLocal; obviously neither of these methods is convenient to use
Of course, there are disadvantages:
Exception method, additional performance overhead, so in custom exceptions, I have covered the following method, don't have a complete stack
@Overridepublic synchronized Throwable fillInStackTrace() { return this;}Some people may not like this method of coding habits
It doesn't seem interesting to just say that you don't practice. The above design is fully reflected in the open source project Quick-Media that I have been maintaining. Of course, there are some differences from the above. After all, it is more related to the business. If you are interested, you can refer to it.
QuickMedia: https://github.com/liuyueyi/quick-media:
BaseAction: com.hust.hui.quickmedia.web.wxapi.WxBaseAction#buildReturn
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.