spring api接口返回数据优化 —— 只返回需要的字段数据

简介:

概述

spring/spring boot 返回的json数据,通常包含了对象所有的字段,有时候浪费流量。例如一个接口有10个字段,而前端只需要2个字段,都返回会浪费流量。
解决方案:前端在header中传递需要包含或需要排除的字段;后端在返回数据前进行统一拦截,只返回需要的字段。
具有有多种实现方式(这里只提供spring boot)。

首先约定返回的BaseResult对象格式如下,里面result属性就是实际各种数据对象。

{
    "ret":0,
    "msg":null,
    "result":{
        "id":1,
        "name":"后摄像头53"
    },
    "time":1540972430498
}

实现方式一:通过AOP controller来实现

aop实现步骤说明:

  • 判断返回的是不是BaseResult对象
  • 判断request header或params是否有x-include-fields、x-exclude-fields属性(有则取出来放入set中)
  • 满足以上条件则对BaseResult.result 对象进行处理,用Map替换result对象,Map只返回需要的字段。如果是Array或Collection则每个Item替换成一个Map。

import com.cehome.cloudbox.common.object.BaseResult;
import com.cehome.cloudbox.common.object.ItemsResult;
import com.cehome.cloudbox.common.object.PageResult;
import com.cehome.cloudbox.common.page.Page;
import com.cehomex.spring.feign.FeignRequestHolder;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.beans.PropertyDescriptor;
import java.util.*;

@Aspect
public class ControllerAOP {
    private static String INCLUDE_FIELDS = "x-include-fields";
    private static String EXCLUDE_FIELDS = "x-exclude-fields";
    private static String P_INCLUDE_FIELDS = "x-include-fields";
    private static String P_EXCLUDE_FIELDS = "x-exclude-fields";

