SpringMVC之HttpMessageConverter 集成Proto3

package cn.nhorizon.ads.server.web.support;

import cn.nhorizon.ads.server.domain.OupengAdsProtos.MaterialMeta.VideoClickMode;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.google.common.collect.Lists;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
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.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.FileCopyUtils;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Support Proto3
 */
public class ProtobufHttpMessageConverter extends AbstractHttpMessageConverter<Message> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private static final MediaType PROTOBUF;
    private static final ConcurrentHashMap<Class<?>, Method> METHOD_CACHE;

    static {
        PROTOBUF = new MediaType("application", "x-protobuf", DEFAULT_CHARSET);
        METHOD_CACHE = new ConcurrentHashMap<>();
    }

    public ProtobufHttpMessageConverter() {
        super(PROTOBUF, MediaType.APPLICATION_JSON);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return Message.class.isAssignableFrom(clazz);
    }

    @Override
    protected MediaType getDefaultContentType(Message message) {
        return PROTOBUF;
    }

    @Override
    protected Message readInternal(Class<? extends Message> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        MediaType contentType = inputMessage.getHeaders().getContentType();
        if (contentType == null) {
            contentType = PROTOBUF;
        }

        Charset charset = contentType.getCharSet();
        if (charset == null) {
            charset = DEFAULT_CHARSET;
        }

        try {
            Message.Builder ex = getMessageBuilder(clazz);
            InputStreamReader reader;
            if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
                reader = new InputStreamReader(inputMessage.getBody(), charset);
                JsonFormat.parser()
                        .ignoringUnknownFields()
                        .merge(reader, ex);
            } else {
                ex.mergeFrom(inputMessage.getBody());
            }
            return ex.build();
        } catch (Exception var7) {
            throw new HttpMessageNotReadableException("Could not read Protobuf message: " + var7.getMessage(), var7);
        }
    }

    @Override
    protected boolean canWrite(MediaType mediaType) {
        return super.canWrite(mediaType);
    }

    @Override
    protected void writeInternal(Message message, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        MediaType contentType = outputMessage.getHeaders().getContentType();
        if (contentType == null) {
            contentType = this.getDefaultContentType(message);
        }

        Charset charset = contentType.getCharSet();
        if (charset == null) {
            charset = DEFAULT_CHARSET;
        }

        OutputStreamWriter outputStreamWriter;
        if (MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
            outputStreamWriter = new OutputStreamWriter(outputMessage.getBody(), charset);
            String result =
                JsonFormat.printer()
                    .includingDefaultValueFields()
                    .preservingProtoFieldNames()
                    .omittingInsignificantWhitespace()
                    .print(message);
            outputStreamWriter.write(formatJsonString(result));
            outputStreamWriter.flush();
        } else if (PROTOBUF.isCompatibleWith(contentType)) {
            FileCopyUtils.copy(message.toByteArray(), outputMessage.getBody());
        }

    }

    private static Message.Builder getMessageBuilder(Class<? extends Message> clazz) throws Exception {
        Method method = METHOD_CACHE.get(clazz);
        if (method == null) {
            method = clazz.getMethod("newBuilder", new Class[0]);
            METHOD_CACHE.put(clazz, method);
        }
        return (Message.Builder) method.invoke(clazz, new Object[0]);
    }

    /*在response中需要保留的字段名*/
    private static final List<String> usableFieldNames = Lists.newArrayList("error_code");
    /*在response中需要删除的字段*/
    private static final Map<String,String> unusableFields = new HashMap<String,String>(){
        {put("video_click_mode",VideoClickMode.UNDEFINE.name());}
    };

    /**
     * 过滤掉响应中的一些空字段(或使用proto默认值的字段)
     * <p>1.枚举类型字段,响应中都是有意义的值,如果使用枚举默认值则删除</p>
     * <p>2.字符串/数组类型字段,为空则过滤掉</p>
     * <p>3.数字类型字段,除fieldNames定义的字段外值为0的则过滤掉</p>
     */
    private static String formatJsonString(String input) {
        PropertyFilter filter = (Object o, String name, Object value)
            -> {
            boolean usable = true;

            if (value instanceof JSONArray && ((JSONArray) value).isEmpty()) {
                usable = false;
            } else if (value instanceof Number
                && !usableFieldNames.contains(name)
                && ((Number) value).doubleValue() == NumberUtils.DOUBLE_ZERO) {
                usable = false;
            } else if (value instanceof String && StringUtils.isBlank((String) value)) {
                usable = false;
            } else if (unusableFields.containsKey(name)
                && Objects.equals(unusableFields.get(name), value)) {
                usable = false;
            }
            return usable;
        };

        return JSON.toJSONString(JSON.parseObject(input), filter);
    }
}

后面别忘记在配置文件中加上:

<mvc:message-converters register-defaults="true">
    <bean class="cn.nhorizon.ads.server.web.support.ProtobufHttpMessageConverter"/>
</mvc:message-converters>

参考:https://blog.csdn.net/zmx729618/article/details/53034420

猜你喜欢

转载自blog.csdn.net/qq_38572383/article/details/81363622