当前位置:   article > 正文

源码分析SpringBoot自定义Jackson序列化,默认null值个性化处理返回值_springboot json返回默认null

springboot json返回默认null

源码分析SpringBoot自定义Jackson序列化,默认null值个性化处理返回值

最近项目要实现一种需求,对于后端返回给前端的json格式的一种规范,不允许缺少字段和字段值都为null,所以琢磨了一下如何进行将springboot的Jackson序列化自定义一下,先看看如何实现,再去看源码

第一步:写配置类

@Configuration
public class WebConfiguration  extends WebMvcConfigurationSupport {
@Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.stream().filter(c -> c instanceof MappingJackson2HttpMessageConverter)
                .map(c ->(MappingJackson2HttpMessageConverter)c)
                .forEach(c->{
                    ObjectMapper mapper = c.getObjectMapper();
                    // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
                    mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
                    c.setObjectMapper(mapper);
                });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

第二步:编写值为null时的自定义序列化

/**
 * @title: MyBeanSerializerModifier
 * @Author junyu
 * 旧巷里有一个穿着白衬衫笑起来如太阳般温暖我的少年。
 * 记忆里有一个穿着连衣裙哭起来如孩子般讨人喜的女孩。
 * 他说,哪年树弯了腰,人见了老,桃花落了白发梢,他讲的笑话她还会笑,那便是好。
 * 她说,哪年国改了号,坟长了草,地府过了奈何桥,她回头看时他还在瞧,就不算糟。
 * @Date: 2020/9/12 16:44
 * @Version 1.0
 */
public class MyBeanSerializerModifier extends BeanSerializerModifier {

    private MyNullStringJsonSerializer myNullStringJsonSerializer;
    private MyNullArrayJsonSerializer MyNullArrayJsonSerializer;
    private MyNullObjectJsonSerializer MyNullObjectJsonSerializer;
    private MyNullJsonSerializer myNullJsonSerializer;

    public MyBeanSerializerModifier(){
        myNullStringJsonSerializer = new MyNullStringJsonSerializer();
        MyNullArrayJsonSerializer = new MyNullArrayJsonSerializer();
        MyNullObjectJsonSerializer =  new MyNullObjectJsonSerializer();
        myNullJsonSerializer = new MyNullJsonSerializer();
    }

    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
                                                     List<BeanPropertyWriter> beanProperties) {
        // 循环所有的beanPropertyWriter
        beanProperties.forEach(writer ->{
            // 判断字段的类型
            if (isArrayType(writer)) {
                //给writer注册一个自己的nullSerializer
                writer.assignNullSerializer(MyNullArrayJsonSerializer);
            } else if (isObjectType(writer)) {
                writer.assignNullSerializer(MyNullObjectJsonSerializer);
            } else if (isStringType(writer)) {
                writer.assignNullSerializer(myNullStringJsonSerializer);
            } else if (isPrimitiveType(writer)) {
                writer.assignNullSerializer(myNullJsonSerializer);
            }
        });
        return beanProperties;
    }

    // 判断是否是boolean类型
    private boolean isPrimitiveType(BeanPropertyWriter writer) {
        Class<?> clazz = writer.getType().getRawClass();
        return clazz.isPrimitive();
    }

    // 判断是否是string类型
    private boolean isStringType(BeanPropertyWriter writer) {
        Class<?> clazz = writer.getType().getRawClass();
        return clazz.equals(String.class);
    }

    // 判断是否是对象类型
    private boolean isObjectType(BeanPropertyWriter writer) {
        Class<?> clazz = writer.getType().getRawClass();
        return !clazz.isPrimitive() && !clazz.equals(String.class)
                && clazz.isAssignableFrom(Object.class);
    }
    // 判断是否是集合类型
    protected boolean isArrayType(BeanPropertyWriter writer) {
        Class<?> clazz = writer.getType().getRawClass();
        return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class);
    }

    class MyNullJsonSerializer extends JsonSerializer<Object>{

        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                jgen.writeNull();
            }
        }
    }


    class MyNullStringJsonSerializer extends JsonSerializer<Object>{

        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                jgen.writeString(StringUtils.EMPTY);
            }
        }
    }

    class MyNullArrayJsonSerializer extends JsonSerializer<Object>{

        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                jgen.writeStartArray();
                jgen.writeEndArray();
            }
        }
    }

    class MyNullObjectJsonSerializer extends JsonSerializer<Object>{

        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                jgen.writeStartObject();
                jgen.writeEndObject();
            }
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

