Skip to content

Commit

Permalink
fix(utils, converters): 更新查询路径处理与转换器逻辑
Browse files Browse the repository at this point in the history
- 更新 `QueryJsonHelper` 中的键分隔符处理逻辑。
- 调整 `UserAuditorConverters` 文档注释及内部转换器逻辑。
- 优化 `QueryHelper` 的 SQL 构建过程。
- 添加 `JsonPointerException` 异常类处理 JSON 指针错误。
- 扩展 `LoggerRequest` 构造方法与日志细节。
- 修正 `AbstractDatabase` 中的转换器调用。
- 更新 `JsonException` 以处理更广泛的异常类型。
- 增强 `ContextUtils` 和 `BeanUtils` 方法功能。
  • Loading branch information
vnobo committed Sep 14, 2024
1 parent 4dbdce1 commit 1818d0d
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ protected <T> Flux<T> queryWithCache(Object key, String sql,
Map<String, Object> bindParams, Class<T> entityClass) {
var executeSpec = this.databaseClient.sql(() -> sql);
executeSpec = executeSpec.bindValues(bindParams);
Flux<T> source = executeSpec.mapProperties(entityClass).all();
Flux<T> source = executeSpec
.map((row, rowMetadata) -> this.r2dbcConverter.read(entityClass, row, rowMetadata))
.all();
source = source.flatMapSequential(ContextUtils::serializeUserAuditor);
return queryWithCache(key, source);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@
import org.springframework.stereotype.Component;

/**
* @author <a href="https://github.com/vnobo">Alex bob</a>
* 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.
* <p>
* The class registers two converters:
* <ul>
* <li>{@link UserAuditorWriteConverter}: Converts a {@link UserAuditor} object to its code as a String.</li>
* <li>{@link UserAuditorReadConverter}: Converts a String back to a {@link UserAuditor} instance using the code.</li>
* </ul>
* 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)
Expand All @@ -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.
* <p>
* The conversion process involves extracting the 'code' attribute from the provided
* {@link UserAuditor} instance and returning it as a plain String.
* <p>
* 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<UserAuditor, String> {
Expand All @@ -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.
* <p>
* 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.
* <p>
* 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<String, UserAuditor> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,35 @@ 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);
}

/**
* Constructs and returns a new {@code JsonException} instance wrapping the provided {@link IOException},
* 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);
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>
* 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}.
* </p>
*/
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -73,12 +74,12 @@ public static <T> T jsonPathToBean(JsonNode json, String path, Class<T> 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);
}
}

Expand Down Expand Up @@ -106,8 +107,14 @@ public static <T> 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());
}

/**
Expand Down Expand Up @@ -252,6 +259,13 @@ public static <T> Map<String, Object> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,24 @@ public static String applySort(Sort sort, String prefix) {
*/
@SuppressWarnings("unchecked")
public static QueryFragment query(Object object, Collection<String> skipKeys, String prefix) {
StringJoiner whereSql = new StringJoiner(" and ");
Map<String, Object> bindParams = Maps.newHashMap();

Map<String, Object> 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<String, Object>) objectMap.get("query"), prefix);
Map<String, Object> 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<String> removeKeys = new HashSet<>(SKIP_CRITERIA_KEYS);
Expand All @@ -121,10 +123,12 @@ public static QueryFragment query(Object object, Collection<String> 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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static QueryFragment queryJson(Map<String, Object> params, String prefix)
* @throws IllegalArgumentException If the keys array is null or empty.
*/
private static QueryCondition prepareQueryPathAndParameters(Map.Entry<String, Object> 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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import java.util.StringJoiner;

/**
* @author <a href="https://github.com/vnobo">Alex bob</a>
* 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)
Expand All @@ -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();
Expand Down

0 comments on commit 1818d0d

Please sign in to comment.