SpringBoot中的內(nèi)容協(xié)商器圖解
背景
使用了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頭里面也能看出

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

{
"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 或者http://127.0.0.1:8079/zeus/user.xls
如下效果


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


總結(jié)
以上所述是小編給大家介紹的SpringBoot中的內(nèi)容協(xié)商器圖解,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Springboot+WebSocket實(shí)現(xiàn)在線聊天功能
WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。這篇文章主要為大家介紹了如何利用Springboot和WebSocket實(shí)現(xiàn)在線聊天功能,感興趣的小伙伴可以了解一下2023-02-02
spring cloud gateway中redis一直打印重連日志問(wèn)題及解決
這篇文章主要介紹了spring cloud gateway中redis一直打印重連日志問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
springboot實(shí)現(xiàn)基于aop的切面日志
這篇文章主要為大家詳細(xì)介紹了springboot實(shí)現(xiàn)基于aop的切面日志,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
Jedis操作Redis數(shù)據(jù)庫(kù)的方法
這篇文章主要為大家詳細(xì)介紹了Jedis操作Redis數(shù)據(jù)庫(kù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
java String類常量池分析及"equals"和"==”區(qū)別詳細(xì)介紹
這篇文章主要介紹了java String類常量池分析及"equals"和"==”區(qū)別詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-12-12

