SpringBoot之内容协商器

作者: 3c69b7c624d9 | 来源:发表于2017-11-28 22:26 被阅读47次

    背景

    使用了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, */*
        
    
    1. 使用扩展名

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

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

    2. 使用参数 现在很多open API是使用这种方式,比如淘宝 114415_WSkT_871390.png114415_WSkT_871390.png

    但是对于不同浏览器可能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

    114555_jUQu_871390.png114555_jUQu_871390.png
    
        {
        "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

    如下效果

    114746_7ue1_871390.png114746_7ue1_871390.png 114800_fAcF_871390.png114800_fAcF_871390.png

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

    114823_Pbgq_871390.png114823_Pbgq_871390.png 114833_ArRc_871390.png114833_ArRc_871390.png

    内容协商器图解

    相关文章

      网友评论

        本文标题:SpringBoot之内容协商器

        本文链接:https://www.haomeiwen.com/subject/twrybxtx.html