    private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);

    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void controller() {
    }

    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void restController() {
    }

    @Around("(controller() || restController()) && execution(public * *(..))")
    public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {

        try {

            Object object=joinPoint.proceed();
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes();
            if (requestAttributes != null) {
                HttpServletRequest request = requestAttributes.getRequest();
                handleReturnValue(object,request);
            }
            return object;
        } finally {

            FeignRequestHolder.removeAll();
        }


    }

    /**
     * 返回前端需要的字段
     * @param o
     * @param request
     * @throws Exception
     */
    public void handleReturnValue(Object o,HttpServletRequest request) throws Exception {
        if(!isSuccess(o)) return;

        //HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        //HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
        String fields1 = StringUtils.trimToEmpty(request.getHeader(INCLUDE_FIELDS));
        if(fields1.length()==0) fields1 = StringUtils.trimToEmpty(request.getParameter(P_INCLUDE_FIELDS));
        String fields2 = StringUtils.trimToEmpty(request.getHeader(EXCLUDE_FIELDS));
        if(fields2.length()==0) fields2 = StringUtils.trimToEmpty(request.getParameter(P_EXCLUDE_FIELDS));
        if (fields1.length() > 0 || fields2.length() > 0) {
            Set<String> includes = fields1.length() == 0 ? new HashSet<>() : new HashSet<>(Arrays.asList(fields1.split(",")));
            Set<String> excludes = fields2.length() == 0 ? new HashSet<>() : new HashSet<>(Arrays.asList(fields2.split(",")));

            if (o instanceof BaseResult) {
                BaseResult result = (BaseResult) o;
                Object object = result.getResult();
                result.setResult(convertResult(object, includes, excludes));

            } else if (o instanceof ItemsResult) {
                ItemsResult result = (ItemsResult) o;
                Object object = result.getItems();
                result.setItems(convertResult(object, includes, excludes));

            } else if (o instanceof PageResult) {
                PageResult result = (PageResult) o;
                Object object = result.getPage();
                if (object instanceof Page) {
                    Page page=(Page) object;
                    List datas = page.getDatas();
                    page.setDatas((List)convertResult(datas, includes, excludes));

                }

            }

        }


    }

    private boolean isSuccess(Object object){
        if(object==null) return false;
        if (object instanceof BaseResult) return ( (BaseResult) object).isSuccess();
        if (object instanceof ItemsResult) return ( (ItemsResult) object).isSuccess();
        if (object instanceof PageResult) return ( (PageResult) object).isSuccess();
        return false;
    }
    /*private void handleObject(Object object, Set<String> includes, Set<String> excludes) throws Exception {

        PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(object);
        for (PropertyDescriptor pd : pds) {
            String name = pd.getName();
            if (name.equals("class")) {
                continue;
            }
            if (excludes.contains(name) || !includes.contains(name)) {
                PropertyUtils.setProperty(object, name, null);
            }
        }
    }*/

    /**
     * convert objects to maps
     * @param object
     * @param includes
     * @param excludes
     * @return
     * @throws Exception
     */
    private Object convertResult(Object object, Set<String> includes, Set<String> excludes) throws Exception{
        if (object instanceof Object[]) {
            Object[] objects = (Object[]) object;
            return convertArray(objects,includes,excludes);
        } else if (object instanceof Collection) {
            Collection collection = (Collection) object;
            return convertCollection(collection,includes,excludes);
        }else{
            return convertObject(object,includes,excludes);
        }
    }

    private Collection<Map> convertCollection(Collection collection, Set<String> includes, Set<String> excludes) throws Exception{
        Collection<Map> result=new ArrayList<>();
        for (Object item : collection) {
            result.add(convertObject(item,includes,excludes));
        }
        return result;
    }
    private Map[] convertArray(Object[] objects, Set<String> includes, Set<String> excludes)  throws Exception{
        Map[] result=new HashMap[objects.length];
        for(int i=0;i<objects.length;i++){
            result[i]=convertObject(objects[i],includes,excludes);
        }

        return result;
    }

    /**
     * convert object to map
     * @param object  input
     * @param includes  include props
     * @param excludes  exclude props
     * @return
     * @throws Exception
     */
    private Map convertObject(Object object, Set<String> includes, Set<String> excludes) throws Exception {

        Map<Object,Object> result=new HashMap<>();
        if(!(object instanceof Map)) {
            PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(object);
            for (PropertyDescriptor pd : pds) {
                String name = pd.getName();
                if (name.equals("class")) {
                    continue;
                }
                if(!excludes.isEmpty() && excludes.contains(name)){
                    continue;
                }
                if(!includes.isEmpty() && !includes.contains(name)){
                    continue;
                }
                result.put(name,PropertyUtils.getProperty(object, name));
            }
        }else {
            Map<Object,Object>  map=(Map<Object,Object>) object;

            for(Map.Entry<Object,Object>  entry :map.entrySet()){
                String name= entry.getKey()==null?"":entry.getKey().toString();
                if(!excludes.isEmpty() && excludes.contains(name)){
                    continue;
                }
                if(!includes.isEmpty() && !includes.contains(name)){
                    continue;
                }
                result.put(entry.getKey(),entry.getValue());

            }
        }

        return result;


    }



}

用Map替换的方式改变了原来的对象,还有一种效率更好的不改变对象的方式,
就是把不需要返回的字段设为null,然后配置一个JSON处理bean,统一过滤null的字段。这种方式null的字段就不再返回前端,需要前端做些兼容。

    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return objectMapper;
    }

实现方式二:自定义HttpMessageConverter来实现

