Overwrite Jackson2Json decoder and encoder (#2145)

pull/2150/head
John Niang 2022-06-09 16:04:13 +08:00 committed by GitHub
parent 02a7143fe5
commit a956758a2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 10 deletions

View File

@ -1,10 +1,14 @@
package run.halo.app.config; package run.halo.app.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List; import java.util.List;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.CodecConfigurer; import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer;
@ -16,6 +20,12 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@EnableWebFlux @EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer { public class WebFluxConfig implements WebFluxConfigurer {
final ObjectMapper objectMapper;
public WebFluxConfig(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Bean @Bean
ServerResponse.Context context(CodecConfigurer codec, ServerResponse.Context context(CodecConfigurer codec,
ViewResolutionResultHandler resultHandler) { ViewResolutionResultHandler resultHandler) {
@ -33,4 +43,13 @@ public class WebFluxConfig implements WebFluxConfigurer {
} }
}; };
} }
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// we need to customize the Jackson2Json[Decoder][Encoder] here to serialize and
// deserialize special types, e.g.: Instant, LocalDateTime. So we use ObjectMapper
// created by outside.
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper));
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper));
}
} }

View File

@ -73,10 +73,14 @@ public class WebServerSecurityConfig {
} }
@Bean @Bean
@Order(0)
SecurityWebFilterChain webFilterChain(ServerHttpSecurity http) { SecurityWebFilterChain webFilterChain(ServerHttpSecurity http) {
http.authorizeExchange( http.authorizeExchange(exchanges -> exchanges.pathMatchers(
exchanges -> exchanges.pathMatchers("/v3/api-docs/**", "/v3/api-docs.yaml", "/v3/api-docs/**",
"/swagger-ui/**", "/swagger-ui.html", "/webjars/**").permitAll()) "/v3/api-docs.yaml",
"/swagger-ui/**",
"/swagger-ui.html",
"/webjars/**").permitAll())
.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated()) .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
.cors(withDefaults()) .cors(withDefaults())
.httpBasic(withDefaults()) .httpBasic(withDefaults())

View File

@ -1,8 +1,6 @@
server: server:
port: 8090 port: 8090
spring: spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
output: output:
ansi: ansi:
enabled: always enabled: always

View File

@ -1,11 +1,9 @@
server: server:
port: 8090 port: 8090
spring: spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
output: output:
ansi: ansi:
enabled: always enabled: detect
datasource: datasource:
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource

View File

@ -0,0 +1,94 @@
package run.halo.app.config;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
@SpringBootTest
@AutoConfigureWebTestClient
@Import(ServerCodecTest.TestConfig.class)
class ServerCodecTest {
static final String INSTANT = "2022-06-09T10:57:30Z";
static final String LOCAL_DATE_TIME = "2022-06-10T10:57:30";
@Autowired
WebTestClient webClient;
@Test
@WithMockUser
void timeSerializationTest() {
webClient.get().uri("/fake/api/times")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.instant").value(equalTo(INSTANT))
.jsonPath("$.localDateTime").value(equalTo(LOCAL_DATE_TIME))
;
}
@Test
@WithMockUser
void timeDeserializationTest() {
webClient
.mutateWith(csrf())
.post().uri("/fake/api/time/report")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(Map.of("now", Instant.parse(INSTANT)))
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody(new ParameterizedTypeReference<Map<String, Instant>>() {
}).isEqualTo(Map.of("now", Instant.parse(INSTANT)))
;
}
@TestConfiguration(proxyBeanMethods = false)
static class TestConfig {
@Bean
RouterFunction<ServerResponse> timesRouter() {
return route().GET("/fake/api/times", request -> {
var times = Map.of("instant", Instant.parse(INSTANT),
"localDateTime", LocalDateTime.parse(LOCAL_DATE_TIME));
return ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(times);
}).build();
}
@Bean
RouterFunction<ServerResponse> reportTime() {
final var type = new ParameterizedTypeReference<Map<String, Instant>>() {
};
return route().POST("/fake/api/time/report",
contentType(MediaType.APPLICATION_JSON).and(accept(MediaType.APPLICATION_JSON)),
request -> ServerResponse.ok()
.body(request.bodyToMono(type), type))
.build();
}
}
}

View File

@ -1,8 +1,6 @@
server: server:
port: 8090 port: 8090
spring: spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
output: output:
ansi: ansi:
enabled: detect enabled: detect