diff --git a/config/pom.xml b/config/pom.xml
index 76ef508..285f24f 100644
--- a/config/pom.xml
+++ b/config/pom.xml
@@ -19,6 +19,10 @@
org.springframework.cloud
spring-cloud-config-server
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
org.springframework.boot
diff --git a/framework/pom.xml b/framework/pom.xml
index 0c1cf48..0fb73e7 100644
--- a/framework/pom.xml
+++ b/framework/pom.xml
@@ -12,12 +12,50 @@
framework
+
+ 7.0.6
+
+
+
+ com.neroyun
+ mediator
+ ${neroyun.mediator.version}
+
+
+ org.springframework
+ spring-core
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-context
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-framework.version}
+
+
+ org.springframework
+ spring-web
+ ${spring-framework.version}
+
org.bouncycastle
bcprov-jdk18on
1.84
+
+ jakarta.persistence
+ jakarta.persistence-api
+ ${jakarta-persistence.version}
+
+
+ org.slf4j
+ slf4j-api
+
org.junit.jupiter
junit-jupiter
diff --git a/framework/src/main/java/io/theurl/framework/application/ApplicationService.java b/framework/src/main/java/io/theurl/framework/application/ApplicationService.java
new file mode 100644
index 0000000..f95f4f3
--- /dev/null
+++ b/framework/src/main/java/io/theurl/framework/application/ApplicationService.java
@@ -0,0 +1,7 @@
+package io.theurl.framework.application;
+
+/**
+ * Marker interface for application services. Application services are responsible for orchestrating use cases and business logic.
+ */
+public interface ApplicationService {
+}
diff --git a/framework/src/main/java/io/theurl/framework/application/BaseApplicationService.java b/framework/src/main/java/io/theurl/framework/application/BaseApplicationService.java
new file mode 100644
index 0000000..c1342ff
--- /dev/null
+++ b/framework/src/main/java/io/theurl/framework/application/BaseApplicationService.java
@@ -0,0 +1,31 @@
+package io.theurl.framework.application;
+
+import com.neroyun.mediator.Mediator;
+import org.springframework.context.ApplicationContext;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.context.annotation.RequestScope;
+
+import java.util.concurrent.CompletionException;
+import java.util.function.Consumer;
+
+@Service
+@RequestScope
+public class BaseApplicationService implements ApplicationService {
+ protected Mediator mediator;
+
+ protected ThreadPoolTaskExecutor mediatorTaskExecutor;
+
+ protected BaseApplicationService(ApplicationContext applicationContext) {
+ mediator = applicationContext.getBean(Mediator.class);
+ mediatorTaskExecutor = applicationContext.getBean(ThreadPoolTaskExecutor.class);
+ }
+
+ protected void handleException(Throwable throwable, Consumer consumer) {
+ if (throwable instanceof CompletionException exception) {
+ handleException(exception.getCause(), consumer);
+ } else {
+ consumer.accept(throwable);
+ }
+ }
+}
diff --git a/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java b/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java
new file mode 100644
index 0000000..95a3525
--- /dev/null
+++ b/framework/src/main/java/io/theurl/framework/configure/GlobalExceptionHandler.java
@@ -0,0 +1,100 @@
+package io.theurl.framework.configure;
+
+import com.neroyun.mediator.internal.AggregateException;
+import com.neroyun.mediator.validation.ValidationException;
+import io.theurl.framework.security.AccountException;
+import io.theurl.framework.security.CredentialException;
+import io.theurl.framework.security.UnauthorizedAccessException;
+import jakarta.persistence.EntityNotFoundException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.CompletionException;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ private final String name = "message";
+ private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+ @ExceptionHandler(AccountException.class)
+ public ResponseEntity
org.springframework.boot
- spring-boot-starter-webflux
+ spring-boot-starter-web
org.springdoc
- springdoc-openapi-starter-webflux-ui
+ springdoc-openapi-starter-webmvc-ui
3.0.3
@@ -91,7 +91,7 @@
org.springframework.boot
- spring-boot-starter-webflux-test
+ spring-boot-starter-webmvc-test
test
diff --git a/identity/src/main/java/io/theurl/identity/IdentityApplication.java b/identity/src/main/java/io/theurl/identity/IdentityApplication.java
index 6d50fa5..188d4b4 100644
--- a/identity/src/main/java/io/theurl/identity/IdentityApplication.java
+++ b/identity/src/main/java/io/theurl/identity/IdentityApplication.java
@@ -2,10 +2,16 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScans;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
+@ComponentScans({
+ @ComponentScan("io.theurl.framework"),
+ @ComponentScan("io.theurl.identity")
+})
public class IdentityApplication {
public static void main(String[] args) {
diff --git a/identity/src/main/java/io/theurl/identity/external/ExternalAuthProvider.java b/identity/src/main/java/io/theurl/identity/external/ExternalAuthProvider.java
index 742df87..9a0e329 100644
--- a/identity/src/main/java/io/theurl/identity/external/ExternalAuthProvider.java
+++ b/identity/src/main/java/io/theurl/identity/external/ExternalAuthProvider.java
@@ -1,5 +1,7 @@
package io.theurl.identity.external;
+import java.util.concurrent.CompletableFuture;
+
/**
* Defines the contract for external authentication providers.
* Implementations of this interface should handle the authentication process with external services.
@@ -10,5 +12,5 @@ public interface ExternalAuthProvider {
* @param authCode The authentication code received from the external service.
* @return The result of the authentication process encapsulated in an ExternalAuthResult object.
*/
- ExternalAuthResult authenticate(String authCode);
+ CompletableFuture authenticateAsync(String authCode);
}
diff --git a/identity/src/main/java/io/theurl/identity/external/FacebookAuthProvider.java b/identity/src/main/java/io/theurl/identity/external/FacebookAuthProvider.java
index 1f79a91..874b6a7 100644
--- a/identity/src/main/java/io/theurl/identity/external/FacebookAuthProvider.java
+++ b/identity/src/main/java/io/theurl/identity/external/FacebookAuthProvider.java
@@ -4,6 +4,7 @@
import org.springframework.stereotype.Component;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
@Component("external-auth-provider-facebook")
public class FacebookAuthProvider extends BaseAuthProvider {
@@ -16,31 +17,33 @@ public class FacebookAuthProvider extends BaseAuthProvider {
private String redirectUri;
@Override
- public ExternalAuthResult authenticate(String authCode) {
- var token = getToken("https://graph.facebook.com//v22.0/oauth/access_token",
- Map.of(
- "client_id", clientId,
- "client_secret", clientSecret,
- "code", authCode,
- "redirect_uri", redirectUri
- ), "query");
-
- var userInfo = getUserInfo(token, "https://graph.facebook.com/v12.0/me?fields=id,name,email,picture");
- return new ExternalAuthResult() {
- @Override
- public String getId() {
- return userInfo.get("id").asText();
- }
-
- @Override
- public String getEmail() {
- return userInfo.get("email").asText();
- }
-
- @Override
- public String getNickname() {
- return userInfo.get("name").asText();
- }
- };
+ public CompletableFuture authenticateAsync(String authCode) {
+ return CompletableFuture.supplyAsync(() -> {
+ var token = getToken("https://graph.facebook.com//v22.0/oauth/access_token",
+ Map.of(
+ "client_id", clientId,
+ "client_secret", clientSecret,
+ "code", authCode,
+ "redirect_uri", redirectUri
+ ), "query");
+
+ var userInfo = getUserInfo(token, "https://graph.facebook.com/v12.0/me?fields=id,name,email,picture");
+ return new ExternalAuthResult() {
+ @Override
+ public String getId() {
+ return userInfo.get("id").asText();
+ }
+
+ @Override
+ public String getEmail() {
+ return userInfo.get("email").asText();
+ }
+
+ @Override
+ public String getNickname() {
+ return userInfo.get("name").asText();
+ }
+ };
+ });
}
}
diff --git a/identity/src/main/java/io/theurl/identity/external/GithubAuthProvider.java b/identity/src/main/java/io/theurl/identity/external/GithubAuthProvider.java
index a8e62a9..2fca186 100644
--- a/identity/src/main/java/io/theurl/identity/external/GithubAuthProvider.java
+++ b/identity/src/main/java/io/theurl/identity/external/GithubAuthProvider.java
@@ -4,6 +4,7 @@
import org.springframework.stereotype.Component;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
@Component("external-auth-provider-github")
public class GithubAuthProvider extends BaseAuthProvider {
@@ -14,38 +15,40 @@ public class GithubAuthProvider extends BaseAuthProvider {
private String secret;
@Override
- public ExternalAuthResult authenticate(String authCode) {
- var token = getToken("https://github.com/login/oauth/access_token", Map.of(
- "client_id", clientId,
- "client_secret", secret,
- "code", authCode
- ), "query");
- var user = getUserInfo(token, "https://api.github.com/user");
- return new ExternalAuthResult() {
- @Override
- public String getId() {
- return user.findValue("id").asText();
- }
-
- @Override
- public String getUsername() {
- return user.findValue("login").asText();
- }
-
- @Override
- public String getNickname() {
- return user.findValue("name").asText();
- }
-
- @Override
- public String getEmail() {
- return user.findValue("email").asText();
- }
-
- @Override
- public String getAvatarUrl() {
- return user.findValue("avatar_url").asText();
- }
- };
+ public CompletableFuture authenticateAsync(String authCode) {
+ return CompletableFuture.supplyAsync(() -> {
+ var token = getToken("https://github.com/login/oauth/access_token", Map.of(
+ "client_id", clientId,
+ "client_secret", secret,
+ "code", authCode
+ ), "query");
+ var user = getUserInfo(token, "https://api.github.com/user");
+ return new ExternalAuthResult() {
+ @Override
+ public String getId() {
+ return user.findValue("id").asText();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.findValue("login").asText();
+ }
+
+ @Override
+ public String getNickname() {
+ return user.findValue("name").asText();
+ }
+
+ @Override
+ public String getEmail() {
+ return user.findValue("email").asText();
+ }
+
+ @Override
+ public String getAvatarUrl() {
+ return user.findValue("avatar_url").asText();
+ }
+ };
+ });
}
}
diff --git a/identity/src/main/java/io/theurl/identity/external/GoogleAuthProvider.java b/identity/src/main/java/io/theurl/identity/external/GoogleAuthProvider.java
index 917de7b..232dfec 100644
--- a/identity/src/main/java/io/theurl/identity/external/GoogleAuthProvider.java
+++ b/identity/src/main/java/io/theurl/identity/external/GoogleAuthProvider.java
@@ -4,6 +4,7 @@
import org.springframework.stereotype.Component;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
@Component("external-auth-provider-google")
public class GoogleAuthProvider extends BaseAuthProvider {
@@ -16,43 +17,45 @@ public class GoogleAuthProvider extends BaseAuthProvider {
private String redirectUri;
@Override
- public ExternalAuthResult authenticate(String authCode) {
- var tokenParams = Map.of(
- "client_id", clientId,
- "client_secret", clientSecret,
- "redirect_uri", redirectUri,
- "code", authCode,
- "grant_type", "authorization_code");
-
- var token = getToken("https://oauth2.googleapis.com/token", tokenParams, "form");
-
- var user = getUserInfo(token, "https://www.googleapis.com/oauth2/v3/userinfo");
-
- return new ExternalAuthResult() {
- @Override
- public String getId() {
- return user.findValue("sub").asText();
- }
-
- @Override
- public String getUsername() {
- return user.findValue("email").asText();
- }
-
- @Override
- public String getNickname() {
- return user.findValue("name").asText();
- }
-
- @Override
- public String getEmail() {
- return user.findValue("email").asText();
- }
-
- @Override
- public String getAvatarUrl() {
- return user.findValue("picture").asText();
- }
- };
+ public CompletableFuture authenticateAsync(String authCode) {
+ return CompletableFuture.supplyAsync(() -> {
+ var tokenParams = Map.of(
+ "client_id", clientId,
+ "client_secret", clientSecret,
+ "redirect_uri", redirectUri,
+ "code", authCode,
+ "grant_type", "authorization_code");
+
+ var token = getToken("https://oauth2.googleapis.com/token", tokenParams, "form");
+
+ var user = getUserInfo(token, "https://www.googleapis.com/oauth2/v3/userinfo");
+
+ return new ExternalAuthResult() {
+ @Override
+ public String getId() {
+ return user.findValue("sub").asText();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.findValue("email").asText();
+ }
+
+ @Override
+ public String getNickname() {
+ return user.findValue("name").asText();
+ }
+
+ @Override
+ public String getEmail() {
+ return user.findValue("email").asText();
+ }
+
+ @Override
+ public String getAvatarUrl() {
+ return user.findValue("picture").asText();
+ }
+ };
+ });
}
}
diff --git a/identity/src/main/java/io/theurl/identity/external/MicrosoftAuthProvider.java b/identity/src/main/java/io/theurl/identity/external/MicrosoftAuthProvider.java
index b5ce883..d83c55c 100644
--- a/identity/src/main/java/io/theurl/identity/external/MicrosoftAuthProvider.java
+++ b/identity/src/main/java/io/theurl/identity/external/MicrosoftAuthProvider.java
@@ -4,6 +4,7 @@
import org.springframework.stereotype.Component;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
@Component("external-auth-provider-microsoft")
public class MicrosoftAuthProvider extends BaseAuthProvider {
@@ -15,43 +16,45 @@ public class MicrosoftAuthProvider extends BaseAuthProvider {
private String redirectUri;
@Override
- public ExternalAuthResult authenticate(String authCode) {
- Map getTokenParams = Map.of(
- "client_id", clientId,
- "client_secret", clientSecret,
- "code", authCode,
- "redirect_uri", redirectUri,
- "grant_type", "authorization_code",
- "scope", "User.Read Mail.Read"
- );
-
- var token = getToken("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", getTokenParams, "form");
- var user = getUserInfo(token, "https://graph.microsoft.com/v1.0/me");
- return new ExternalAuthResult() {
- @Override
- public String getId() {
- return user.findValue("id").asText();
- }
-
- @Override
- public String getUsername() {
- return user.findValue("userPrincipalName").asText();
- }
-
- @Override
- public String getNickname() {
- return user.findValue("displayName").asText();
- }
-
- @Override
- public String getEmail() {
- return user.findValue("email").asText();
- }
-
- @Override
- public String getPhone() {
- return user.findValue("mobilePhone").asText();
- }
- };
+ public CompletableFuture authenticateAsync(String authCode) {
+ return CompletableFuture.supplyAsync(() -> {
+ Map getTokenParams = Map.of(
+ "client_id", clientId,
+ "client_secret", clientSecret,
+ "code", authCode,
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "scope", "User.Read Mail.Read"
+ );
+
+ var token = getToken("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", getTokenParams, "form");
+ var user = getUserInfo(token, "https://graph.microsoft.com/v1.0/me");
+ return new ExternalAuthResult() {
+ @Override
+ public String getId() {
+ return user.findValue("id").asText();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.findValue("userPrincipalName").asText();
+ }
+
+ @Override
+ public String getNickname() {
+ return user.findValue("displayName").asText();
+ }
+
+ @Override
+ public String getEmail() {
+ return user.findValue("email").asText();
+ }
+
+ @Override
+ public String getPhone() {
+ return user.findValue("mobilePhone").asText();
+ }
+ };
+ });
}
}
diff --git a/identity/src/main/resources/application.yaml b/identity/src/main/resources/application.yaml
index d682a50..00d39a6 100644
--- a/identity/src/main/resources/application.yaml
+++ b/identity/src/main/resources/application.yaml
@@ -22,11 +22,19 @@ spring:
ddl-auto: update
dialect: ${DB_DIALECT:org.hibernate.dialect.PostgreSQLDialect}
show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
data:
redis:
url: ${REDIS_URL:redis://localhost:6379}
mongodb:
uri: ${MONGO_URI:mongodb://localhost:27017/linkyou}
+ web:
+ error:
+ include-message: ALWAYS
+ include-exception: true
+ include-stacktrace: ALWAYS
external-auth:
redirect-uri: "https://theurl.io/auth/callback"
@@ -43,10 +51,16 @@ external-auth:
client-id: ${MICROSOFT_CLIENT_ID:your-microsoft-client-id}
client-secret: ${MICROSOFT_CLIENT_SECRET:your-microsoft-client-secret}
+jwt:
+ secret: ${JWT_SECRET:your-jwt-secret}
+ issuer: theurl.io
+ expiration: 3600 # in seconds
+
logging:
file:
+ name: #{level}-{T(java.time.LocalDate).now()}.log
path: logs
level:
io.theurl.identity: debug
- org.springframework: info
+ org.springframework: debug
root: info
diff --git a/identity/src/main/resources/logback-spring.xml b/identity/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..4e7ae83
--- /dev/null
+++ b/identity/src/main/resources/logback-spring.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
+ utf8
+
+
+
+
+ ${LOG_PATH}/info/current.log
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+ ${LOG_PATH}/info/%d{yyyy-MM-dd}.log
+ 100MB
+ 30
+
+
+
+
+ ${LOG_PATH}/error/current.log
+
+ ERROR
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+ ${LOG_PATH}/error/%d{yyyy-MM-dd}.log
+ 100MB
+ 30
+
+
+
+
+ ${LOG_PATH}/warn/current.log
+
+ WARN
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+ ${LOG_PATH}/warn/%d{yyyy-MM-dd}.log
+ 100MB
+ 30
+
+
+
+
+ ${LOG_PATH}/debug/current.log
+
+ DEBUG
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+ ${LOG_PATH}/debug/%d{yyyy-MM-dd}.log
+ 100MB
+ 30
+
+
+
+
+ ${LOG_PATH}/sql/current.log
+
+ DEBUG
+ ACCEPT
+ DENY
+
+
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+ ${LOG_PATH}/sql/%d{yyyy-MM-dd}.log
+ 100MB
+ 30
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/message/pom.xml b/message/pom.xml
index cc9fa41..fc8ce14 100644
--- a/message/pom.xml
+++ b/message/pom.xml
@@ -31,11 +31,11 @@
org.springframework.boot
- spring-boot-starter-webflux
+ spring-boot-starter-web
org.springdoc
- springdoc-openapi-starter-webflux-ui
+ springdoc-openapi-starter-webmvc-ui
3.0.3
@@ -80,7 +80,7 @@
org.springframework.boot
- spring-boot-starter-webflux-test
+ spring-boot-starter-webmvc-test
test
diff --git a/pom.xml b/pom.xml
index fd5231f..f5e24e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,7 +50,8 @@
2025.1.0.0
4.0.6
true
- 1.1.0
+ 1.1.1
+ 3.2.0
@@ -77,7 +78,7 @@
org.springdoc
- springdoc-openapi-starter-webflux-ui
+ springdoc-openapi-starter-webmvc-ui
3.0.3
pom
import
@@ -96,6 +97,20 @@
pom
import
+
+ jakarta.persistence
+ jakarta.persistence-api
+ ${jakarta-persistence.version}
+ pom
+ import
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.7
+ pom
+ import
+