SpringBoot之内容协商器


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1577808,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

背景

使用了restful的小伙伴对于导出这些需求本能就是拒绝的~破坏了restful的url的一致性【严格矫正 不是http json就是restful 很多小伙伴都会吧暴露出一个json就直接称为restful 】

正如上文的代码生成器 我们会批量生成一堆代码 其中绝大部分都是RestController

public abstract class AbstractRestController<V extends Vo, S extends So, PK extends Serializable> {       protected Class<V> voClazz;     @Autowired     private Service<V, S, PK> service;       public AbstractRestController() {         TypeToken<V> voType = new TypeToken<V>(getClass()) {         };         voClazz = (Class<V>) voType.getRawType();     }       @PostMapping()     @ApiOperation(value = "新建实体", notes = "")     public Result add(@RequestBody V vo) {         service.saveSelective(vo);         return ResultGenerator.genSuccessResult();     }       @DeleteMapping("/{id}")     @ApiOperation(value = "删除实体", notes = "")     public Result delete(@PathVariable PK id) {         service.deleteById(id);         return ResultGenerator.genSuccessResult();     }         @PutMapping     @ApiOperation(value = "更新实体", notes = "")     public Result update(@RequestBody V vo) {         service.updateByPrimaryKeySelective(vo);         return ResultGenerator.genSuccessResult();     }       @GetMapping     @ApiOperation(value = "获取实体列表", notes = "")     public Result list(S so) {         PageHelper.startPage(so.getCurrentPage(), so.getPageSize());         List<V> list = service.findAll();         PageInfo pageInfo = new PageInfo(list);         excelExportParam();         return ResultGenerator.genSuccessResult(pageInfo);     }       protected void excelExportParam() {         ExportParams ep = new ExportParams(null, "数据");         ExcelExportParam<V> param = new ExcelExportParam<>();         param.setClazz(voClazz);         param.setExcelExport(ExcelExport.NormalExcel);         param.setExportParams(ep);         param.setFileName("文件.xls");         F6Static.setExcelExportParam(param);     }       @GetMapping("/{id}")     @ApiOperation(value = "获取单个实体", notes = "")     public Result detail(@PathVariable PK id) {           V vo = service.findById(id);         return ResultGenerator.genSuccessResult(vo);     }       @DeleteMapping("/batch")     @ApiOperation(value = "批量删除实体", notes = "")     public Result batchDelete(@RequestParam String ids) {         service.deleteByIds(ids);         return ResultGenerator.genSuccessResult();     }       @GetMapping("/batch")     @ApiOperation(value = "批量获取实体", notes = "")     public Result batchDetail(@RequestParam String ids) {         List<V> vos = service.findByIds(ids);         return ResultGenerator.genSuccessResult(vos);     }       @PostMapping("/batch")     @ApiOperation(value = "批量新建实体", notes = "")     public Result add(@RequestBody List<V> vos) {         service.save(vos);         return ResultGenerator.genSuccessResult();     }         @GetMapping("/count")     @ApiOperation(value = "获取实体数目", notes = "")     public Result count(@RequestBody V v) {         int count = service.selectCount(v);         return ResultGenerator.genSuccessResult(count);     }

那么导出如何做呢?【其实可以理解成导出就是数据的展示 不过此时结果不是json而已】

抛出一个问题那么登录登出呢?传统的方案都是login logout 那么换成restful资源的思路是啥呢?

提示: 登录就是session的新建 登出就是session的删除

实现

基于上述思路 我们自然就想到了那么我们只需要对同一个url返回多种结果不就OK了?【pdf一个版本 json一个版本 xml一个版本 xls一个版本】

bingo!这个是内容协商器的由来

内容协商器并不是Spring创造出来的 事实上这个从http头里面也能看出

  1. 比如给英语客户返回英语页面 过于客户返回汉语页面 
    HTTP 协议中定义了质量值(简称 q 值),允许客户端为每种偏好类别列出多种选项,并为每种偏好选项关联一个优先次序。 
    Accept-Language: en;q=0.5, fr;q=0.0, nl;q=1.0, tr;q=0.0 
    其中 q 值的范围从 0.0 ~ 1.0(0.0 是优先级最低的,而 1.0 是优先级最高的)。 
    注意,偏好的排列顺序并不重要,只有与偏好相关的 q 值才是重要的
  2. 那么还有其他的一些参数 比如 accept-header

通常是先内容协商器有如下几种方案

