From 94f7dc0297ae38472aab363f240aa98ade004bdd Mon Sep 17 00:00:00 2001 From: AlexBob Date: Wed, 18 Sep 2024 14:33:51 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(`Menu.java`,=20`RedisConfigura?= =?UTF-8?q?tion.java`,=20`SessionConfiguration.java`,=20`R2dbcConfiguratio?= =?UTF-8?q?n.java`,=20`SecurityConfiguration.java`,=20`LoggerFilter.java`)?= =?UTF-8?q?:=20Enhance=20code=20documentation=20and=20refine=20security=20?= =?UTF-8?q?configurations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces comprehensive documentation comments for the `Menu` class, improving the understanding of its structure and methods. Additionally, it augments security configurations in `SecurityConfiguration.java` with detailed explanations for OAuth2 client services, session registry, password encoding, RSocket security, and CSRF protection customization. Furthermore, it refines Redis cache management in `RedisConfiguration.java` and enhances database connection setup in `R2dbcConfiguration.java`. Lastly, it bolsters request-response logging mechanics in `LoggerFilter.java` with clearer commentary. --- .../plate/boot/config/R2dbcConfiguration.java | 26 +++ .../plate/boot/config/RedisConfiguration.java | 24 +++ .../boot/config/SecurityConfiguration.java | 133 +++++++++++++ .../boot/config/SessionConfiguration.java | 2 +- .../plate/boot/relational/LoggerFilter.java | 175 +++++++++++++++++- .../com/plate/boot/relational/menus/Menu.java | 141 +++++++++++++- 6 files changed, 494 insertions(+), 7 deletions(-) diff --git a/boot/platform/src/main/java/com/plate/boot/config/R2dbcConfiguration.java b/boot/platform/src/main/java/com/plate/boot/config/R2dbcConfiguration.java index b633bd85..955cb4e6 100644 --- a/boot/platform/src/main/java/com/plate/boot/config/R2dbcConfiguration.java +++ b/boot/platform/src/main/java/com/plate/boot/config/R2dbcConfiguration.java @@ -28,18 +28,44 @@ @RequiredArgsConstructor public class R2dbcConfiguration extends AbstractR2dbcConfiguration { + /** + * A collection of custom converters used to adapt between various data types when interacting with the database. + * These converters facilitate the mapping of application-specific objects to database-compatible representations and vice versa. + */ private final List> customConverters; + /** + * Establishes and returns the configured R2DBC Connection Factory instance. + * This method is part of the configuration setup for R2DBC within the application, + * providing the necessary connection details to interact with the database in a reactive manner. + * + * @return A non-null instance of {@link ConnectionFactory} configured for R2DBC connectivity. + */ @Override public @NonNull ConnectionFactory connectionFactory() { return ConnectionFactories.get("r2dbc:.."); } + /** + * Retrieves a list of custom converters that have been configured for the application. + * These converters are typically used to customize data binding between Java objects and + * the database, enabling the application to handle specific data types or transformations. + * + * @return A non-null list containing instances of custom converters. The list is a fresh copy + * and modifications to it will not affect the original configuration. + */ @Override public @NonNull List getCustomConverters() { return Lists.newArrayList(customConverters); } + /** + * Provides a bean instance of {@link ReactiveAuditorAware} configured to use {@link UserAuditorAware}. + * This method sets up auditing awareness for reactive contexts, allowing the system to automatically + * populate audit fields like creator, updater, etc., with details from the current security context. + * + * @return A bean instance of {@link ReactiveAuditorAware} that supplies the current auditor details. + */ @Bean public ReactiveAuditorAware userAuditorProvider() { return new UserAuditorAware(); diff --git a/boot/platform/src/main/java/com/plate/boot/config/RedisConfiguration.java b/boot/platform/src/main/java/com/plate/boot/config/RedisConfiguration.java index c91da8cd..0b5a7886 100644 --- a/boot/platform/src/main/java/com/plate/boot/config/RedisConfiguration.java +++ b/boot/platform/src/main/java/com/plate/boot/config/RedisConfiguration.java @@ -28,6 +28,17 @@ @EnableCaching public class RedisConfiguration { + /** + * Customizes the RedisCacheManagerBuilder by setting up default serialization for keys and values. + * + *

This method configures the cache manager to use a StringRedisSerializer for serializing keys, + * ensuring they are stored as strings in Redis. For values, it utilizes a Jackson2JsonRedisSerializer, + * which allows any Java object to be serialized into JSON format before being saved into Redis, with the + * help of the provided ObjectMapper instance. + * + * @param objectMapper The ObjectMapper instance used to serialize and deserialize Java objects to and from JSON. + * @return A RedisCacheManagerBuilderCustomizer that applies the serialization settings to the cache builder. + */ @Bean public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer(ObjectMapper objectMapper) { return (builder) -> builder.cacheDefaults() @@ -37,6 +48,19 @@ public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer(O .fromSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, Object.class))); } + /** + * Creates and configures a {@link ReactiveRedisTemplate} for interacting with Redis in a reactive manner. + * This template is designed to work with string keys and arbitrary Java objects, serialized as JSON, + * enabling efficient storage and retrieval of complex data structures from a Redis data store. + * + * @param factory A {@link ReactiveRedisConnectionFactory} that provides the connection to the Redis server. + * This factory should be capable of supporting reactive operations. + * @param objectMapper An {@link ObjectMapper} instance used for serializing and deserializing Java objects + * to and from JSON. This is crucial for handling value serialization in a way that is + * compatible with the application's object model. + * @return A configured instance of {@link ReactiveRedisTemplate} ready to perform + * reactive Redis operations, with keys as strings and values as arbitrary Java objects (serialized as JSON). + */ @Bean public ReactiveRedisTemplate reactiveObjectRedisTemplate(ReactiveRedisConnectionFactory factory, ObjectMapper objectMapper) { diff --git a/boot/platform/src/main/java/com/plate/boot/config/SecurityConfiguration.java b/boot/platform/src/main/java/com/plate/boot/config/SecurityConfiguration.java index b6def842..06521cb4 100644 --- a/boot/platform/src/main/java/com/plate/boot/config/SecurityConfiguration.java +++ b/boot/platform/src/main/java/com/plate/boot/config/SecurityConfiguration.java @@ -66,6 +66,15 @@ public class SecurityConfiguration { private final Oauth2SuccessHandler authenticationSuccessHandler; + /** + * Configures and provides an instance of {@link ReactiveOAuth2AuthorizedClientService} which is primarily responsible + * for managing OAuth2 authorized client details, storing and retrieving them from a reactive data source via + * {@link DatabaseClient} and handling client registrations defined in {@link ReactiveClientRegistrationRepository}. + * + * @param databaseClient The reactive database client used for interacting with the data source to persist OAuth2 authorization data. + * @param clientRepository The repository containing the definitions of all registered clients for OAuth2 authentication. + * @return A configured instance of {@link R2dbcReactiveOAuth2AuthorizedClientService} capable of handling OAuth2 authorized client services within a reactive context. + */ @Bean @Primary public ReactiveOAuth2AuthorizedClientService oAuth2ClientService(DatabaseClient databaseClient, @@ -73,6 +82,18 @@ public ReactiveOAuth2AuthorizedClientService oAuth2ClientService(DatabaseClient return new R2dbcReactiveOAuth2AuthorizedClientService(databaseClient, clientRepository); } + /** + * Creates and configures a SpringSessionBackedReactiveSessionRegistry bean. + * This registry is designed to manage sessions within a reactive environment, backed by the provided + * ReactiveSessionRepository and ReactiveFindByIndexNameSessionRepository instances. + * + * @param The type of session extending the Session interface. + * @param sessionRepository A reactive session repository for storing and retrieving session data. + * @param indexedSessionRepository A reactive session repository capable of finding sessions by index name, + * enhancing session management capabilities. + * @return An instance of SpringSessionBackedReactiveSessionRegistry configured with the given repositories, + * ready to manage and provide session-related services in a reactive context. + */ @Bean public SpringSessionBackedReactiveSessionRegistry sessionRegistry( ReactiveSessionRepository sessionRepository, @@ -80,11 +101,26 @@ public SpringSessionBackedReactiveSessionRegistry session return new SpringSessionBackedReactiveSessionRegistry<>(sessionRepository, indexedSessionRepository); } + /** + * Provides a PasswordEncoder bean that utilizes a delegating strategy for encoding passwords. + * This delegating encoder can handle different password encoding schemes based on encoded passwords' ids. + * It consults a set of encoders and picks the one matching the id prepended to the encoded password. + * + * @return A {@link PasswordEncoder} instance which is capable of delegating to the appropriate password encoding strategy. + */ @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } + /** + * Configures and provides an RSocket interceptor for security purposes. + * This method sets up authorization policies for RSocket interactions, + * enforcing authentication requirements and permission rules. + * + * @param rsocket The RSocketSecurity configuration object used to define security rules. + * @return A configured PayloadSocketAcceptorInterceptor that enforces the defined security policies for RSocket communications. + */ @Bean public PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) { rsocket.authorizePayload(authorize -> @@ -97,6 +133,15 @@ public PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsock return rsocket.build(); } + /** + * Configures and returns a SecurityWebFilterChain bean for Spring Security's WebFlux setup. + * This method customizes various aspects of security including authorization rules, + * session management, basic authentication entry point, form login, CSRF protection, + * logout handling, and OAuth2 login support. + * + * @param http The ServerHttpSecurity builder used to configure web security settings. + * @return A configured SecurityWebFilterChain ready to be used in the application's security filter chain. + */ @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange(exchange -> { @@ -121,12 +166,33 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) return http.build(); } + /** + * Configures the Cross-Site Request Forgery (CSRF) protection specifications within the provided {@link ServerHttpSecurity.CsrfSpec}. + * This method customizes the CSRF behavior by: + *

    + *
  • Defining a matcher to ignore CSRF protection for certain endpoints.
  • + *
  • Setting a cookie-based repository for storing CSRF tokens with HTTP-only flag disabled.
  • + *
  • Registering a handler to place the CSRF token as a request attribute.
  • + *
+ * + * @param csrfSpec The CSRF specification object to be configured, part of the {@link ServerHttpSecurity} configuration. + */ private void setCsrfSpec(ServerHttpSecurity.CsrfSpec csrfSpec) { csrfSpec.requireCsrfProtectionMatcher(new IgnoreRequireCsrfProtectionMatcher()); csrfSpec.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()); csrfSpec.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler()); } + /** + * Configures the logout behavior for the server HTTP security. + * + * This method sets up the logout process by adding a security context logout handler, + * a handler to clear site data through headers, and delegates these handlers + * to manage the logout process. It also specifies the URL that triggers the logout + * action and defines the success handler to return a HTTP status upon successful logout. + * + * @param logout The LogoutSpec instance to configure logout specifics of ServerHttpSecurity. + */ private void setLogout(ServerHttpSecurity.LogoutSpec logout) { ServerLogoutHandler securityContext = new SecurityContextServerLogoutHandler(); ClearSiteDataServerHttpHeadersWriter writer = new ClearSiteDataServerHttpHeadersWriter( @@ -139,6 +205,13 @@ private void setLogout(ServerHttpSecurity.LogoutSpec logout) { logout.logoutSuccessHandler(new HttpStatusReturningServerLogoutSuccessHandler()); } + /** + * Matcher implementation to exclude specific HTTP requests from requiring CSRF protection. + * This is particularly useful for endpoints that are deemed safe from CSRF attacks due to their nature + * or implementation specifics, such as certain GET requests or known safe POST requests like OAuth2 token exchanges. + * The matcher combines a set of allowed HTTP methods and a list of more specific matchers to determine + * if CSRF protection should be ignored for a given request. + */ static class IgnoreRequireCsrfProtectionMatcher implements ServerWebExchangeMatcher { private final Set allowedMethods = new HashSet<>( Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS)); @@ -146,6 +219,17 @@ static class IgnoreRequireCsrfProtectionMatcher implements ServerWebExchangeMatc new PathPatternParserServerWebExchangeMatcher("/oauth2/none", HttpMethod.POST) ); + /** + * Evaluates whether a given server web exchange should be excluded from CSRF protection. + * It checks if the request's HTTP method is within the allowed set and if it matches any + * specified additional matchers. + * + * @param exchange The server web exchange to evaluate for CSRF protection exclusion. + * @return A Mono that emits a MatchResult indicating whether the exchange should be ignored for CSRF protection. + * - If the request method is allowed or matches custom criteria, emits MatchResult.notMatch(), + * suggesting the request should be ignored by CSRF protection. + * - Otherwise, emits MatchResult.match() indicating standard CSRF protection should apply. + */ @Override public Mono matches(ServerWebExchange exchange) { Mono ignoreMono = new OrServerWebExchangeMatcher(allowedMatchers) @@ -158,9 +242,34 @@ public Mono matches(ServerWebExchange exchange) { } } + /** + * CustomServerAuthenticationEntryPoint is a specialized entry point for handling authentication failures in a reactive server environment. + * It extends the HttpBasicServerAuthenticationEntryPoint to customize the behavior when an authentication exception occurs. + * Specifically, it differentiates between standard requests and XMLHttpRequests (AJAX calls) to provide appropriate responses. + * + *

This class overrides the commence method to log authentication failure details and customize the response based on the nature + * of the incoming request. If the request is identified as an AJAX call, it will return a JSON formatted error response with + * an UNAUTHORIZED status code. Otherwise, it falls back to the default behavior provided by its superclass.

+ * + *

Notable Methods:

+ *
    + *
  • {@link #commence(ServerWebExchange, AuthenticationException)}: Handles the commencement of the authentication failure handling.
  • + *
  • {@link #isXmlHttpRequest(String)}: Determines if the request is an XMLHttpRequest.
  • + *
  • {@link #handleXmlHttpRequestFailure(ServerWebExchange, AuthenticationException)}: Manages the response for failed AJAX requests.
  • + *
  • {@link #createErrorResponse(ServerWebExchange, AuthenticationException)}: Constructs an error response object for AJAX request failures.
  • + *
+ */ @Log4j2 static class CustomServerAuthenticationEntryPoint extends HttpBasicServerAuthenticationEntryPoint { + /** + * Handles the commencement of failure handling due to an authentication exception during a server web exchange. + * Determines the nature of the request and delegates to the appropriate failure handling method. + * + * @param exchange The current server web exchange containing the request and response objects. + * @param e The authentication exception that triggered this failure handling. + * @return A Mono that, when subscribed to, signals the completion of the failure handling, typically without a value (Void). + */ @Override public Mono commence(ServerWebExchange exchange, AuthenticationException e) { ServerHttpRequest request = exchange.getRequest(); @@ -173,10 +282,26 @@ public Mono commence(ServerWebExchange exchange, AuthenticationException e return super.commence(exchange, e); } + /** + * Determines whether the provided request header 'requestedWith' indicates an XMLHttpRequest. + * This is typically used to check if the request was made via AJAX. + * + * @param requestedWith The value of the 'X-Requested-With' header from the HTTP request. + * @return {@code true} if the 'requestedWith' header indicates an XMLHttpRequest, {@code false} otherwise. + */ private boolean isXmlHttpRequest(String requestedWith) { return requestedWith != null && requestedWith.contains(XML_HTTP_REQUEST); } + /** + * Handles the failure case of an XML HTTP request by setting the response status to UNAUTHORIZED, + * preparing an error response in JSON format, and sending it back to the client. + * + * @param exchange The current server web exchange containing the request and response objects. + * @param e The authentication exception that caused the request handling failure. + * @return A Mono that, when subscribed to, completes after writing the error response to the client + * and handling any potential errors during the write operation by releasing allocated resources. + */ private Mono handleXmlHttpRequestFailure(ServerWebExchange exchange, AuthenticationException e) { var response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); @@ -191,6 +316,14 @@ private Mono handleXmlHttpRequestFailure(ServerWebExchange exchange, Authe .doOnError((error) -> DataBufferUtils.release(bodyBuffer)); } + /** + * Constructs an error response object for authentication failures. + * + * @param exchange The ServerWebExchange associated with the request that triggered the error. + * @param e The AuthenticationException that caused the error response to be generated. + * @return An ErrorResponse instance representing the authentication failure, including details + * from the original request and the exception message. + */ private ErrorResponse createErrorResponse(ServerWebExchange exchange, AuthenticationException e) { return ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(), 401, "认证失败,检查你的用户名,密码是否正确或安全密钥是否过期!", List.of(e.getMessage())); diff --git a/boot/platform/src/main/java/com/plate/boot/config/SessionConfiguration.java b/boot/platform/src/main/java/com/plate/boot/config/SessionConfiguration.java index bac53c05..25e30d36 100644 --- a/boot/platform/src/main/java/com/plate/boot/config/SessionConfiguration.java +++ b/boot/platform/src/main/java/com/plate/boot/config/SessionConfiguration.java @@ -130,7 +130,7 @@ public void setSessionId(@NonNull ServerWebExchange exchange, @NonNull String id /** * Expires the session associated with the given server web exchange. - * + *

* This method utilizes the {@code cookieWebSessionIdResolver} to expire the session, * effectively invalidating any session data linked to the provided exchange. * diff --git a/boot/platform/src/main/java/com/plate/boot/relational/LoggerFilter.java b/boot/platform/src/main/java/com/plate/boot/relational/LoggerFilter.java index bdd5aaf9..0401b573 100644 --- a/boot/platform/src/main/java/com/plate/boot/relational/LoggerFilter.java +++ b/boot/platform/src/main/java/com/plate/boot/relational/LoggerFilter.java @@ -39,21 +39,79 @@ import static org.springframework.security.web.server.csrf.CsrfWebFilter.DEFAULT_CSRF_MATCHER; /** - * @author Alex bob(https://github.com/vnobo) + * LoggerFilter is a WebFilter designed to intercept and log HTTP request and response details. + * It caches the request and response bodies, decorates the requests and responses for logging purposes, + * and utilizes a LoggersService to persist operation logs with enriched content. + * The filter matches requests based on a predefined matcher (defaultLoggerMatcher) and can be bypassed + * if the match is not successful. + * + * Key Features: + * - Caches request and response bodies to ensure they can be logged even after consumption. + * - Matches requests to determine if logging should occur based on a ServerWebExchangeMatcher. + * - Processes and logs the HTTP method, status, path, headers, cookies, query parameters, and bodies. + * - Handles DataBuffer retention and release to prevent memory leaks. + * - Utilizes a separate service (LoggersService) to handle the logging operation asynchronously. + * - Supports tracing-level logging for debugging filter operations. + * + * Dependencies: + * - ServerWebExchangeMatcher for matching requests. + * - LoggersService for persisting log entries. + * - SLF4J Logger (log) for internal logging. + * + * Usage: + * Implemented as a Spring component (@Component), it's automatically registered in the WebFilter chain. + * Configuration may be required to customize the matching behavior or logging service interaction. */ @Log4j2 @Component @RequiredArgsConstructor public class LoggerFilter implements WebFilter { + /** + * Constants for the attribute key used to cache request body information. + * This string represents the attribute name under which the cached request body can be stored or retrieved in a context where attributes are managed. + */ public static final String CACHED_REQUEST_BODY_ATTR = "cachedRequestBody"; + /** + * Constant defining an attribute key for storing the cached response body. + * This can be used in scenarios where the response body needs to be temporarily stored, + * such as during request handling or caching purposes within an application. + */ public static final String CACHED_RESPONSE_BODY_ATTR = "cachedResponseBody"; + /** + * Constants for attribute key used to store cached ServerHttpRequest decorator. + * This string represents the attribute name under which a decorated ServerHttpRequest + * can be found within the attribute map of a request processing context. + */ public static final String CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR = "cachedServerHttpRequestDecorator"; + /** + * Constant defining an attribute key for the cached server HTTP response decorator. + * This attribute can be used to store or retrieve a decorated server HTTP response in a context where + * caching mechanisms or decorators are applied to server responses. + */ public static final String CACHED_SERVER_HTTP_RESPONSE_DECORATOR_ATTR = "cachedServerHttpResponseDecorator"; + /** + * The default logger matcher instance used to match server web exchange requests. + * This matcher is initialized with the DEFAULT_CSRF_MATCHER constant. + */ private final ServerWebExchangeMatcher defaultLoggerMatcher = DEFAULT_CSRF_MATCHER; + /** + * Represents a private final instance of the LoggersService class. + * This service is responsible for handling logging operations within the application, + * providing functionality to log messages at various levels of severity. + */ private final LoggersService loggerService; + /** + * Caches the request body of a ServerWebExchange and decorates the ServerHttpRequest + * to be used within the provided function, ensuring the request body can be consumed multiple times. + * + * @param The type of the result emitted by the decorated function. + * @param exchange The current ServerWebExchange containing the request and response. + * @param function A function that takes a ServerHttpRequest with a cached body and returns a Mono of T. + * @return A Mono emitting the result of applying the function to the request with the cached body. + */ public static Mono cacheRequestBody(ServerWebExchange exchange, Function> function) { ServerHttpResponse response = exchange.getResponse(); @@ -64,6 +122,15 @@ public static Mono cacheRequestBody(ServerWebExchange exchange, return requestDecorate.flatMap(function); } + /** + * Decorates the ServerHttpResponse to cache the response body and modify it when written. + * It stores an initial default message in case no response body is provided later. + * The decoration process involves intercepting the write operation to cache the actual response + * and restore a previous cached response if needed. + * + * @param exchange The current ServerWebExchange containing the response to be decorated. + * @return A ServerHttpResponseDecorator that overrides the writeWith method to manage caching of response bodies. + */ public static ServerHttpResponse responseDecorate(ServerWebExchange exchange) { var response = exchange.getResponse(); var factory = response.bufferFactory(); @@ -87,6 +154,16 @@ public static ServerHttpResponse responseDecorate(ServerWebExchange exchange) { return decorator; } + /** + * Decorates the provided ServerHttpRequest to cache its body content. + * This method is useful for scenarios where the request body needs to be read or processed + * multiple times without losing its original state. + * + * @param exchange The ServerWebExchange containing the current request and response. + * @param dataBuffer The DataBuffer representing the body of the request. + * @return A decorated ServerHttpRequest with the body cached for subsequent accesses. + * Returns null if the data buffer capacity is zero. + */ private static ServerHttpRequest requestDecorate(ServerWebExchange exchange, DataBuffer dataBuffer) { if (dataBuffer.capacity() > 0) { if (log.isTraceEnabled()) { @@ -113,6 +190,17 @@ private static ServerHttpRequest requestDecorate(ServerWebExchange exchange, Dat return decorator; } + /** + * Constructs a new DataBuffer based on the type of the input DataBuffer. + * This method supports conversion for NettyDataBuffer and DefaultDataBuffer instances, + * ensuring compatibility or preparing the buffer for operations that require a specific format. + * + * @param dataBuffer The source DataBuffer to be converted or wrapped. + * @return A new DataBuffer instance, either wrapped or converted from the input, + * maintaining the content of the original buffer. + * @throws IllegalArgumentException If the input DataBuffer is neither a NettyDataBuffer + * nor a DefaultDataBuffer, indicating an unsupported type. + */ private static DataBuffer buildDataBuffer(DataBuffer dataBuffer) { if (dataBuffer instanceof NettyDataBuffer pdb) { return pdb.factory().wrap(pdb.getNativeBuffer().retainedSlice()); @@ -124,6 +212,13 @@ private static DataBuffer buildDataBuffer(DataBuffer dataBuffer) { } } + /** + * Reads and parses the request body from the given server web exchange. + * + * @param exchange The ServerWebExchange containing the request information. + * @return A JsonNode representing the parsed request body. + * @throws JsonException if an IOException occurs during JSON parsing. + */ private JsonNode readRequestBody(ServerWebExchange exchange) { String bodyStr = exchange.getRequiredAttribute(CACHED_REQUEST_BODY_ATTR); try { @@ -133,6 +228,15 @@ private JsonNode readRequestBody(ServerWebExchange exchange) { } } + /** + * Filters the given server web exchange based on a matching condition and applies caching before continuing the filter chain. + * If the default logger matcher determines a match, it proceeds to cache the response and logs the request details using user details. + * If no match is found, it simply continues the filter chain without caching or logging. + * + * @param exchange The current server web exchange to be filtered. + * @param chain The filter chain to be invoked for further processing. + * @return A Mono that completes void when the filtering and optional caching/logging are finished. + */ @Override public @NonNull Mono filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) { var filterMono = defaultLoggerMatcher.matches(exchange); @@ -142,17 +246,45 @@ private JsonNode readRequestBody(ServerWebExchange exchange) { .doOnNext(userDetails -> logRequest(exchange, userDetails)).then()); } + /** + * Continues the filter chain by deferring to the next filter in the sequence. + * This method is typically used within a custom WebFilter implementation to ensure + * that the request processing flows correctly through the chain after some + * preliminary actions or checks have been performed. + * + * @param exchange The current server web exchange which holds information about + * the client request and provides a way to manipulate the response. + * @param chain The WebFilterChain representing the remaining filters to be applied. + * @return A Mono indicating the completion or error of the deferred filter operation. + * The completion signifies that the request has been processed by the next filter + * in line, or an error indicates that the processing failed at some point in the chain. + */ private Mono continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) { log.debug("{}Logger filter chain [continueFilterChain] next.", exchange.getLogPrefix()); return Mono.defer(() -> chain.filter(exchange)); } + /** + * Continues the filter chain after caching the request body. + * This method first caches the request body and then proceeds to execute the next filters in the chain. + * + * @param exchange The current server web exchange containing the request and response objects. + * @param chain The next filter chain to be executed after caching the request body. + * @return A Mono that completes when the filter chain execution is finished, or signals an error if any occurs. + */ private Mono cacheFilterChain(ServerWebExchange exchange, WebFilterChain chain) { log.debug("{}Logger filter chain [cacheRequestBody] next.", exchange.getLogPrefix()); return cacheRequestBody(exchange, serverHttpRequest -> processRequestBody(exchange, serverHttpRequest)) .then(Mono.defer(() -> processFilter(exchange, chain))); } + /** + * Processes the request body from the given ServerWebExchange and ServerHttpRequest. + * + * @param exchange The current server web exchange which holds information about the HTTP request and response. + * @param serverHttpRequest The actual HTTP request containing the body to be processed. + * @return A Mono that emits the processed request body as a String. + */ private Mono processRequestBody(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) { ServerHttpResponse cachedResponse = responseDecorate(exchange); ServerRequest serverRequest = ServerRequest.create(exchange.mutate() @@ -165,6 +297,13 @@ private Mono processRequestBody(ServerWebExchange exchange, ServerHttpRe }); } + /** + * Processes the filter chain after preparing the cached request and response decorators. + * + * @param exchange The current server web exchange that holds the request and response information. + * @param chain The filter chain to continue processing. + * @return A Mono that completes when the processing is done or empty if caching attributes are null. + */ private Mono processFilter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest cachedRequest = exchange.getAttribute(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); ServerHttpResponse cachedResponse = exchange.getAttribute(CACHED_SERVER_HTTP_RESPONSE_DECORATOR_ATTR); @@ -182,6 +321,13 @@ private Mono processFilter(ServerWebExchange exchange, WebFilterChain chai .doFinally(s -> releaseResources(exchange)); } + /** + * Releases the resources held by the cached request and response bodies in the given ServerWebExchange. + * This method retrieves the backup cached request body and response body from the exchange attributes + * and then calls the `releaseDataBuffer` method to free up the resources associated with these data buffers. + * + * @param exchange The ServerWebExchange context from which the cached bodies' resources will be released. + */ private void releaseResources(ServerWebExchange exchange) { Object backupCachedBody = exchange.getAttributes().get("cachedOriginalRequestBodyBackup"); Object backupCachedBody1 = exchange.getAttributes().get("cachedOriginalResponseBodyBackup"); @@ -190,6 +336,12 @@ private void releaseResources(ServerWebExchange exchange) { releaseDataBuffer(backupCachedBody1); } + /** + * Releases the given data buffer object if it is an instance of {@link DataBuffer}. + * This method ensures that the resources held by the data buffer are properly released. + * + * @param dataBufferObject The object that may be a DataBuffer instance to be released. + */ private void releaseDataBuffer(Object dataBufferObject) { if (dataBufferObject instanceof DataBuffer dataBuffer) { try { @@ -200,6 +352,20 @@ private void releaseDataBuffer(Object dataBufferObject) { } } + /** + * Logs the details of an HTTP request along with optional user details for security audit purposes. + * It extracts information from the provided ServerWebExchange including request and response headers, + * cookies, query parameters, body content, and response status code. The logged data is structured + * into an ObjectNode which is then used to create a LoggerRequest object. This method also associates + * the log entry with a tenant code extracted from the user details, if available. The logging process + * is asynchronous and the result is logged at the debug level with a prefixed log message indicating + * the operation performed. + * + * @param exchange The current server web exchange which holds both the incoming request and the + * outgoing response. + * @param userDetails Optional security details of the authenticated user, containing a username + * and a tenant code. If not provided, defaults are used. + */ private void logRequest(ServerWebExchange exchange, SecurityDetails userDetails) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); @@ -228,6 +394,13 @@ private void logRequest(ServerWebExchange exchange, SecurityDetails userDetails) exchange.getLogPrefix(), exchange.getRequest().getMethod().name(), res)); } + /** + * Reads and parses the response body from the given server web exchange into a JSON node. + * + * @param exchange The ServerWebExchange containing the response body to be read. + * @return A JsonNode representing the parsed response body. If the body is empty or parsing fails, an empty ObjectNode is returned. + * @throws JsonException If an IOException occurs during JSON parsing. + */ private JsonNode readResponseBody(ServerWebExchange exchange) { DataBuffer dataBuffer = exchange.getRequiredAttribute(CACHED_RESPONSE_BODY_ATTR); if (dataBuffer.capacity() < 2) { diff --git a/boot/platform/src/main/java/com/plate/boot/relational/menus/Menu.java b/boot/platform/src/main/java/com/plate/boot/relational/menus/Menu.java index e4dae4bf..89d71799 100644 --- a/boot/platform/src/main/java/com/plate/boot/relational/menus/Menu.java +++ b/boot/platform/src/main/java/com/plate/boot/relational/menus/Menu.java @@ -21,53 +21,158 @@ import java.util.Set; /** - * @author Alex Bob + * Represents a menu entity within an application, encapsulating details about menu items including + * their type, associated permissions, hierarchy, and metadata for display and management purposes. + * This class integrates with JPA annotations for database persistence and utilizes Lombok's {@link Data} + * annotation for boilerplate code reduction related to getters, setters, equals, hash, and toString methods. + * It also features custom methods to handle specific business logic such as authority formatting and + * extraction of additional properties stored as JSON. */ @Data @Table("se_menus") public class Menu implements BaseEntity { + /** + * Unique identifier for the menu entity. + * This field represents the primary key of the menu within the database + * and is annotated with {@link Id} to denote its role as the identity field. + * The type {@code Integer} suggests that the identifier is a numeric value. + */ @Id private Integer id; + /** + * Represents the unique code associated with a menu item within the system. + * This string identifier helps distinguish one menu from another and is likely used + * in various operations such as retrieval, modification, and deletion of menu items. + */ private String code; + /** + * Represents the private code attribute associated with a menu item. + * This code serves as an internal reference or identifier within the system. + * It is not exposed publicly and can be utilized for backend processing, + * data linking, or any other purpose that requires a distinct code within the menu context. + */ private String pcode; + /** + * The unique code representing the tenant associated with this menu item. + * This field is crucial for multi-tenancy support, enabling the distinction between + * menu items belonging to different tenants within the system. + */ private String tenantCode; + /** + * Represents the type of a menu item. + *

+ * This enumeration defines the different categories a menu item can fall into: + *

    + *
  • {@link MenuType#FOLDER}: Denotes a folder that can contain other menu items.
  • + *
  • {@link MenuType#MENU}: Represents a standard menu option that can be interacted with directly.
  • + *
  • {@link MenuType#LINK}: Specifies a menu item that links to an external resource or URL.
  • + *
  • {@link MenuType#API}: Indicates a menu item that is associated with an API endpoint.
  • + *
+ *

+ */ @NotNull(message = "Menu type cannot be null!") private MenuType type; + /** + * Represents the authority associated with a menu item. + * This field ensures that the authority adheres to specific validation rules: + * - It must not be blank. + * - It can only include English letters or '_' symbols, with a length constraint of 1 to 256 characters. + * The authority follows a predefined pattern to maintain consistency and security within the system's permission structure. + */ @NotBlank(message = "Authority cannot be blank!") @Pattern(regexp = "^[a-zA-Z_]{1,256}$", message = "Authority can only contain English letters or '_' symbols.") private String authority; + /** + * The name of the menu item. + * This field must not be blank; otherwise, a validation error will occur with the message "Name cannot be blank!". + * It represents the display name or title of the menu entry within the application's navigation or interface. + */ @NotBlank(message = "Name cannot be blank!") private String name; + /** + * The path variable represents the route or URL path associated with a menu item. + * It defines the specific path that can be accessed within the application's routing system. + * This field is crucial for determining the navigation structure and linking menu items to their respective functionalities. + */ private String path; + /** + * Represents the sorting order of a menu item within a list or hierarchy. + * A higher number indicates a lower priority in the sorting sequence. + * Typically used for organizing the display order of menu items. + */ private Short sortNo; + /** + * This field holds additional, extendable information about the menu item in a structured JSON format. + * It allows for storing custom metadata that does not fit into the predefined fields of the `Menu` class. + * The content can vary and may include data used for UI customization, access control details, or any other + * application-specific information. + */ private JsonNode extend; + /** + * The creator of the menu item. + * This field holds a reference to the {@link UserAuditor} who initially created the menu. + * It captures metadata about the creator, including their code, username, and name. + */ @CreatedBy private UserAuditor creator; + /** + * The {@code updater} field represents the user auditor who last modified the {@link Menu} entity. + * It captures metadata about the user responsible for the most recent update to the menu item, + * including their code, username, and name. This information is particularly useful for auditing + * purposes, enabling tracking of changes and maintaining a log of who made modifications. + *

+ * Type: {@link UserAuditor} + *

+ * Access: Private + */ @LastModifiedBy private UserAuditor updater; + /** + * Represents the timestamp indicating when the menu was initially created. + * This field is automatically populated with the date and time at creation. + */ @CreatedDate private LocalDateTime createdTime; + /** + * Represents the last modified date and time of the menu. + * This field captures when the menu was last updated and is automatically managed + * by the system to reflect the most recent modification timestamp. + */ @LastModifiedDate private LocalDateTime updatedTime; + /** + * Sets the authority for the menu item. + * This method enhances the provided authority by prepending a prefix if necessary and converting it to uppercase. + * It delegates the actual transformation to the {@link #upgradeAuthorityUpperCase(String)} method. + * + * @param authority The authority string to be set. It can be in any format but will be standardized. + */ public void setAuthority(String authority) { this.authority = upgradeAuthorityUpperCase(authority); } + /** + * Converts the provided authority string to uppercase and prefixes it with 'ROLE_' if not already present, + * following a conversion from lowerCamelCase to UPPER_UNDERSCORE format. If the input is empty, returns null. + * + * @param authority The authority string to be upgraded. Expected in lowerCamelCase if not prefixed. + * @return The upgraded authority string in UPPER_UNDERSCORE format, prefixed with 'ROLE_' if necessary, or null if input was empty. + */ private String upgradeAuthorityUpperCase(String authority) { if (!StringUtils.hasLength(authority)) { return null; @@ -81,6 +186,14 @@ private String upgradeAuthorityUpperCase(String authority) { return role; } + /** + * Retrieves the set of permissions associated with the menu item. + * This method checks if the 'extend' field is present and contains a 'permissions' node. + * If found, it converts the value of 'permissions' to a set of Permission objects using an ObjectMapper. + * Returns null if the 'permissions' node is not present or 'extend' is null. + * + * @return A set of Permission objects representing the permissions, or null if not available. + */ @JsonGetter public Set getPermissions() { return Optional.ofNullable(this.getExtend()).map(node -> node.get("permissions")) @@ -88,27 +201,45 @@ public Set getPermissions() { })).orElse(null); } + /** + * Retrieves the icons associated with the menu item. + * This method inspects the 'extend' property, which is expected to be a JSON node, + * attempting to extract a text value under the key 'icons'. If the 'extend' property or the 'icons' + * key within it is not present, the method returns null. + * + * @return A string representing the icons of the menu item, or null if not found. + */ @JsonGetter public String getIcons() { return Optional.ofNullable(this.getExtend()).map(node -> node.get("icons")) .map(JsonNode::asText).orElse(null); } + /** + * Enumerates the different types of menu items that can be defined within a system. + * Each constant represents a distinct category with specific functionalities or purposes. + */ enum MenuType { /** - * folder + * Represents a category for folder-type menu items within the system. + * This type signifies a menu item that can contain other sub-menu items, organizing them in a hierarchical structure. */ FOLDER, /** - * menu + * Represents a menu item within a system's navigation structure. + * This could be a collection of sub-items, an actionable menu option, a link to another page, or an API endpoint. + * It is a part of the {@link MenuType} enumeration, which categorizes various types of menu elements. */ MENU, /** - * 链接 + * Represents a type of menu item that serves as a hyperlink to external resources or internal system pages. + * Unlike {@link MenuType#FOLDER} and {@link MenuType#MENU}, which can contain nested items, {@code LINK} is a terminal item + * intended to redirect users when selected. */ LINK, /** - * 接口 + * Represents an API-related menu item within the system. + * This type signifies that the menu item is associated with an API call or functionality. */ API; }