From 6850304019dee23ba0a8a9e7ea49df56fe188f87 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 11:50:16 +0800 Subject: [PATCH 1/7] Enhance exception handling by logging all exceptions in AggregateException; add BeanScope class for defining bean scopes and override toString method in ObjectId for better representation --- .../configure/GlobalExceptionHandler.java | 5 +++- .../io/theurl/framework/core/BeanScope.java | 27 +++++++++++++++++++ .../io/theurl/framework/core/ObjectId.java | 5 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 framework/src/main/java/io/theurl/framework/core/BeanScope.java diff --git a/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java b/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java index 95a3525..47db6e6 100644 --- a/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java +++ b/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java @@ -66,7 +66,10 @@ public ResponseEntity> handleEntityNotFoundException(EntityN @ExceptionHandler(AggregateException.class) public ResponseEntity> handleAggregateException(AggregateException exception) { - LOGGER.error(exception.getMessage(), exception); + for (var ex : exception.getExceptions()) { + LOGGER.error(ex.getLocalizedMessage(), ex); + } + Throwable cause = exception.getExceptions().getFirst(); LOGGER.debug("AggregateException contains {} exceptions, handling the first one: {}", exception.getExceptions().size(), cause.getMessage()); return handleGeneralException(cause); diff --git a/framework/src/main/java/io/theurl/framework/core/BeanScope.java b/framework/src/main/java/io/theurl/framework/core/BeanScope.java new file mode 100644 index 0000000..04ce303 --- /dev/null +++ b/framework/src/main/java/io/theurl/framework/core/BeanScope.java @@ -0,0 +1,27 @@ +package io.theurl.framework.core; + +public class BeanScope { + public final static String APPLICATION = "application"; + public final static String PROTOTYPE = "prototype"; + public final static String REQUEST = "request"; + public final static String SESSION = "session"; + public final static String SINGLETON = "singleton"; + public final static String WEB_SOCKET = "websocket"; +// APPLICATION("application"), +// PROTOTYPE("prototype"), +// REQUEST("request"), +// SESSION("session"), +// SINGLETON("singleton"), +// WEB_SOCKET("websocket"); +// +// private final String name; +// +// BeanScope(String name) { +// this.name = name; +// } +// +// @Override +// public String toString() { +// return name; +// } +} diff --git a/framework/src/main/java/io/theurl/framework/core/ObjectId.java b/framework/src/main/java/io/theurl/framework/core/ObjectId.java index af07dd9..a44ebf5 100644 --- a/framework/src/main/java/io/theurl/framework/core/ObjectId.java +++ b/framework/src/main/java/io/theurl/framework/core/ObjectId.java @@ -55,4 +55,9 @@ public boolean equals(Object obj) { ObjectId objectId = (ObjectId) obj; return value.equals(objectId.value); } + + @Override + public String toString() { + return value.toString(); + } } From 2f1558a8181ea41c8a9393120f40a5a97f60d95b Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 11:50:53 +0800 Subject: [PATCH 2/7] Add data map initialization and getter to UserAuthSuccessEvent for improved data handling --- .../identity/application/event/UserAuthSuccessEvent.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/identity/src/main/java/io/theurl/identity/application/event/UserAuthSuccessEvent.java b/identity/src/main/java/io/theurl/identity/application/event/UserAuthSuccessEvent.java index 2b5c928..2aeb0df 100644 --- a/identity/src/main/java/io/theurl/identity/application/event/UserAuthSuccessEvent.java +++ b/identity/src/main/java/io/theurl/identity/application/event/UserAuthSuccessEvent.java @@ -4,8 +4,10 @@ import io.theurl.framework.domain.ApplicationEvent; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.Map; @EqualsAndHashCode(callSuper = true) @@ -14,7 +16,6 @@ public final class UserAuthSuccessEvent extends ApplicationEvent implements Even private final String grantType; private final Long userId; - public UserAuthSuccessEvent(String grantType, Long userId) { this.grantType = grantType; this.userId = userId; @@ -22,5 +23,7 @@ public UserAuthSuccessEvent(String grantType, Long userId) { private String username; private LocalDateTime grantTime; - private Map data; + + @Getter + private Map data = new HashMap<>(); } From 4f4aefbffdd4e9ccdef942bf5868b54665af1000 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 11:56:46 +0800 Subject: [PATCH 3/7] Refactor token handling by updating scope to BeanScope.PROTOTYPE and modifying JWT content retrieval in TokenEventSubscriber --- .../application/handler/TokenCreateCommandHandler.java | 6 +++--- .../application/subscriber/TokenEventSubscriber.java | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java b/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java index 37f27c1..f21688d 100644 --- a/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java +++ b/identity/src/main/java/io/theurl/identity/application/handler/TokenCreateCommandHandler.java @@ -1,19 +1,19 @@ package io.theurl.identity.application.handler; import com.neroyun.mediator.Handler; +import io.theurl.framework.core.BeanScope; import io.theurl.identity.application.command.TokenCreateCommand; import io.theurl.identity.domain.aggregate.Token; import io.theurl.identity.domain.repository.TokenRepository; import jakarta.annotation.Resource; import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; -import org.springframework.web.context.WebApplicationContext; import java.util.concurrent.CompletableFuture; @Component -@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) +@Scope(BeanScope.PROTOTYPE) +//@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public class TokenCreateCommandHandler implements Handler { @Resource diff --git a/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java b/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java index 0c34575..de939ed 100644 --- a/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java +++ b/identity/src/main/java/io/theurl/identity/application/subscriber/TokenEventSubscriber.java @@ -1,13 +1,16 @@ package io.theurl.identity.application.subscriber; import com.neroyun.mediator.Mediator; +import io.theurl.framework.core.BeanScope; import io.theurl.identity.application.command.TokenCreateCommand; import io.theurl.identity.application.event.UserAuthSuccessEvent; +import org.springframework.context.annotation.Scope; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component +@Scope(BeanScope.PROTOTYPE) public class TokenEventSubscriber { private final Mediator mediator; @@ -20,7 +23,7 @@ public TokenEventSubscriber(Mediator mediator) { public void handleUserAuthSucceedEvent(UserAuthSuccessEvent event) { var command = new TokenCreateCommand() {{ setJti(event.getData().get("jti")); - setContent(event.getData().get("content")); + setContent(event.getData().get("jwt")); setSubject(event.getUserId()); }}; From 6ce8341303fead5d434b47ac1960bb6b4cf8b865 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 12:00:37 +0800 Subject: [PATCH 4/7] Enhance authentication flow by adding null checks for account lock status and updating UserAuthSuccessEvent with grant time and token data --- identity/pom.xml | 11 ++++++++ .../implement/AuthApplicationServiceImpl.java | 10 +++++--- .../identity/domain/aggregate/Token.java | 25 +++++++++++++++---- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/identity/pom.xml b/identity/pom.xml index 929e189..dd8eb5c 100644 --- a/identity/pom.xml +++ b/identity/pom.xml @@ -33,6 +33,17 @@ jjwt-api 0.13.0 + + io.jsonwebtoken + jjwt-impl + 0.13.0 + + + io.jsonwebtoken + jjwt-jackson + 0.13.0 + runtime + diff --git a/identity/src/main/java/io/theurl/identity/application/implement/AuthApplicationServiceImpl.java b/identity/src/main/java/io/theurl/identity/application/implement/AuthApplicationServiceImpl.java index d76d893..ecc742f 100644 --- a/identity/src/main/java/io/theurl/identity/application/implement/AuthApplicationServiceImpl.java +++ b/identity/src/main/java/io/theurl/identity/application/implement/AuthApplicationServiceImpl.java @@ -81,7 +81,7 @@ public CompletableFuture grant(TokenGrantRequestDto reque throw new CredentialNotFoundException(request.username(), "Invalid username or password."); } - if (userInfo.getLockedUntil().isAfter(LocalDateTime.now())) { + if (userInfo.getLockedUntil() != null && userInfo.getLockedUntil().isAfter(LocalDateTime.now())) { throw new AccountLockedException(request.username(), "Account is locked until " + userInfo.getLockedUntil()); } @@ -92,13 +92,17 @@ public CompletableFuture grant(TokenGrantRequestDto reque } } - events.add(new UserAuthSuccessEvent(request.grantType(), userInfo.getId())); - var jwtId = ObjectId.guid().toString(); var iat = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); var exp = LocalDateTime.now().plusHours(24).toEpochSecond(ZoneOffset.UTC); var accessToken = generateToken(jwtId, userInfo, iat, exp); + var event = new UserAuthSuccessEvent(request.grantType(), userInfo.getId()); + event.setGrantTime(LocalDateTime.now()); + event.getData().put("jti", jwtId); + event.getData().put("jwt", accessToken); + events.add(event); + return new TokenGrantResponseDto(accessToken, jwtId, "Bearer", 3600 * 24, iat, userInfo.getUsername(), userInfo.getId()); } catch (Exception e) { diff --git a/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java b/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java index 4ff26d4..44605bd 100644 --- a/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java +++ b/identity/src/main/java/io/theurl/identity/domain/aggregate/Token.java @@ -7,9 +7,9 @@ @SuppressWarnings({"LombokGetterMayBeUsed"}) public class Token extends AggregateRoot { - private String jti; - private String content; - private Long subject; + private final String jti; + private final String content; + private final Long subject; private LocalDateTime issuedAt; private LocalDateTime expiresAt; @@ -20,6 +20,9 @@ public class Token extends AggregateRoot { */ public Token(Long id, String jti, String content, Long subject) { super(id); + this.jti = jti; + this.content = content; + this.subject = subject; } public LocalDateTime getIssuedAt() { @@ -30,15 +33,27 @@ public LocalDateTime getExpiresAt() { return expiresAt; } + public String getContent() { + return content; + } + + public String getJti() { + return jti; + } + + public Long getSubject() { + return subject; + } + public void setIssuedAt(LocalDateTime issuedAt) { - if (issuedAt.isBefore(LocalDateTime.now())) { + if (issuedAt != null && issuedAt.isBefore(LocalDateTime.now())) { throw new IllegalArgumentException("issuedAt must be in the future"); } this.issuedAt = issuedAt; } public void setExpiresAt(LocalDateTime expiresAt) { - if (expiresAt.isBefore(LocalDateTime.now())) { + if (expiresAt != null && expiresAt.isBefore(LocalDateTime.now())) { throw new IllegalArgumentException("expiresAt must be in the future"); } this.expiresAt = expiresAt; From e7a29df96222af66b78720d725fd394d5dca8570 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 12:00:46 +0800 Subject: [PATCH 5/7] Update .gitignore to exclude application property and YAML files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index fb8d881..e331b61 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,7 @@ build/ .vscode/ .env + +application-*.properties +application-*.yml +application-*.yaml From e38da672af81ac0f562ddb63a1c1e6d1c17b5f91 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 12:02:22 +0800 Subject: [PATCH 6/7] Delete identity/src/main/resources/application-dev.yaml --- identity/src/main/resources/application-dev.yaml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 identity/src/main/resources/application-dev.yaml diff --git a/identity/src/main/resources/application-dev.yaml b/identity/src/main/resources/application-dev.yaml deleted file mode 100644 index 26e8472..0000000 --- a/identity/src/main/resources/application-dev.yaml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - datasource: - url: ${DB_URL:jdbc:postgresql://localhost:5432/linkyou?currentSchema=public} - username: ${DB_USERNAME:postgres} - password: ${DB_PASSWORD:nerosoft.8888} From 8bda94bd511ecae623ea1bdebde77c983835b858 Mon Sep 17 00:00:00 2001 From: damon Date: Tue, 26 May 2026 12:02:43 +0800 Subject: [PATCH 7/7] Delete message/src/main/resources/application-dev.yaml --- message/src/main/resources/application-dev.yaml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 message/src/main/resources/application-dev.yaml diff --git a/message/src/main/resources/application-dev.yaml b/message/src/main/resources/application-dev.yaml deleted file mode 100644 index 26e8472..0000000 --- a/message/src/main/resources/application-dev.yaml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - datasource: - url: ${DB_URL:jdbc:postgresql://localhost:5432/linkyou?currentSchema=public} - username: ${DB_USERNAME:postgres} - password: ${DB_PASSWORD:nerosoft.8888}