diff --git a/boot/platform/src/main/java/com/plate/boot/commons/base/AbstractDatabase.java b/boot/platform/src/main/java/com/plate/boot/commons/base/AbstractDatabase.java index 7011344f..86353b8c 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/base/AbstractDatabase.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/base/AbstractDatabase.java @@ -93,7 +93,9 @@ protected Flux queryWithCache(Object key, String sql, Map bindParams, Class entityClass) { var executeSpec = this.databaseClient.sql(() -> sql); executeSpec = executeSpec.bindValues(bindParams); - Flux source = executeSpec.mapProperties(entityClass).all(); + Flux source = executeSpec + .map((row, rowMetadata) -> this.r2dbcConverter.read(entityClass, row, rowMetadata)) + .all(); source = source.flatMapSequential(ContextUtils::serializeUserAuditor); return queryWithCache(key, source); } diff --git a/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java b/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java index 13d6eba3..b0d41958 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/converters/UserAuditorConverters.java @@ -11,7 +11,17 @@ import org.springframework.stereotype.Component; /** - * @author Alex bob + * Configures converters for handling the transformation between {@link UserAuditor} instances and their string representations. + * This configuration is particularly useful in data persistence layers where type conversion is necessary, such as when storing + * auditor details as strings in a database column. + *

+ * The class registers two converters: + *

    + *
  • {@link UserAuditorWriteConverter}: Converts a {@link UserAuditor} object to its code as a String.
  • + *
  • {@link UserAuditorReadConverter}: Converts a String back to a {@link UserAuditor} instance using the code.
  • + *
+ * These converters are annotated with Spring's {@link Component}, {@link WritingConverter}, and {@link ReadingConverter} + * to integrate seamlessly with Spring Data's conversion service. */ @Log4j2 @Configuration(proxyBeanMethods = false) @@ -22,6 +32,18 @@ public void afterPropertiesSet() { log.info("Initializing converter [UserAuditorConverters]..."); } + /** + * Converts a {@link UserAuditor} object to its code represented as a String. + * This converter is intended for write operations, facilitating the storage of auditor details + * in a textual format, typically used within data persistence frameworks where type conversion + * services are leveraged. + *

+ * The conversion process involves extracting the 'code' attribute from the provided + * {@link UserAuditor} instance and returning it as a plain String. + *

+ * Part of the {@link UserAuditorConverters} configuration which manages the bidirectional + * transformations between {@link UserAuditor} and String for read/write operations. + */ @Component @WritingConverter public static class UserAuditorWriteConverter implements Converter { @@ -31,6 +53,22 @@ public String convert(@NonNull UserAuditor source) { } } + /** + * Converts a String representation of a user auditor code into a {@link UserAuditor} instance. + * This converter is designed to be utilized during read operations, where auditor information stored + * as strings (e.g., in a database) needs to be transformed back into a domain object. + *

+ * The conversion logic employs the {@link UserAuditor#withCode(String)} factory method, + * passing the source string, which typically corresponds to the 'code' attribute of a {@link UserAuditor}, + * to reconstruct the auditor object with default values for username and name. + *

+ * This class is part of the {@link UserAuditorConverters} configuration, complementing the write-side + * conversion provided by {@link UserAuditorWriteConverter}. + * + * @see UserAuditorConverters + * @see UserAuditorWriteConverter + * @see UserAuditor#withCode(String) + */ @Component @ReadingConverter public static class UserAuditorReadConverter implements Converter { diff --git a/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java b/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java index af39f90e..3b4d34d3 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonException.java @@ -12,23 +12,22 @@ public class JsonException extends RestServerException { * Constructs a new {@code JsonException} instance initialized with a default status code of 500 and a predefined error message, * wrapping the provided {@link IOException} which represents a JSON processing error. * - * @param jsonProcessingException The {@link IOException} that was encountered during JSON processing, providing specifics about the processing failure. + * @param exception The {@link IOException} that was encountered during JSON processing, providing specifics about the processing failure. */ - public JsonException(IOException jsonProcessingException) { - this(500, "Json processing exception", jsonProcessingException); + public JsonException(Throwable exception) { + this("Json processing exception", exception); } /** - * Constructs a new {@code JsonException} with a specified status code, error message, and additional details. - * This exception is typically utilized to wrap issues encountered during JSON processing, adding a layer of specificity - * over the broader {@link RestServerException}. + * Constructs a new {@code JsonException} with a specified error message and the cause of the exception. + * This constructor allows for detailed customization of the exception message while preserving the original + * exception's stack trace for debugging purposes. * - * @param status The HTTP status code representing the type of error occurred. This helps in categorizing the exception. - * @param message A human-readable description of the error, providing context about what went wrong during JSON processing. - * @param msg An optional object containing further information or metadata related to the error, which can assist in diagnosing the issue. + * @param message A descriptive message explaining the reason for the exception. + * @param exception The underlying {@link Throwable} that caused this exception to be thrown, providing additional context. */ - public JsonException(int status, String message, Object msg) { - super(status, message, msg); + public JsonException(String message, Throwable exception) { + super(message, exception); } /** @@ -36,12 +35,12 @@ public JsonException(int status, String message, Object msg) { * which is indicative of a JSON processing error. This method is a convenience factory for creating * {@code JsonException} objects without needing to explicitly reference the constructor arguments. * - * @param jsonProcessingException The {@link IOException} that occurred during JSON processing, - * providing details about the processing error. + * @param error The {@link IOException} that occurred during JSON processing, + * providing details about the processing error. * @return A new instance of {@code JsonException} initialized with the given {@code IOException} * as the cause, carrying a default status code and message indicative of a JSON processing failure. */ - public static JsonException withError(IOException jsonProcessingException) { - return new JsonException(jsonProcessingException); + public static JsonException withError(Throwable error) { + return new JsonException(error); } } \ No newline at end of file diff --git a/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonPointerException.java b/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonPointerException.java new file mode 100644 index 00000000..25ea781d --- /dev/null +++ b/boot/platform/src/main/java/com/plate/boot/commons/exception/JsonPointerException.java @@ -0,0 +1,45 @@ +package com.plate.boot.commons.exception; + +import java.io.IOException; + +/** + * Represents an exception that occurs when attempting to access a non-existent path within a JSON structure. + * This exception extends {@link JsonException}, specifically catering to errors involving JSON pointer operations + * which fail due to invalid or unreachable paths within the JSON data. + * + *

+ * This class provides constructors to initialize the exception with a descriptive message and an underlying + * {@link IOException} that led to the JSON pointer error, allowing for detailed diagnostics of the issue. + * Additionally, it includes a static factory method for conveniently creating instances with an error message + * and the associated {@linkplain IOException}. + *

+ */ +public class JsonPointerException extends JsonException { + + /** + * Constructs a new {@code JsonPointerException} with a specified error message and an {@link IOException} cause. + * This exception is thrown when attempting to access a non-existent path within a JSON structure using a JSON pointer, + * indicating that the referenced location could not be found or accessed due to structural issues within the JSON data. + * + * @param message A human-readable description of the error, explaining the context of the JSON pointer operation that failed. + * @param exception The {@link IOException} that triggered this exception, providing additional context or details about the failure. + */ + public JsonPointerException(String message, IllegalArgumentException exception) { + super(message, exception); + } + + /** + * Creates a new instance of {@code JsonPointerException} with a specified error message and an {@link IOException}. + * This static method serves as a convenience factory for initializing {@code JsonPointerException} instances, + * encapsulating both a descriptive error message and the original {@linkplain IOException} that caused the failure. + * It is particularly useful when the exception is due to an issue encountered while accessing a JSON structure, + * such as a malformed JSON pointer or inaccessible data. + * + * @param message A human-readable message describing the error context. This should explain the problem encountered with the JSON pointer operation. + * @param exception The {@link IOException} that represents the underlying cause of the JSON pointer error. It provides deeper insight into why the operation failed. + * @return A new {@code JsonPointerException} instance initialized with the given message and {@code IOException}. + */ + public static JsonPointerException withError(String message, IllegalArgumentException exception) { + return new JsonPointerException(message, exception); + } +} diff --git a/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java b/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java index e40b4dd8..b0ce986b 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/exception/RestServerException.java @@ -39,6 +39,21 @@ public class RestServerException extends RuntimeException implements Serializabl */ protected int code; + /** + * Constructs a new instance of {@code RestServerException} with the specified error message and cause. + * This exception is typically used to convey that a RESTful service encountered an issue on the server side, + * where the provided message describes the error and the throwable denotes the underlying cause. + * + * @param message The detailed message explaining the reason for the exception. + * @param throwable The cause of the exception, usually an instance of {@link Throwable} + * that triggered this exception. + */ + public RestServerException(String message, Throwable throwable) { + super(message, throwable); + this.code = 500; + this.msg = throwable.fillInStackTrace().getMessage(); + } + /** * Constructs a new instance of {@code RestServerException} with specified error code, message, and additional details. * This exception is intended for conveying REST server-side error conditions, providing a more granular error code diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java index b23bb329..7a923f71 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/BeanUtils.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.google.common.collect.Maps; import com.plate.boot.commons.exception.JsonException; +import com.plate.boot.commons.exception.JsonPointerException; import com.plate.boot.commons.exception.RestServerException; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.InitializingBean; @@ -23,8 +24,8 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.util.Map; -import java.util.Objects; import java.util.StringJoiner; +import java.util.stream.Collectors; /** * Converts a JavaBean object into a Map representation. @@ -73,12 +74,12 @@ public static T jsonPathToBean(JsonNode json, String path, Class clazz) { JsonPointer jsonPointer = JsonPointer.valueOf(pathJoiner.toString()); JsonNode valueNode = json.at(jsonPointer); if (valueNode.isMissingNode()) { - throw JsonException.withMsg("Json pointer path is not exist!", + throw JsonPointerException.withMsg("Json pointer path error", "JsonPointer path is not exist!"); } return ContextUtils.OBJECT_MAPPER.convertValue(valueNode, clazz); } catch (IllegalArgumentException e) { - throw JsonException.withMsg("转换JsonPointer字符转异常!", e.getMessage()); + throw JsonPointerException.withError("Json pointer covert error", e); } } @@ -106,8 +107,14 @@ public static byte[] objectToBytes(T object) { * @return A string representation of the combined hash code, serving as a unique cache key. */ public static String cacheKey(Object... objects) { - int hashCode = Objects.hash(objects); - return String.valueOf(hashCode); + StringJoiner keyJoiner = new StringJoiner("&"); + for (var obj : objects) { + var objMap = BeanUtils.beanToMap(obj, true); + var setStr = objMap.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.toSet()); + setStr.forEach(keyJoiner::add); + } + return ContextUtils.encodeToMD5(keyJoiner.toString()); } /** @@ -252,6 +259,13 @@ public static Map beanToMap(T bean, final boolean isToUnderl return objectMapper.convertValue(bean, type); } + /** + * Sets the maximum size of data that can be stored in memory before being written to disk. + * This method allows configuration of the maximum in-memory size limit, which is particularly + * useful for managing memory usage when handling large amounts of data, such as file uploads. + * + * @param dataSize The maximum in-memory size limit defined as a {@link DataSize}. Defaults to 256 kilobytes if not explicitly set. + */ @Value("${spring.codec.max-in-memory-size:256kb}") public void setMaxInMemorySize(DataSize dataSize) { MAX_IN_MEMORY_SIZE = dataSize; diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java index 020423f3..fbd5658a 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/ContextUtils.java @@ -20,7 +20,11 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Base64; import java.util.List; import java.util.Objects; @@ -47,6 +51,17 @@ public final class ContextUtils implements InitializingBean { "HTTP_VIA", "REMOTE_ADDR" }; + + public final static MessageDigest MD; + + static { + try { + MD = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw RestServerException.withMsg("MD5 algorithm not found", e); + } + } + public static ObjectMapper OBJECT_MAPPER; public static UsersService USERS_SERVICE; @@ -55,6 +70,11 @@ public final class ContextUtils implements InitializingBean { ContextUtils.USERS_SERVICE = usersService; } + public static String encodeToMD5(String input) { + byte[] bytes = MD.digest(input.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(bytes); + } + public static String getClientIpAddress(ServerHttpRequest httpRequest) { HttpHeaders headers = httpRequest.getHeaders(); for (String header : IP_HEADER_CANDIDATES) { diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryHelper.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryHelper.java index 0bde4e60..84763735 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryHelper.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryHelper.java @@ -95,22 +95,24 @@ public static String applySort(Sort sort, String prefix) { */ @SuppressWarnings("unchecked") public static QueryFragment query(Object object, Collection skipKeys, String prefix) { + StringJoiner whereSql = new StringJoiner(" and "); + Map bindParams = Maps.newHashMap(); Map objectMap = BeanUtils.beanToMap(object, false, true); if (ObjectUtils.isEmpty(objectMap)) { - return QueryFragment.of(new StringJoiner(" and "), Maps.newHashMap()); + return QueryFragment.of(whereSql, bindParams); } QueryFragment jsonQueryFragment = QueryJsonHelper.queryJson((Map) objectMap.get("query"), prefix); - Map params = jsonQueryFragment.params(); - StringJoiner sql = jsonQueryFragment.sql(); + whereSql.merge(jsonQueryFragment.sql()); + bindParams.putAll(jsonQueryFragment.params()); String securityCodeKey = "securityCode"; if (!skipKeys.contains(securityCodeKey) && !ObjectUtils.isEmpty(objectMap.get(securityCodeKey))) { String key = "tenant_code"; if (StringUtils.hasLength(prefix)) { key = prefix + "." + key; } - sql.add(key + " like :securityCode"); - params.put(securityCodeKey, objectMap.get(securityCodeKey)); + whereSql.add(key + " like :securityCode"); + bindParams.put(securityCodeKey, objectMap.get(securityCodeKey)); } Set removeKeys = new HashSet<>(SKIP_CRITERIA_KEYS); @@ -121,10 +123,12 @@ public static QueryFragment query(Object object, Collection skipKeys, St } objectMap = Maps.filterKeys(objectMap, key -> !removeKeys.contains(key)); + QueryFragment entityQueryFragment = query(objectMap, prefix); - params.putAll(entityQueryFragment.params()); - sql.merge(entityQueryFragment.sql()); - return QueryFragment.of(sql, params); + + whereSql.merge(entityQueryFragment.sql()); + bindParams.putAll(entityQueryFragment.params()); + return QueryFragment.of(whereSql, bindParams); } /** diff --git a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJsonHelper.java b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJsonHelper.java index d7005500..65e720c7 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJsonHelper.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/utils/query/QueryJsonHelper.java @@ -138,7 +138,7 @@ public static QueryFragment queryJson(Map params, String prefix) * @throws IllegalArgumentException If the keys array is null or empty. */ private static QueryCondition prepareQueryPathAndParameters(Map.Entry entry, String prefix) { - String[] keys = StringUtils.commaDelimitedListToStringArray(entry.getKey()); + String[] keys = StringUtils.delimitedListToStringArray(entry.getKey(), "."); // 处理第一个键 String column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, keys[0]); if (StringUtils.hasLength(prefix)) { diff --git a/boot/platform/src/main/java/com/plate/boot/relational/logger/LoggerRequest.java b/boot/platform/src/main/java/com/plate/boot/relational/logger/LoggerRequest.java index 97b2f603..606ad696 100644 --- a/boot/platform/src/main/java/com/plate/boot/relational/logger/LoggerRequest.java +++ b/boot/platform/src/main/java/com/plate/boot/relational/logger/LoggerRequest.java @@ -15,7 +15,10 @@ import java.util.StringJoiner; /** - * @author Alex bob + * Represents a logging request that extends the basic {@link Logger} functionality, + * encapsulating details necessary for logging specific HTTP requests. This includes + * query parameters, security codes, and provides utility methods for constructing + * log entries compatible with the core {@link Logger} infrastructure. */ @Data @EqualsAndHashCode(callSuper = true) @@ -26,6 +29,18 @@ public class LoggerRequest extends Logger { private String securityCode; + /** + * Constructs a new {@link LoggerRequest} instance with specified details. + * + * @param tenantCode The tenant code associated with the logging request. + * @param operator The operator (user or system component) initiating the request. + * @param prefix A prefix to categorize or namespace the log entry. + * @param method The HTTP method of the request (e.g., GET, POST). + * @param status The status of the request processing (e.g., success, error). + * @param url The URL path targeted by the request. + * @param context Additional contextual information in JSON format. + * @return A configured {@link LoggerRequest} object ready for logging purposes. + */ public static LoggerRequest of(String tenantCode, String operator, String prefix, String method, String status, String url, JsonNode context) { LoggerRequest request = new LoggerRequest();