这样基本配置就完事了,现在可以试试效果了,自己定义一个bean用来返回,定义一个简单的controller去接受访问就行了,博主就不进行写这两个类了。返回结果如下

img

这是我的项目需求需要实现的,大家可以根据的自己的需求去改写MyBeanSerializerModifier这个类。还有另一种实现方式:不继承

@Configuration
public class WebConfiguration {

@Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();
        mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
        mappingJackson2HttpMessageConverter.setObjectMapper(mapper);
        return mappingJackson2HttpMessageConverter;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这种方法也是可以设置成功的,主要是不是继承了WebMvcConfigurationSupport类,毕竟这个类有很多可以自定义的方法,用起来顺手而已。

第一个问题:为什么继承WebMvcConfigurationSupport后,要重写extendMessageConverters方法;

第二个问题:为什么继承WebMvcConfigurationSupport后,再去生成@Bean的MappingJackson2HttpMessageConverter,却不生效;

第三个问题:为什么不继承WebMvcConfigurationSupport时,生成@Bean的MappingJackson2HttpMessageConverter是生效的;

这几个问题,都需要我们进入源码观察,废活不多说,我们来进入源码的世界。解决问题之前必须搞清楚在哪里进行了序列化。

第一步:我们要弄清楚在哪里进行的Jackson序列化,看这里https://www.processon.com/embed/5f5c6464f346fb7afd55448b,从返回请求开始的序列化基本流程就在这里了,虽然图有点low,但是清楚的记录的每一步,我们主要看一下下面的源码

/*
    /**********************************************************
    /* Field serialization methods
    /**********************************************************
     */
    //序列化每一个字段
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
            throws IOException
    {
        final BeanPropertyWriter[] props;
        if (_filteredProps != null && provider.getActiveView() != null) {
            props = _filteredProps;
        } else {
            props = _props;
        }
        int i = 0;
        try {
            for (final int len = props.length; i < len; ++i) {
                BeanPropertyWriter prop = props[i];
                if (prop != null) { // can have nulls in filtered list
                    //关键就在这一步进行的序列化,而为什么BeanPropertyWriter是数组,我们一会解释
                    prop.serializeAsField(bean, gen, provider);
                }
            }
            if (_anyGetterWriter != null) {
                _anyGetterWriter.getAndSerialize(bean, gen, provider);
            }
        } catch (Exception e) {
            String name = (i == props.length) ? "[anySetter]" : props[i].getName();
            wrapAndThrow(provider, e, bean, name);
        } catch (StackOverflowError e) {
            // 04-Sep-2009, tatu: Dealing with this is tricky, since we don't have many
            //   stack frames to spare... just one or two; can't make many calls.

            // 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly:
            //JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e);
            JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);

            String name = (i == props.length) ? "[anySetter]" : props[i].getName();
            mapE.prependPath(new JsonMappingException.Reference(bean, name));
            throw mapE;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

既然已经找到了在哪里要进行序列化,那我们看看是如何实现的:

/**
     * Method called to access property that this bean stands for, from within
     * given bean, and to serialize it as a JSON Object field using appropriate
     * serializer.
     */
    @Override
    public void serializeAsField(Object bean, JsonGenerator gen,
                                 SerializerProvider prov) throws Exception {
        // inlined 'get()'
        final Object value = (_accessorMethod == null) ? _field.get(bean)
                : _accessorMethod.invoke(bean, (Object[]) null);

        // Null handling is bit different, check that first
        if (value == null) {
            //看到这里大家应该就知道null值是如何进行序列化 的了,如果不配置的话,默认是返回null
            //因为_nullSerializer是有默认值的,大家看一看这个类的初始化
            //那我们要是改一下_nullSerializer的这个默认类,让每一个字段调用我们自己的_nullSerializer不就可以了吗,
            //yes、我们就这么干
            if (_nullSerializer != null) {
                gen.writeFieldName(_name);
                _nullSerializer.serialize(null, gen, prov);
            }
            return;
        }
        // then find serializer to use
        JsonSerializer<Object> ser = _serializer;
        if (ser == null) {
            Class<?> cls = value.getClass();
            PropertySerializerMap m = _dynamicSerializers;
            ser = m.serializerFor(cls);
            if (ser == null) {
                ser = _findAndAddDynamic(m, cls, prov);
            }
        }
        // and then see if we must suppress certain values (default, empty)
        if (_suppressableValue != null) {
            if (MARKER_FOR_EMPTY == _suppressableValue) {
                if (ser.isEmpty(prov, value)) {
                    return;
                }
            } else if (_suppressableValue.equals(value)) {
                return;
            }
        }
        // For non-nulls: simple check for direct cycles
        if (value == bean) {
            // three choices: exception; handled by call; or pass-through
            if (_handleSelfReference(bean, gen, prov, ser)) {
                return;
            }
        }
        gen.writeFieldName(_name);
        if (_typeSerializer == null) {
            ser.serialize(value, gen, prov);
        } else {
            ser.serializeWithType(value, gen, prov, _typeSerializer);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

那我们来解决第一个问题:为什么继承WebMvcConfigurationSupport后,要重写extendMessageConverters方法?

不知道大家记得不记得我们请求过来的时候,如果我们配置类集成了WebMvcConfigurationSupport类,dispatchservlet处理handle请求的ha,其实就是RequestMappingHandlerAdapter类,这个类是在WebMvcConfigurationSupport配置的,看源码:

@Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
            @Qualifier("mvcConversionService") FormattingConversionService conversionService,
            @Qualifier("mvcValidator") Validator validator) {

        RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
        adapter.setContentNegotiationManager(contentNegotiationManager);
        adapter.setMessageConverters(getMessageConverters());
        adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
        adapter.setCustomArgumentResolvers(getArgumentResolvers());
        adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

        if (jackson2Present) {
            adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
            adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
        }

        AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
        configureAsyncSupport(configurer);
        if (configurer.getTaskExecutor() != null) {
            adapter.setTaskExecutor(configurer.getTaskExecutor());
        }
        if (configurer.getTimeout() != null) {
            adapter.setAsyncRequestTimeout(configurer.getTimeout());
        }
        adapter.setCallableInterceptors(configurer.getCallableInterceptors());
        adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

        return adapter;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

adapter.setMessageConverters(getMessageConverters());当大家看到这个方法的时候,应该就会想到我们的默认jackson转换器:MappingJackson2HttpMessageConverter,我们看看这个getMessageConverters()有什么幺蛾子:

protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList<>();
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
1 protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
 2 
 3             //这些都不用管,跟我们的需求没啥作用,我们只看关键的部分,在下面
 4             messageConverters.add(new ByteArrayHttpMessageConverter());
 5             messageConverters.add(new StringHttpMessageConverter());
 6             messageConverters.add(new ResourceHttpMessageConverter());
 7             messageConverters.add(new ResourceRegionHttpMessageConverter());
 8             try {
 9                 messageConverters.add(new SourceHttpMessageConverter<>());
10             }
11             catch (Throwable ex) {
12                 // Ignore when no TransformerFactory implementation is available...
13             }
14             messageConverters.add(new AllEncompassingFormHttpMessageConverter());
15 
16             if (romePresent) {
17                 messageConverters.add(new AtomFeedHttpMessageConverter());
18                 messageConverters.add(new RssChannelHttpMessageConverter());
19             }
20 
21             if (jackson2XmlPresent) {
22                 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
23                 if (this.applicationContext != null) {
24                     builder.applicationContext(this.applicationContext);
25                 }
26                 messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
27             }
28             else if (jaxb2Present) {
29                 messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
30             }
31 
32             if (jackson2Present) {
33                 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
34                 if (this.applicationContext != null) {
35                     builder.applicationContext(this.applicationContext);
36                 }
37                 //解析我们返回值的转换器就是在这里生成的
38                 messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
39             }
40             else if (gsonPresent) {
41                 messageConverters.add(new GsonHttpMessageConverter());
42             }
43             else if (jsonbPresent) {
44                 messageConverters.add(new JsonbHttpMessageConverter());
45             }
46 
47             if (jackson2SmilePresent) {
48                 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
49                 if (this.applicationContext != null) {
50                     builder.applicationContext(this.applicationContext);
51                 }
52                 messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
53             }
54             if (jackson2CborPresent) {
55                 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
56                 if (this.applicationContext != null) {
57                     builder.applicationContext(this.applicationContext);
58                 }
59                 messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
60             }
61         }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

我们的MappingJackson2HttpMessageConverter类就是这里初始化的,初始化的时候默认的_nullSerializer也会被初始化,大家肯定说这已经初始化完了,该咋办,大家应该看到了extendMessageConverters(this.messageConverters);这个方法就是用来重写实现的了,这回知道我们继承WebMvcConfigurationSupport后,为什么要重写extendMessageConverters,我们的配置类遍历已经获取到的convert,然后对我们想要的转换器进行修改添加,那修改完了,是在哪里起作用的呢,我们再来看一看源码:

在序列化之前有一些方法是可以进行修改操作的,在调用writeWithMessageConverters方法的时候:

 1 protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
 2                                                       ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
 3                 throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
 4 
 5             .......
 6 
 7             MediaType selectedMediaType = null;
 8             MediaType contentType = outputMessage.getHeaders().getContentType();
 9             boolean isContentTypePreset = contentType != null && contentType.isConcrete();
10             if (isContentTypePreset) {
11                 if (logger.isDebugEnabled()) {
12                     logger.debug("Found 'Content-Type:" + contentType + "' in response");
13                 }
14                 selectedMediaType = contentType;
15             }
16             else {
17                 HttpServletRequest request = inputMessage.getServletRequest();
18                 List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
19                 //这里进行自定义操作修改MappingJackson2HttpMessageConverter
20                 List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
21 
22                 .......
23 
24             if (selectedMediaType != null) {
25                 selectedMediaType = selectedMediaType.removeQualityValue();
26                 //这这里进行选择我们的MappingJackson2HttpMessageConverter去自定义序列化
27                 for (HttpMessageConverter<?> converter : this.messageConverters) {
28                     GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
29                             (GenericHttpMessageConverter<?>) converter : null);
30                     if (genericConverter != null ?
31                             ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
32                             converter.canWrite(valueType, selectedMediaType)) {
33                         body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
34                                 (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
35                                 inputMessage, outputMessage);
36                         if (body != null) {
37                             Object theBody = body;
38                             LogFormatUtils.traceDebug(logger, traceOn ->
39                                     "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
40                             addContentDispositionHeader(inputMessage, outputMessage);
41                             if (genericConverter != null) {
42                                 genericConverter.write(body, targetType, selectedMediaType, outputMessage);
43                             }
44                             else {
45                                 ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
46                             }
47                         }
48                         else {
49                             if (logger.isDebugEnabled()) {
50                                 logger.debug("Nothing to write: null body");
51                             }
52                         }
53                         return;
54                     }
55                 }
56             }
57 
58             if (body != null) {
59                 Set<MediaType> producibleMediaTypes =
60                         (Set<MediaType>) inputMessage.getServletRequest()
61                                 .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
62 
63                 if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
64                     throw new HttpMessageNotWritableException(
65                             "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
66                 }
67                 throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
68             }
69         }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

[复制代码](javascript:void(0)声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/思考机器5/article/detail/61434

推荐阅读
相关标签