溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringBoot中的內(nèi)容協(xié)商器圖解

發(fā)布時(shí)間:2020-09-22 21:50:02 來(lái)源:腳本之家 閱讀:176 作者:波波維奇 欄目:編程語(yǔ)言

背景

使用了restful的小伙伴對(duì)于導(dǎo)出這些需求本能就是拒絕的~破壞了restful的url的一致性【嚴(yán)格矯正 不是http json就是restful 很多小伙伴都會(huì)吧暴露出一個(gè)json就直接稱為restful 】

正如上文的代碼生成器 我們會(huì)批量生成一堆代碼 其中絕大部分都是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 = "新建實(shí)體", notes = "")
    public Result add(@RequestBody V vo) {
      service.saveSelective(vo);
      return ResultGenerator.genSuccessResult();
    }
    @DeleteMapping("/{id}")
    @ApiOperation(value = "刪除實(shí)體", notes = "")
    public Result delete(@PathVariable PK id) {
      service.deleteById(id);
      return ResultGenerator.genSuccessResult();
    }
    @PutMapping
    @ApiOperation(value = "更新實(shí)體", notes = "")
    public Result update(@RequestBody V vo) {
      service.updateByPrimaryKeySelective(vo);
      return ResultGenerator.genSuccessResult();
    }
    @GetMapping
    @ApiOperation(value = "獲取實(shí)體列表", 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, "數(shù)據(jù)");
      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 = "獲取單個(gè)實(shí)體", notes = "")
    public Result detail(@PathVariable PK id) {
      V vo = service.findById(id);
      return ResultGenerator.genSuccessResult(vo);
    }
    @DeleteMapping("/batch")
    @ApiOperation(value = "批量刪除實(shí)體", notes = "")
    public Result batchDelete(@RequestParam String ids) {
      service.deleteByIds(ids);
      return ResultGenerator.genSuccessResult();
    }
    @GetMapping("/batch")
    @ApiOperation(value = "批量獲取實(shí)體", notes = "")
    public Result batchDetail(@RequestParam String ids) {
      List<V> vos = service.findByIds(ids);
      return ResultGenerator.genSuccessResult(vos);
    }
    @PostMapping("/batch")
    @ApiOperation(value = "批量新建實(shí)體", notes = "")
    public Result add(@RequestBody List<V> vos) {
      service.save(vos);
      return ResultGenerator.genSuccessResult();
    }
    @GetMapping("/count")
    @ApiOperation(value = "獲取實(shí)體數(shù)目", notes = "")
    public Result count(@RequestBody V v) {
      int count = service.selectCount(v);
      return ResultGenerator.genSuccessResult(count);
    }

那么導(dǎo)出如何做呢?【其實(shí)可以理解成導(dǎo)出就是數(shù)據(jù)的展示 不過(guò)此時(shí)結(jié)果不是json而已】

拋出一個(gè)問(wèn)題那么登錄登出呢?傳統(tǒng)的方案都是login logout 那么換成restful資源的思路是啥呢?

提示: 登錄就是session的新建 登出就是session的刪除

實(shí)現(xiàn)

基于上述思路 我們自然就想到了那么我們只需要對(duì)同一個(gè)url返回多種結(jié)果不就OK了?【pdf一個(gè)版本 json一個(gè)版本 xml一個(gè)版本 xls一個(gè)版本】

bingo!這個(gè)是內(nèi)容協(xié)商器的由來(lái)

內(nèi)容協(xié)商器并不是Spring創(chuàng)造出來(lái)的 事實(shí)上這個(gè)從http頭里面也能看出

SpringBoot中的內(nèi)容協(xié)商器圖解

1.比如給英語(yǔ)客戶返回英語(yǔ)頁(yè)面 過(guò)于客戶返回漢語(yǔ)頁(yè)面

HTTP 協(xié)議中定義了質(zhì)量值(簡(jiǎn)稱 q 值),允許客戶端為每種偏好類別列出多種選項(xiàng),并為每種偏好選項(xiàng)關(guān)聯(lián)一個(gè)優(yōu)先次序。

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 是優(yōu)先級(jí)最低的,而 1.0 是優(yōu)先級(jí)最高的)。

注意,偏好的排列順序并不重要,只有與偏好相關(guān)的 q 值才是重要的

2.那么還有其他的一些參數(shù) 比如 accept-header

通常是先內(nèi)容協(xié)商器有如下幾種方案

1.使用Accept header:

這一種為教科書(shū)中通常描述的一種,理想中這種方式也是最好的,但如果你的資源要給用戶直接通過(guò)瀏覽器訪問(wèn)(即html展現(xiàn)),那么由于瀏覽器的差異,發(fā)送上來(lái)的Accept Header頭將是不一樣的. 將導(dǎo)致服務(wù)器不知要返回什么格式的數(shù)據(jù)給你. 下面是瀏覽器的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.使用擴(kuò)展名

喪失了同一url多種展現(xiàn)的方式,但現(xiàn)在這種在實(shí)際環(huán)境中是使用最多的.因?yàn)楦臃铣绦騿T的審美觀.

比如/user.json /user.xls /user.xml

使用參數(shù) 現(xiàn)在很多open API是使用這種方式,比如淘寶

但是對(duì)于不同瀏覽器可能accept-header并不是特別統(tǒng)一 因此許多實(shí)現(xiàn)選擇了2 3兩種方案

我們?cè)赟pring中采用上述兩種方案

首先配置內(nèi)容協(xié)商器

代碼

@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);
  }

創(chuàng)建對(duì)應(yīng)的轉(zhuǎn)換器

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

直接使用easy-poi導(dǎo)出數(shù)據(jù)

  /*
   * 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);
    }
  }

暫時(shí)只是針對(duì)導(dǎo)出 因此在使用的時(shí)候如下

 @GetMapping
  @ApiOperation(value = "獲取實(shí)體列表", 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, "數(shù)據(jù)");
    ExcelExportParam<V> param = new ExcelExportParam<>();
    param.setClazz(voClazz);
    param.setExcelExport(ExcelExport.NormalExcel);
    param.setExportParams(ep);
    param.setFileName("文件.xls");
    F6Static.setExcelExportParam(param);
  }

當(dāng)我們?cè)L問(wèn)時(shí)如下

http://127.0.0.1:8079/zeus/user

SpringBoot中的內(nèi)容協(xié)商器圖解

 {
  "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"
  }

當(dāng)訪問(wèn)http://127.0.0.1:8079/zeus/user?format=xls 或者h(yuǎn)ttp://127.0.0.1:8079/zeus/user.xls

如下效果

SpringBoot中的內(nèi)容協(xié)商器圖解

SpringBoot中的內(nèi)容協(xié)商器圖解

由于這邊的數(shù)據(jù)和查詢有關(guān) 因此我們可以這樣操作http://127.0.0.1:8079/zeus/user.xls?pageSize=1000 輕而易舉實(shí)現(xiàn)了查詢結(jié)果xls化!

SpringBoot中的內(nèi)容協(xié)商器圖解

SpringBoot中的內(nèi)容協(xié)商器圖解

總結(jié)

以上所述是小編給大家介紹的SpringBoot中的內(nèi)容協(xié)商器圖解,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI