diff --git a/boot/platform/src/main/java/com/plate/boot/commons/ErrorResponse.java b/boot/platform/src/main/java/com/plate/boot/commons/ErrorResponse.java index 34f2c89d..cde17381 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/ErrorResponse.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/ErrorResponse.java @@ -4,26 +4,30 @@ import java.time.LocalDateTime; /** - * Represents an error response structure that is used to convey detailed information - * about an error encountered during processing. - * This record encapsulates metadata such as the request identifier, the endpoint path, - * the error code, a descriptive message, - * additional error details, and the timestamp when the error occurred. + * Represents an error response structure, encapsulating details about a specific error occurrence. + * This record is designed to be serialized and used for communicating error information between systems. + * + * @param requestId A unique identifier for the request that encountered the error. + * @param code The HTTP status code representing the type of error. + * @param path The endpoint or URI path where the error occurred. + * @param message A human-readable description of the error. + * @param errors Additional details or objects related to the error, can vary based on the context. + * @param time The timestamp indicating when the error response was created. */ -public record ErrorResponse(String requestId, String path, Integer code, +public record ErrorResponse(String requestId, Integer code, String path, String message, Object errors, LocalDateTime time) implements Serializable { + /** - * Constructs a new {@link ErrorResponse} instance with the provided parameters, - * automatically setting the timestamp to the current date and time. + * Creates an instance of {@link ErrorResponse} with the current local date and time. * - * @param requestId The unique identifier for the request associated with the error. - * @param path The endpoint path where the error occurred. - * @param code The error code representing the type of error. - * @param message A descriptive message explaining the error. - * @param errors Additional details about the error, can be an object like a list of errors or a structured error message. - * @return A new {@link ErrorResponse} instance encapsulating the error details including the current timestamp. + * @param requestId A unique identifier for the request associated with this error response. + * @param code The HTTP status code reflecting the error type. + * @param path The path or endpoint where the error was encountered. + * @param message A descriptive message explaining the error. + * @param errors Additional error-related data, which could include exception details, validation errors, etc. + * @return A new {@link ErrorResponse} instance initialized with the provided parameters and the current timestamp. */ - public static ErrorResponse of(String requestId, String path, Integer code, String message, Object errors) { - return new ErrorResponse(requestId, path, code, message, errors, LocalDateTime.now()); + public static ErrorResponse of(String requestId, Integer code, String path, String message, Object errors) { + return new ErrorResponse(requestId, code, path, message, errors, LocalDateTime.now()); } } \ No newline at end of file diff --git a/boot/platform/src/main/java/com/plate/boot/commons/GlobalExceptionHandler.java b/boot/platform/src/main/java/com/plate/boot/commons/GlobalExceptionHandler.java index d1bb605a..13589af7 100644 --- a/boot/platform/src/main/java/com/plate/boot/commons/GlobalExceptionHandler.java +++ b/boot/platform/src/main/java/com/plate/boot/commons/GlobalExceptionHandler.java @@ -18,6 +18,8 @@ import java.util.List; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; + /** * GlobalExceptionHandler is a centralized exception handler that intercepts and processes * various exceptions thrown during the execution of REST endpoints within an application. @@ -65,8 +67,8 @@ public ResponseEntity handleBindException(ServerWebExchange excha log.error(ex.getReason(), ex); } return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).contentType(MediaType.APPLICATION_JSON) - .body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(), - 417, ex.getReason(), errors)); + .body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.BAD_REQUEST.value(), + exchange.getRequest().getPath().value(), ex.getReason(), errors)); } /** @@ -99,8 +101,8 @@ public ResponseEntity handleFailureException(ServerWebExchange ex log.error(ex.getCause().getMessage(), ex.getCause()); } return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).contentType(MediaType.APPLICATION_JSON) - .body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(), - 507, ex.getLocalizedMessage(), errors)); + .body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.INSUFFICIENT_STORAGE.value(), + exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), errors)); } /** @@ -123,30 +125,10 @@ public ResponseEntity handleRestServerException(ServerWebExchange if (log.isDebugEnabled()) { log.error(ex.getLocalizedMessage(), ex); } - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) - .body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(), - ex.getCode(), ex.getLocalizedMessage(), ex.getMsg())); + return ResponseEntity.status(INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) + .body(ErrorResponse.of(exchange.getRequest().getId(), ex.getStatusCode().value(), + exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), ex.getErrors())); } - /** - * Handles exceptions that occur during the processing of requests within the application. - * This method logs the exception details and constructs an appropriate error response - * to be sent back to the client. - * - * @param exchange The current server web exchange which holds the request and response information. - * @param ex The exception that was thrown during request processing. - * @return A ResponseEntity containing an ErrorResponse with details about the exception, - * including the request ID, the endpoint path, HTTP status code 500 (Internal Server Error), - * the exception message, and the cause message if available. The response body is in JSON format. - */ - @ExceptionHandler(Exception.class) - public ResponseEntity handleException(ServerWebExchange exchange, Exception ex) { - if (log.isDebugEnabled()) { - log.error(ex.getLocalizedMessage(), ex); - } - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON) - .body(ErrorResponse.of(exchange.getRequest().getId(), exchange.getRequest().getPath().value(), - 500, ex.getLocalizedMessage(), ex.getCause().getMessage())); - } } \ No newline at end of file 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 b0ce986b..bb57383e 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 @@ -2,6 +2,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import java.io.Serializable; @@ -24,81 +26,25 @@ @EqualsAndHashCode(callSuper = true) public class RestServerException extends RuntimeException implements Serializable { - /** - * Encapsulates additional metadata or details about the error condition. - * This field can hold any type of object, enabling the transmission of structured - * information alongside the exception, such as maps, lists, or custom objects that - * provide context for the error scenario. - */ - protected Object msg; - /** - * The error code associated with this {@link RestServerException}. - * This field represents a custom error code that provides more granular information - * about the specific error scenario beyond standard HTTP status codes. - * It is intended for use in identifying and differentiating between various error conditions. - */ - protected int code; + protected final HttpStatusCode statusCode; + protected final Object errors; - /** - * 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(); + public RestServerException(HttpStatusCode code, String message, Object msg) { + super(message); + this.statusCode = code; + this.errors = msg; } - /** - * 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 - * alongside a standard exception message and an optional object that can carry detailed contextual information. - * - * @param code The custom error code associated with the exception. This can be used to differentiate between - * various error scenarios beyond the standard HTTP status codes. - * @param message The human-readable error message explaining the exception circumstances. Should be concise and informative. - * @param msg An optional object containing additional metadata or details about the error. Can be any type, - * facilitating the passing of structured error information (e.g., maps, lists, or domain-specific objects). - */ - public RestServerException(int code, String message, Object msg) { - super(message); - this.msg = msg; - this.code = code; + public RestServerException(String message, Throwable throwable) { + this(HttpStatus.INTERNAL_SERVER_ERROR, message, throwable.fillInStackTrace()); } - /** - * Creates a new instance of {@code RestServerException} with a predefined HTTP status code of 500 (Internal Server Error), - * a custom message, and additional details encapsulated in the {@code msg} parameter. - * This method serves as a convenience factory for generating exceptions that indicate a generic server error - * along with specific contextual information. - * - * @param message A descriptive message explaining the reason for the exception. - * @param msg An arbitrary object containing additional details about the error. This can be used to provide more - * extensive error context or metadata. - * @return A new instance of {@code RestServerException} initialized with the provided message, a status code of 500, - * and the additional details object. - */ - public static RestServerException withMsg(String message, Object msg) { - return withMsg(500, message, msg); + public static RestServerException withMsg(String message, Object errors) { + return withMsg(HttpStatusCode.valueOf(500), message, errors); } - /** - * Creates a new instance of {@code RestServerException} with a specified error code, message, and additional details. - * This static factory method allows for customization of the error response by providing a unique error code and - * a message along with an arbitrary object that can contain further information about the error condition. - * - * @param code The custom error code to identify the specific error scenario. This code supplements the HTTP status code. - * @param message The error message describing the exception's nature. Should be informative for debugging purposes. - * @param msg An optional object holding additional metadata or details related to the error. Can be any type. - * @return A new instance of {@code RestServerException} initialized with the provided code, message, and additional details. - */ - public static RestServerException withMsg(int code, String message, Object msg) { - return new RestServerException(code, message, msg); + public static RestServerException withMsg(HttpStatusCode code, String message, Object errors) { + return new RestServerException(code, message, errors); } } \ No newline at end of file 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 06521cb4..832f1bb1 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 @@ -325,9 +325,9 @@ private Mono handleXmlHttpRequestFailure(ServerWebExchange exchange, Authe * 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())); + return ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.UNAUTHORIZED.value(), + exchange.getRequest().getPath().value(), + "认证失败,检查你的用户名,密码是否正确或安全密钥是否过期!", List.of(e.getMessage())); } - } } \ No newline at end of file diff --git a/boot/platform/src/main/java/com/plate/boot/security/SecurityDetails.java b/boot/platform/src/main/java/com/plate/boot/security/SecurityDetails.java index 6c4b2c6d..720dc8d3 100644 --- a/boot/platform/src/main/java/com/plate/boot/security/SecurityDetails.java +++ b/boot/platform/src/main/java/com/plate/boot/security/SecurityDetails.java @@ -1,8 +1,8 @@ package com.plate.boot.security; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.plate.boot.security.core.UserAuditor; import com.plate.boot.security.core.group.member.GroupMemberResponse; import com.plate.boot.security.core.tenant.member.TenantMemberResponse; import lombok.Getter; @@ -19,44 +19,92 @@ import java.util.Set; /** - * @author Alex bob + * SecurityDetails represents a user's security details, extending DefaultOAuth2User and implementing UserDetails. + * It encapsulates user authentication and authorization data, including OAuth2 attributes, + * tenant and group affiliations, and account status flags. */ @Setter @Getter public final class SecurityDetails extends DefaultOAuth2User implements UserDetails { + /** + * The serialVersionUID is a unique identifier for Serializable classes. + * It ensures compatibility between serialized objects and their respective classes during deserialization. + * This field is automatically generated by the SpringSecurityCoreVersion class to maintain serialization compatibility. + */ @Serial private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + /** + * Represents the unique identifier code for a security detail within the {@link SecurityDetails} class. + * This string value is used to distinguish different security details instances. + */ private String code; + /** + * Represents the username of the security principal. + * This field stores the unique identifier for a user within the system, typically used for authentication and authorization purposes. + */ private String username; + /** + * Represents the nickname of a user within the security details. + * This string holds the colloquial or display name associated with the user's account. + */ private String nickname; + /** + * A collection of {@link TenantMemberResponse} objects representing the tenants associated with the security details. + * Each {@code TenantMemberResponse} encapsulates information about a tenant member, including a unique identifier, + * tenant code, user code, enabled status, and audit details provided by {@link UserAuditor}. + * This set facilitates management and access to tenant-specific data for the authenticated user within a security context. + */ private Set tenants; + /** + * Represents a collection of {@link GroupMemberResponse} instances associated with a security details object. + * This set encapsulates the group membership responses for a user, providing information about the groups + * the user is a part of, including each group's name and additional metadata in the form of a JSON node. + */ private Set groups; + /** + * The password field securely stores the authentication credential for a user. + * This field is marked as ignored for JSON serialization and deserialization to prevent + * password exposure in transit or storage. Use the provided password methods to handle + * password operations safely. + */ @JsonIgnore private String password; + /** + * Indicates whether the security details are disabled. + * This field is not serialized during JSON processing due to the {@link JsonIgnore} annotation. + */ @JsonIgnore private Boolean disabled; + /** + * Indicates whether the account has expired or not. + * This field is not serialized in JSON responses due to the {@link JsonIgnore} annotation. + */ @JsonIgnore private Boolean accountExpired; + /** + * Indicates whether the account is locked or not. + * This field is not serialized in JSON responses due to the {@link JsonIgnore} annotation. + */ @JsonIgnore private Boolean accountLocked; + /** + * Indicates whether the user's credentials (password) have expired. + * This flag is used to determine if the user needs to reset their password upon next login attempt. + */ @JsonIgnore private Boolean credentialsExpired; - @JsonCreator - public SecurityDetails() { - super(null, Map.of("username", "any_none"), "username"); - } public SecurityDetails(Collection authorities, Map attributes, String nameAttributeKey) { diff --git a/boot/platform/src/main/java/com/plate/boot/security/core/user/UsersService.java b/boot/platform/src/main/java/com/plate/boot/security/core/user/UsersService.java index c3588e97..86422fc6 100644 --- a/boot/platform/src/main/java/com/plate/boot/security/core/user/UsersService.java +++ b/boot/platform/src/main/java/com/plate/boot/security/core/user/UsersService.java @@ -49,7 +49,7 @@ public Mono loadByCode(String code) { public Mono add(UserRequest request) { return this.usersRepository.existsByUsernameIgnoreCase(request.getUsername()).flatMap(exists -> { if (exists) { - return Mono.error(RestServerException.withMsg(417, "User already exists", + return Mono.error(RestServerException.withMsg("User already exists", "Username [" + request.getUsername() + "] already exists!")); } return this.operate(request); @@ -58,7 +58,7 @@ public Mono add(UserRequest request) { public Mono modify(UserRequest request) { return this.usersRepository.findByUsername(request.getUsername()) - .switchIfEmpty(Mono.defer(() -> Mono.error(RestServerException.withMsg(417, + .switchIfEmpty(Mono.defer(() -> Mono.error(RestServerException.withMsg( "User not found!", "User by username [" + request.getUsername() + "] not found!")))) .flatMap(user -> { request.setId(user.getId()); @@ -89,7 +89,7 @@ public Mono save(User user) { } else { assert user.getId() != null; return this.usersRepository.findById(user.getId()) - .switchIfEmpty(Mono.error(RestServerException.withMsg(1404, "User not found", + .switchIfEmpty(Mono.error(RestServerException.withMsg("User not found", "User by id [" + user.getId() + "] not found!"))) .flatMap(old -> { user.setCreatedTime(old.getCreatedTime());