spring boot缺省包含了好几个消息转换器,根据返回媒体类型进行匹配,第一个匹配上就忽略掉其它的了。
MappingJackson2HttpMessageConverter 是其处理JSON的消息转换器。

  • 所以,需要先删除缺省的MappingJackson2HttpMessageConverter
  • 继承MappingJackson2HttpMessageConverter,实现自定义的消息转换。


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        //-- 移除缺省的JSON处理器
        for (int i = converters.size() - 1; i >= 0; i--) {
            HttpMessageConverter<?> messageConverter = converters.get(i);
            if (messageConverter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter)
                converters.remove(i);
        }

        // --  添加自己得JSON处理器

        MappingJackson2HttpMessageConverter c = new MappingJackson2HttpMessageConverter() {
            @Override
            protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
                
                //-- 例子一: 转成fastjson对象,然后替换name字段
                JSONObject json = (JSONObject) JSON.toJSON(object);
                json.getJSONObject("result").put("name", "coolma");
                super.writeInternal(json, type, outputMessage);
               
                //-- 例子二: 用过滤器只保留name字段,其它字段不要。
                //注意,例子二需要给BaseResult对象的result属性加上com.fasterxml.jackson.annotation.JsonFilter注解:
                // @JsonFilter("result") 
                // private  T result;
                MappingJacksonValue value = new MappingJacksonValue(object);
                value.setFilters(new SimpleFilterProvider().addFilter("result",
                        SimpleBeanPropertyFilter.filterOutAllExcept("name")));
                super.writeInternal(value, type, outputMessage);
            }

        };
        c.setDefaultCharset(Charset.forName("UTF-8"));
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        c.setSupportedMediaTypes(mediaTypes);
        converters.add(c);

    }
}
目录
相关文章
|
2天前
|
JSON Shell API
如何通过API获取淘宝商品月销售数据
淘宝开放平台提供了丰富的API接口,允许开发者获取商品的详细信息,包括月销售数据。
|
1天前
|
数据采集 JSON Java
Java爬虫获取微店快递费用item_fee API接口数据实现
本文介绍如何使用Java开发爬虫程序,通过微店API接口获取商品快递费用(item_fee)数据。主要内容包括:微店API接口的使用方法、Java爬虫技术背景、需求分析和技术选型。具体实现步骤为:发送HTTP请求获取数据、解析JSON格式的响应并提取快递费用信息,最后将结果存储到本地文件中。文中还提供了完整的代码示例,并提醒开发者注意授权令牌、接口频率限制及数据合法性等问题。
|
4天前
|
XML 数据挖掘 API
1688商品详情数据示例参考,1688API接口系列
在成长的路上,我们都是同行者。这篇关于详情API接口的文章,希望能帮助到您。期待与您继续分享更多API接口的知识,请记得关注Anzexi58哦!
|
17天前
|
存储 NoSQL Java
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
60 21
|
12天前
|
数据采集 监控 搜索推荐
深度解析淘宝商品详情API接口:解锁电商数据新维度,驱动业务增长
淘宝商品详情API接口,是淘宝开放平台为第三方开发者提供的一套用于获取淘宝、天猫等电商平台商品详细信息的应用程序接口。该接口涵盖了商品的基本信息(如标题、价格、图片)、属性参数、库存状况、销量评价、物流信息等,是电商企业实现商品管理、市场分析、营销策略制定等功能的得力助手。
|
7天前
|
人工智能 数据挖掘 API
淘宝评论与商品详情数据API接口的使用与应用
商品详情数据API:获取商品的基本信息(如标题、价格、库存、描述等)。 评论数据API:获取商品的用户评价数据(如评分、评论内容、用户昵称、购买时间等)。
|
22天前
|
搜索推荐 API 开发者
深度解析:利用商品详情 API 接口实现数据获取与应用
在电商蓬勃发展的今天,数据成为驱动业务增长的核心。商品详情API接口作为连接海量商品数据的桥梁,帮助运营者、商家和开发者获取精准的商品信息(如价格、描述、图片、评价等),优化策略、提升用户体验。通过理解API概念、工作原理及不同平台特点,掌握获取权限、构建请求、处理响应和错误的方法,可以将数据应用于商品展示、数据分析、竞品分析和个性化推荐等场景,助力电商创新与发展。未来,随着技术进步,API接口将与人工智能、大数据深度融合,带来更多变革。
62 3
|
6天前
|
API PHP 开发者
速卖通商品详情接口(速卖通API系列)
速卖通(AliExpress)是阿里巴巴旗下的跨境电商平台,提供丰富的商品数据。通过速卖通开放平台(AliExpress Open API),开发者可获取商品详情、订单管理等数据。主要功能包括商品搜索、商品详情、订单管理和数据报告。商品详情接口aliexpress.affiliate.productdetail.get用于获取商品标题、价格、图片等详细信息。开发者需注册账号并创建应用以获取App Key和App Secret,使用PHP等语言调用API。该接口支持多种请求参数和返回字段,方便集成到各类电商应用中。
|
12天前
|
JSON API 数据格式
微店商品列表接口(微店 API 系列)
微店商品列表接口是微店API系列的一部分,帮助开发者获取店铺中的商品信息。首先需注册微店开发者账号并完成实名认证,选择合适的开发工具如PyCharm或VS Code,并确保熟悉HTTP协议和JSON格式。该接口支持GET/POST请求,主要参数包括店铺ID、页码、每页数量和商品状态等。响应数据为JSON格式,包含商品详细信息及状态码。Python示例代码展示了如何调用此接口。应用场景包括商品管理系统集成、数据分析、多平台数据同步及商品展示推广。
|
3天前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
25 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡

热门文章

最新文章