user service 빌드 성공

This commit is contained in:
cyjadela
2025-10-24 09:34:52 +09:00
parent 607e0ae022
commit ca88d308c8
36 changed files with 4845 additions and 1249 deletions
@@ -0,0 +1,44 @@
package com.unicorn.hgzero.user.config;
import com.azure.messaging.eventhubs.EventHubProducerClient;
import com.azure.messaging.eventhubs.EventHubClientBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Azure EventHub 설정
* 사용자 인증 관련 이벤트 발행을 위한 EventHub 설정
*/
@Slf4j
@Configuration
public class EventHubConfig {
@Value("${spring.cloud.azure.eventhub.connection-string:}")
private String connectionString;
@Value("${spring.cloud.azure.eventhub.name:hgzero-eventhub-name}")
private String eventHubName;
/**
* EventHub Producer Client 빈 생성
* 연결 문자열이 있을 때만 생성
*/
@Bean
@ConditionalOnExpression("'${spring.cloud.azure.eventhub.connection-string:}'.length() > 0")
public EventHubProducerClient eventHubProducerClient() {
try {
EventHubProducerClient client = new EventHubClientBuilder()
.connectionString(connectionString, eventHubName)
.buildProducerClient();
log.info("EventHub Producer Client 생성 완료: {}", eventHubName);
return client;
} catch (Exception e) {
log.error("EventHub Producer Client 생성 실패: {}", e.getMessage());
throw new RuntimeException("EventHub Producer Client 생성 실패", e);
}
}
}
@@ -46,7 +46,7 @@ public class JwtTokenProvider {
public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(secretKey)
.verifyWith(secretKey)
.build()
.parseClaimsJws(token);
return true;
@@ -67,7 +67,7 @@ public class JwtTokenProvider {
*/
public String getUserId(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.verifyWith(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
@@ -80,7 +80,7 @@ public class JwtTokenProvider {
*/
public String getUsername(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.verifyWith(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
@@ -93,7 +93,7 @@ public class JwtTokenProvider {
*/
public String getAuthority(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.verifyWith(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
@@ -107,7 +107,7 @@ public class JwtTokenProvider {
public boolean isTokenExpired(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.verifyWith(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
@@ -123,7 +123,7 @@ public class JwtTokenProvider {
*/
public Date getExpirationDate(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.verifyWith(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
@@ -0,0 +1,33 @@
package com.unicorn.hgzero.user.service;
/**
* 이벤트 발행 서비스 인터페이스
* 사용자 인증 관련 이벤트를 외부 시스템에 발행
*/
public interface EventPublishService {
/**
* 로그인 이벤트 발행
*
* @param userId 사용자 ID
* @param username 사용자명
* @param timestamp 로그인 시각
*/
void publishLoginEvent(String userId, String username, long timestamp);
/**
* 로그아웃 이벤트 발행
*
* @param userId 사용자 ID
* @param timestamp 로그아웃 시각
*/
void publishLogoutEvent(String userId, long timestamp);
/**
* 토큰 갱신 이벤트 발행
*
* @param userId 사용자 ID
* @param timestamp 토큰 갱신 시각
*/
void publishTokenRefreshEvent(String userId, long timestamp);
}
@@ -0,0 +1,89 @@
package com.unicorn.hgzero.user.service;
import com.azure.messaging.eventhubs.EventData;
import com.azure.messaging.eventhubs.EventHubProducerClient;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
/**
* 이벤트 발행 서비스 구현체
* Azure EventHub를 통한 사용자 인증 이벤트 발행
*/
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnBean(EventHubProducerClient.class)
public class EventPublishServiceImpl implements EventPublishService {
private final EventHubProducerClient eventHubProducerClient;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void publishLoginEvent(String userId, String username, long timestamp) {
Map<String, Object> eventData = new HashMap<>();
eventData.put("eventType", "USER_LOGIN");
eventData.put("userId", userId);
eventData.put("username", username);
eventData.put("timestamp", timestamp);
eventData.put("eventTime", Instant.ofEpochMilli(timestamp).toString());
publishEvent(eventData, "로그인 이벤트");
}
@Override
public void publishLogoutEvent(String userId, long timestamp) {
Map<String, Object> eventData = new HashMap<>();
eventData.put("eventType", "USER_LOGOUT");
eventData.put("userId", userId);
eventData.put("timestamp", timestamp);
eventData.put("eventTime", Instant.ofEpochMilli(timestamp).toString());
publishEvent(eventData, "로그아웃 이벤트");
}
@Override
public void publishTokenRefreshEvent(String userId, long timestamp) {
Map<String, Object> eventData = new HashMap<>();
eventData.put("eventType", "TOKEN_REFRESH");
eventData.put("userId", userId);
eventData.put("timestamp", timestamp);
eventData.put("eventTime", Instant.ofEpochMilli(timestamp).toString());
publishEvent(eventData, "토큰 갱신 이벤트");
}
/**
* EventHub로 이벤트 발행
*
* @param eventData 이벤트 데이터
* @param eventDescription 이벤트 설명 (로깅용)
*/
private void publishEvent(Map<String, Object> eventData, String eventDescription) {
if (eventHubProducerClient == null) {
log.debug("EventHub Producer Client가 없어 {} 발행을 건너뜀", eventDescription);
return;
}
try {
String jsonData = objectMapper.writeValueAsString(eventData);
EventData event = new EventData(jsonData);
// EventData를 Iterable로 감싸서 전송
eventHubProducerClient.send(java.util.Collections.singletonList(event));
log.debug("{} 발행 완료: {}", eventDescription, eventData.get("userId"));
} catch (JsonProcessingException e) {
log.error("{} JSON 변환 실패: {}", eventDescription, e.getMessage());
} catch (Exception e) {
log.error("{} 발행 실패: {}", eventDescription, e.getMessage());
}
}
}
@@ -0,0 +1,30 @@
package com.unicorn.hgzero.user.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;
/**
* No-Op 이벤트 발행 서비스
* EventHub가 설정되지 않은 경우 사용되는 기본 구현체
*/
@Slf4j
@Service
@ConditionalOnMissingBean(EventPublishServiceImpl.class)
public class NoOpEventPublishService implements EventPublishService {
@Override
public void publishLoginEvent(String userId, String username, long timestamp) {
log.debug("EventHub 미설정으로 로그인 이벤트 발행 건너뜀: userId={}", userId);
}
@Override
public void publishLogoutEvent(String userId, long timestamp) {
log.debug("EventHub 미설정으로 로그아웃 이벤트 발행 건너뜀: userId={}", userId);
}
@Override
public void publishTokenRefreshEvent(String userId, long timestamp) {
log.debug("EventHub 미설정으로 토큰 갱신 이벤트 발행 건너뜀: userId={}", userId);
}
}
@@ -41,6 +41,7 @@ public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RedisTemplate<String, String> redisTemplate;
private final EventPublishService eventPublishService;
@Value("${jwt.secret}")
private String jwtSecret;
@@ -123,6 +124,9 @@ public class UserServiceImpl implements UserService {
saveRefreshToken(user.getUserId(), refreshToken);
log.info("로그인 성공: userId={}", request.getUserId());
// 로그인 이벤트 발행
eventPublishService.publishLoginEvent(user.getUserId(), user.getUsername(), System.currentTimeMillis());
return LoginResponse.builder()
.accessToken(accessToken)
@@ -173,6 +177,9 @@ public class UserServiceImpl implements UserService {
String newAccessToken = generateAccessToken(user);
log.info("토큰 갱신 성공: userId={}", userId);
// 토큰 갱신 이벤트 발행
eventPublishService.publishTokenRefreshEvent(userId, System.currentTimeMillis());
return RefreshTokenResponse.builder()
.accessToken(newAccessToken)
@@ -191,6 +198,9 @@ public class UserServiceImpl implements UserService {
// Redis에서 Refresh Token 삭제
deleteRefreshToken(userId);
// 로그아웃 이벤트 발행
eventPublishService.publishLogoutEvent(userId, System.currentTimeMillis());
log.info("로그아웃 완료: userId={}", userId);
}
+15 -8
View File
@@ -8,7 +8,7 @@ spring:
datasource:
url: jdbc:${DB_KIND:postgresql}://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:userdb}
username: ${DB_USERNAME:hgzerouser}
password: ${DB_PASSWORD:}
password: ${DB_PASSWORD:Hi5Jessica!}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 20
@@ -25,8 +25,13 @@ spring:
hibernate:
format_sql: true
use_sql_comments: true
dialect: org.hibernate.dialect.PostgreSQLDialect
jdbc:
time_zone: UTC
hibernate:
ddl-auto: ${JPA_DDL_AUTO:update}
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
# Redis Configuration
data:
@@ -43,13 +48,13 @@ spring:
max-wait: -1ms
database: ${REDIS_DATABASE:0}
# LDAP Configuration
ldap:
urls: ${LDAP_URLS:ldaps://ldap.example.com:636}
base: ${LDAP_BASE:dc=example,dc=com}
username: ${LDAP_USERNAME:}
password: ${LDAP_PASSWORD:}
user-dn-pattern: ${LDAP_USER_DN_PATTERN:uid={0},ou=people}
# LDAP Configuration (비활성화 for development)
# ldap:
# urls: ${LDAP_URLS:ldaps://ldap.example.com:636}
# base: ${LDAP_BASE:dc=example,dc=com}
# username: ${LDAP_USERNAME:}
# password: ${LDAP_PASSWORD:}
# user-dn-pattern: ${LDAP_USER_DN_PATTERN:uid={0},ou=people}
# Event Configuration (Azure EventHub)
cloud:
@@ -96,6 +101,8 @@ management:
enabled: true
readinessState:
enabled: true
ldap:
enabled: false
# OpenAPI Documentation
springdoc: