首页 > 编程 > Java > 正文

SpringBoot中的内容协商器图解

2019-11-26 10:48:56
字体:
来源:转载
供稿:网友

背景

使用了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

使用参数 现在很多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化!

总结

以上所述是小编给大家介绍的SpringBoot中的内容协商器图解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对武林网网站的支持!

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表