  1. 使用Accept header:
     这一种为教科书中通常描述的一种,理想中这种方式也是最好的,但如果你的资源要给用户直接通过浏览器访问(即html展现),那么由于浏览器的差异,发送上来的Accept Header头将是不一样的. 将导致服务器不知要返回什么格式的数据给你. 下面是浏览器的Accept Header
    chrome:  Accept:application/xml,application/xhtml+xml,textml;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5    firefox: Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8      IE8:  Accept:image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/x-silverlight, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, */* 

     

  2. 使用扩展名

    丧失了同一url多种展现的方式,但现在这种在实际环境中是使用最多的.因为更加符合程序员的审美观.

    比如/user.json /user.xls /user.xml 
  3. 使用参数 现在很多open API是使用这种方式,比如淘宝

但是对于不同浏览器可能accept-header并不是特别统一 因此许多实现选择了2 3两种方案

我们在Spring中采用上述两种方案

首先配置内容协商器

代码

@Bean public ViewResolver contentNegotiatingViewResolver(         ContentNegotiationManager manager) {     // Define the view resolvers     ViewResolver beanNameViewResolver = new BeanNameViewResolver();     List<ViewResolver> resolvers = Lists.newArrayList(beanNameViewResolver);         ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();     resolver.setViewResolvers(resolvers);     resolver.setContentNegotiationManager(manager);     return resolver; }   @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {     configurer.favorPathExtension(true)             .useJaf(false)             .favorParameter(true)             .parameterName("format")             .ignoreAcceptHeader(true)             .defaultContentType(MediaType.APPLICATION_JSON)             .mediaType("json", MediaType.APPLICATION_JSON)             .mediaType("xls", EXCEL_MEDIA_TYPE); }

创建对应的转换器

private HttpMessageConverter<Object> createExcelHttpMessageConverter() {     ExcelHttpMessageConverter excelHttpMessageConverter = new ExcelHttpMessageConverter();     return excelHttpMessageConverter; }

直接使用easy-poi导出数据

/*  * Copyright (c) 2017. Lorem ipsum dolor sit amet, consectetur adipiscing elit.  * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.  * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.  * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.  * Vestibulum commodo. Ut rhoncus gravida arcu.  */   package com.f6car.base.web.converter;   import cn.afterturn.easypoi.excel.ExcelExportUtil; import com.f6car.base.common.Result; import com.f6car.base.core.ExcelExport; import com.f6car.base.core.ExcelExportParam; import com.github.pagehelper.PageInfo; import com.google.common.collect.Lists; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException;   import java.io.IOException; import java.lang.reflect.Type; import java.net.URLEncoder; import java.util.Collection; import java.util.Collections; import java.util.Map;   import static com.f6car.base.core.F6Static.getExcelExportParam;   /**  * @author qixiaobo  */ public class ExcelHttpMessageConverter extends AbstractHttpMessageConverter<Object>         implements GenericHttpMessageConverter<Object> {     public static final MediaType EXCEL_MEDIA_TYPE = new MediaType("application", "vnd.ms-excel");       public ExcelHttpMessageConverter() {         super(EXCEL_MEDIA_TYPE);     }       @Override     protected boolean supports(Class<?> clazz) {         return false;     }       @Override     protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {         return null;     }       @Override     protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {         HttpHeaders headers = outputMessage.getHeaders();         Collection data = getActualData((Result) o);         ExcelExportParam excelExportParam = getExcelExportParam();         Workbook workbook;         switch (excelExportParam.getExcelExport()) {             case NormalExcel:                 workbook = ExcelExportUtil.exportExcel(                         excelExportParam.getExportParams(),                         (Class<?>) excelExportParam.getClazz(),                         (Collection<?>) data);                 break;             case MapExcel:                 workbook = ExcelExportUtil.exportExcel(                         excelExportParam.getExportParams(),                         excelExportParam.getExcelExportEntities(),                         (Collection<? extends Map<?, ?>>) data);                 break;             case BigExcel:             case MapExcelGraph:             case PDFTemplate:             case TemplateExcel:             case TemplateWord:             default:                 throw new RuntimeException();         }         if (workbook != null) {             if (excelExportParam.getFileName() != null) {                 String codedFileName = URLEncoder.encode(excelExportParam.getFileName(), "UTF8");                 headers.setContentDispositionFormData("attachment", codedFileName);             }             workbook.write(outputMessage.getBody());         }         }       private Collection getActualData(Result r) {         if (r != null && r.getData() != null) {             Object data = r.getData();             if (data instanceof PageInfo) {                 return ((PageInfo) data).getList();             } else if (!(data instanceof Collection)) {                 data = Lists.newArrayList(data);             } else {                 return (Collection) data;             }         }         return Collections.emptyList();         }       @Override     public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {         //不支持excel         return false;     }       @Override     public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {         return null;     }       @Override     public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {         return super.canWrite(mediaType) && clazz == Result.class && support();     }         private boolean support() {         ExcelExportParam param = getExcelExportParam();         if (param == null || param.getExcelExport() == null || param.getExportParams() == null) {             return false;         }         if (param.getExcelExport() == ExcelExport.NormalExcel) {             return true;         } else {             logger.warn(param.getExcelExport() + " not supprot now!");             return false;         }       }       @Override     public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {         super.write(o, contentType, outputMessage);     } }

暂时只是针对导出 因此在使用的时候如下

@GetMapping @ApiOperation(value = "获取实体列表", notes = "") public Result list(S so) {     PageHelper.startPage(so.getCurrentPage(), so.getPageSize());     List<V> list = service.findAll();     PageInfo pageInfo = new PageInfo(list);     excelExportParam();     return ResultGenerator.genSuccessResult(pageInfo); } protected void excelExportParam() {     ExportParams ep = new ExportParams(null, "数据");     ExcelExportParam<V> param = new ExcelExportParam<>();     param.setClazz(voClazz);     param.setExcelExport(ExcelExport.NormalExcel);     param.setExportParams(ep);     param.setFileName("文件.xls");     F6Static.setExcelExportParam(param); }

当我们访问时如下

http://127.0.0.1:8079/zeus/user

{ "code": 200, "data": { "endRow": 10, "firstPage": 1, "hasNextPage": true, "hasPreviousPage": false, "isFirstPage": true, "isLastPage": false, "lastPage": 8, "list": [ { "cellPhone": "13857445502", "idEmployee": 24201883434352650, "idOwnOrg": 23993199378825296, "idRole": 88, "idWxbStation": "332", "idWxbUser": "207", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 23993199378825296, "username": "lingweiqiche" }, { "cellPhone": "", "idEmployee": 0, "idOwnOrg": 9999, "idRole": 4, "idWxbStation": "", "idWxbUser": "", "isAdmin": 0, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434356532, "username": "007" }, { "cellPhone": "15715139000", "idEmployee": 24351585207523460, "idOwnOrg": 24201883434357600, "idRole": 89, "idWxbStation": "540", "idWxbUser": "298", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434357600, "username": "15715139000" }, { "cellPhone": "", "idEmployee": 0, "idOwnOrg": 24201883434357600, "idRole": 216, "idWxbStation": "", "idWxbUser": "", "isAdmin": 0, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434357920, "username": "sunlingli" }, { "cellPhone": "", "idEmployee": 24351585207425676, "idOwnOrg": 24201883434359384, "idRole": 90, "idWxbStation": "348", "idWxbUser": "227", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "opzUDs_v13WE500kxYMj6Xg_gFeE", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434359388, "username": "15952920979" }, { "cellPhone": "", "idEmployee": 0, "idOwnOrg": 24201883434359790, "idRole": 91, "idWxbStation": "315", "idWxbUser": "175", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434359790, "username": "13809056211" }, { "cellPhone": "18903885585", "idEmployee": 24201883434366164, "idOwnOrg": 24201883434359890, "idRole": 92, "idWxbStation": "317", "idWxbUser": "178", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434359892, "username": "18903885585" }, { "cellPhone": "", "idEmployee": 24351585207425668, "idOwnOrg": 24201883434359924, "idRole": 93, "idWxbStation": "318", "idWxbUser": "179", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434359930, "username": "13372299595" }, { "cellPhone": "", "idEmployee": 0, "idOwnOrg": 24201883434360052, "idRole": 94, "idWxbStation": "321", "idWxbUser": "188", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434360052, "username": "15221250005" }, { "cellPhone": "", "idEmployee": 0, "idOwnOrg": 24201883434360070, "idRole": 95, "idWxbStation": "325", "idWxbUser": "198", "isAdmin": 1, "isDel": 0, "isGuideOpen": 0, "limitMac": 0, "openid": "", "password": "96e79218965eb72c92a549dd5a330112", "pkId": 24201883434360070, "username": "13837251167" } ], "navigateFirstPage": 1, "navigateLastPage": 8, "navigatePages": 8, "navigatepageNums": [ 1, 2, 3, 4, 5, 6, 7, 8 ], "nextPage": 2, "orderBy": "", "pageNum": 1, "pageSize": 10, "pages": 102, "prePage": 0, "size": 10, "startRow": 1, "total": 1012 }, "message": "SUCCESS" }

当访问http://127.0.0.1:8079/zeus/user?format=xls 或者http://127.0.0.1:8079/zeus/user.xls

如下效果

 

由于这边的数据和查询有关 因此我们可以这样操作http://127.0.0.1:8079/zeus/user.xls?pageSize=1000 轻而易举实现了查询结果xls化!

内容协商器图解

本文发表于2017年11月23日 14:33
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1577808,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2454 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1