抱歉,你查看的文章不存在

SpringBoot之内容协商器

背景

使用了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 Headerchrome: 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化!

内容协商器图解

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

后端之路

0 篇文章0 人订阅

相关文章

来自专栏码匠的流水账

聊聊eureka的delta配置

eureka-client-1.8.8-sources.jar!/com/netflix/discovery/DiscoveryClient.java

961
来自专栏阿杜的世界

Spring Boot:定制type Formatters

考虑到PropertyEditor的无状态和非线程安全特性,Spring 3增加了一个Formatter接口来替代它。Formatters提供和Property...

793
来自专栏Jerry的SAP技术分享

ABAP和Java的destination和JNDI

3613
来自专栏进击的程序猿

orm 系列 之 常用设计模式 The Repository Pattern

本文是orm系列的第一篇,内容来自github上的一个Markdown,清晰的讲述了一些数据库设计上常用的设计模式,并且阐述了orm是什么?

3613
来自专栏为数不多的Android技巧

Android 插件化原理解析——Hook机制之AMS&PMS

在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook;插件框架通过AOP实现了插件使用和开发的透明性。在讲述Dr...

1311
来自专栏Lambda

编程规范

领域层–编码规范 2018年4月4日14:10:38 Controller层编写规范 controller层只是负责从service层获得数据,对外暴露API接...

3646
来自专栏后端云

resize失败原因调查

对一个vm做resize,即从一个小的flavor换一个大的flavor,没有成功

1413
来自专栏JAVA后端开发

JAVA实现编写平台代码生成器

[项目中经常写CRUD,但实际这些工作,我觉得如果有一个完整的代码规范,完全可以自动生成,加快开发效率. 代码生成器技术原理不复杂,一般就是写好一个模板生成一...

9022
来自专栏JackieZheng

学习SpringMVC——你们要的REST风格的CRUD来了

  来来来,让一下,客官,您要的REST清蒸CRUD来了,火候刚刚好,不油不腻,请慢用~~~   如果说前面是准备调料,洗菜,切菜,摆盘,那么今天就来完整的上道...

34410
来自专栏老码农专栏

Actframework依赖注入 II - 注入对象类型

1103

扫码关注云+社区

领取腾讯云代金券