feat : initial commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
// user-service/build.gradle
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
|
||||
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
}
|
||||
|
||||
// jar version
|
||||
allprojects {
|
||||
group = 'com.healthsync'
|
||||
version = '1.0.0' // 원하는 버전으로 설정
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthsync;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class UserServiceApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.healthsync.common.dto;
|
||||
|
||||
public class ApiResponse<T> {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private T data;
|
||||
private String error;
|
||||
|
||||
public ApiResponse() {}
|
||||
|
||||
public ApiResponse(boolean success, String message, T data) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ApiResponse(boolean success, String message, String error) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(T data, String message) {
|
||||
return new ApiResponse<>(true, message, data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message, String error) {
|
||||
return new ApiResponse<>(false, message, error);
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
|
||||
public T getData() { return data; }
|
||||
public void setData(T data) { this.data = data; }
|
||||
|
||||
public String getError() { return error; }
|
||||
public void setError(String error) { this.error = error; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
public class CustomException extends RuntimeException {
|
||||
public CustomException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CustomException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import com.healthsync.common.dto.ApiResponse;
|
||||
import com.healthsync.common.response.ResponseHelper;
|
||||
import com.healthsync.user.exception.AuthenticationException;
|
||||
import com.healthsync.user.exception.UserNotFoundException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(UserNotFoundException.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleUserNotFoundException(UserNotFoundException e) {
|
||||
logger.error("User not found: {}", e.getMessage());
|
||||
return ResponseHelper.notFound("사용자를 찾을 수 없습니다", e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(AuthenticationException.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleAuthenticationException(AuthenticationException e) {
|
||||
logger.error("Authentication error: {}", e.getMessage());
|
||||
return ResponseHelper.unauthorized("인증에 실패했습니다", e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleAccessDeniedException(AccessDeniedException e) {
|
||||
logger.error("Access denied: {}", e.getMessage());
|
||||
return ResponseHelper.forbidden("접근이 거부되었습니다", e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiResponse<Void>> handleGenericException(Exception e) {
|
||||
logger.error("Unexpected error: {}", e.getMessage(), e);
|
||||
return ResponseHelper.internalServerError("서버 오류가 발생했습니다", e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.healthsync.common.response;
|
||||
|
||||
import com.healthsync.common.dto.ApiResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
public class ResponseHelper {
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> success(T data, String message) {
|
||||
return ResponseEntity.ok(ApiResponse.success(data, message));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> created(T data, String message) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(data, message));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> badRequest(String message, String error) {
|
||||
return ResponseEntity.badRequest().body(ApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> unauthorized(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> forbidden(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> notFound(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<ApiResponse<T>> internalServerError(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.error(message, error));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.healthsync.common.util;
|
||||
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
private final JwtDecoder jwtDecoder;
|
||||
|
||||
public JwtUtil(JwtDecoder jwtDecoder) {
|
||||
this.jwtDecoder = jwtDecoder;
|
||||
}
|
||||
|
||||
public Jwt parseToken(String token) {
|
||||
return jwtDecoder.decode(token);
|
||||
}
|
||||
|
||||
public String getUserIdFromToken(String token) {
|
||||
Jwt jwt = parseToken(token);
|
||||
return jwt.getSubject();
|
||||
}
|
||||
|
||||
public String getEmailFromToken(String token) {
|
||||
Jwt jwt = parseToken(token);
|
||||
return jwt.getClaimAsString("email");
|
||||
}
|
||||
|
||||
public String getNameFromToken(String token) {
|
||||
Jwt jwt = parseToken(token);
|
||||
return jwt.getClaimAsString("name");
|
||||
}
|
||||
|
||||
public String getRoleFromToken(String token) {
|
||||
Jwt jwt = parseToken(token);
|
||||
return jwt.getClaimAsString("role");
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 생년월일 추출
|
||||
*/
|
||||
public LocalDate getBirthDateFromToken(String token) {
|
||||
Jwt jwt = parseToken(token);
|
||||
String birthDateStr = jwt.getClaimAsString("birthDate");
|
||||
if (birthDateStr != null) {
|
||||
return LocalDate.parse(birthDateStr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 생년월일 추출 (Jwt 객체 사용)
|
||||
*/
|
||||
public LocalDate getBirthDateFromJwt(Jwt jwt) {
|
||||
String birthDateStr = jwt.getClaimAsString("birthDate");
|
||||
if (birthDateStr != null) {
|
||||
return LocalDate.parse(birthDateStr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 이름 추출 (Jwt 객체 사용)
|
||||
*/
|
||||
public String getNameFromJwt(Jwt jwt) {
|
||||
return jwt.getClaimAsString("name");
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 Google ID 추출 (Jwt 객체 사용)
|
||||
*/
|
||||
public String getGoogleIdFromJwt(Jwt jwt) {
|
||||
return jwt.getClaimAsString("googleId");
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 사용자 ID 추출 (Jwt 객체 사용)
|
||||
*/
|
||||
public Long getMemberSerialNumberFromJwt(Jwt jwt) {
|
||||
return Long.valueOf(jwt.getSubject());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.healthsync.user.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OAuth2Config {
|
||||
// OAuth2 관련 추가 설정이 필요한 경우 여기에 추가
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.healthsync.user.config;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import com.healthsync.user.domain.Oauth.RefreshToken;
|
||||
import com.healthsync.user.service.Oauth.JwtTokenService;
|
||||
import com.healthsync.user.service.UserProfile.UserService;
|
||||
import com.healthsync.user.service.Oauth.RefreshTokenService;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OAuth2LoginSuccessHandler.class);
|
||||
|
||||
private final UserService userService;
|
||||
private final JwtTokenService jwtTokenService;
|
||||
private final RefreshTokenService refreshTokenService;
|
||||
|
||||
@Value("${app.oauth2.redirect-url}")
|
||||
private String redirectUrl;
|
||||
|
||||
public OAuth2LoginSuccessHandler(UserService userService,
|
||||
JwtTokenService jwtTokenService,
|
||||
RefreshTokenService refreshTokenService) {
|
||||
this.userService = userService;
|
||||
this.jwtTokenService = jwtTokenService;
|
||||
this.refreshTokenService = refreshTokenService;
|
||||
|
||||
// redirectUrl은 @PostConstruct에서 설정
|
||||
}
|
||||
|
||||
@jakarta.annotation.PostConstruct
|
||||
public void init() {
|
||||
// application.yml에서 주입받은 값으로 기본 URL 설정
|
||||
setDefaultTargetUrl(redirectUrl);
|
||||
setAlwaysUseDefaultTargetUrl(true);
|
||||
logger.info("OAuth2 리다이렉트 URL 설정: {}", redirectUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
|
||||
logger.info("OAuth2 로그인 성공 처리 시작");
|
||||
|
||||
if (authentication.getPrincipal() instanceof OAuth2User oauth2User) {
|
||||
try {
|
||||
String googleId = oauth2User.getAttribute("sub");
|
||||
String name = oauth2User.getAttribute("name");
|
||||
|
||||
logger.info("OAuth2 사용자 정보 - Google ID: {}, Name: {}", googleId, name);
|
||||
|
||||
// 기존 사용자 확인
|
||||
Optional<User> existingUser = userService.findByGoogleId(googleId);
|
||||
boolean isNewUser = existingUser.isEmpty();
|
||||
|
||||
User user;
|
||||
if (isNewUser) {
|
||||
logger.info("신규 사용자 생성 - Google ID: {}", googleId);
|
||||
User newUser = new User(googleId, name, LocalDate.of(1000, 1, 1), null);
|
||||
user = userService.saveUser(newUser);
|
||||
logger.info("신규 사용자 저장 완료 - Member Serial Number: {}", user.getMemberSerialNumber());
|
||||
} else {
|
||||
user = existingUser.get();
|
||||
logger.info("기존 사용자 로그인 - Member Serial Number: {}", user.getMemberSerialNumber());
|
||||
userService.updateLastLoginAt(user.getMemberSerialNumber());
|
||||
user = userService.findById(user.getMemberSerialNumber()).orElse(user);
|
||||
|
||||
boolean isDeault = user.getBirthDate().equals(LocalDate.of(1000, 1, 1));
|
||||
if(isDeault){
|
||||
isNewUser = true;
|
||||
}
|
||||
}
|
||||
|
||||
// JWT 토큰 생성
|
||||
String accessToken = jwtTokenService.generateAccessToken(user);
|
||||
RefreshToken refreshToken = refreshTokenService.createRefreshToken(user.getMemberSerialNumber());
|
||||
|
||||
// URL에 토큰 정보 추가하여 리다이렉트
|
||||
String finalRedirectUrl = String.format(
|
||||
"%s?success=true&isNewUser=%s&accessToken=%s&refreshToken=%s&userId=%d",
|
||||
redirectUrl,
|
||||
isNewUser,
|
||||
accessToken,
|
||||
refreshToken.getToken(),
|
||||
user.getMemberSerialNumber()
|
||||
);
|
||||
|
||||
logger.info("OAuth2 로그인 처리 완료 - Member Serial Number: {}, 신규 사용자: {}, 리다이렉트: {}",
|
||||
user.getMemberSerialNumber(), isNewUser, redirectUrl);
|
||||
|
||||
// SimpleUrlAuthenticationSuccessHandler의 리다이렉트 기능 사용
|
||||
getRedirectStrategy().sendRedirect(request, response, finalRedirectUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("OAuth2 로그인 처리 중 오류 발생", e);
|
||||
// 에러 시에도 프론트엔드로 리다이렉트
|
||||
String errorUrl = redirectUrl + "?success=false&error=" +
|
||||
java.net.URLEncoder.encode(e.getMessage(), "UTF-8");
|
||||
getRedirectStrategy().sendRedirect(request, response, errorUrl);
|
||||
}
|
||||
} else {
|
||||
logger.error("OAuth2User 정보를 가져올 수 없음");
|
||||
String errorUrl = redirectUrl + "?success=false&error=" +
|
||||
java.net.URLEncoder.encode("OAuth2 인증 정보를 찾을 수 없습니다", "UTF-8");
|
||||
getRedirectStrategy().sendRedirect(request, response, errorUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.healthsync.user.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class ObjectMapperConfig {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.healthsync.user.config;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
@Configuration
|
||||
public class UserJwtConfig {
|
||||
|
||||
|
||||
@Value("${jwt.private-key}")
|
||||
private String privateKeyString;
|
||||
|
||||
@Value("${jwt.public-key}")
|
||||
private String publicKeyString;
|
||||
|
||||
@Bean
|
||||
public RSAPrivateKey rsaPrivateKey() {
|
||||
try {
|
||||
// "-----BEGIN PRIVATE KEY-----"와 "-----END PRIVATE KEY-----" 제거
|
||||
String privateKeyPEM = privateKeyString
|
||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.replace("-----END PRIVATE KEY-----", "")
|
||||
.replaceAll("\\s", "");
|
||||
|
||||
byte[] decoded = Base64.getDecoder().decode(privateKeyPEM);
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
return (RSAPrivateKey) keyFactory.generatePrivate(spec);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to load RSA private key", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RSAPublicKey rsaPublicKey() {
|
||||
try {
|
||||
// "-----BEGIN PUBLIC KEY-----"와 "-----END PUBLIC KEY-----" 제거
|
||||
String publicKeyPEM = publicKeyString
|
||||
.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replace("-----END PUBLIC KEY-----", "")
|
||||
.replaceAll("\\s", "");
|
||||
|
||||
byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
return (RSAPublicKey) keyFactory.generatePublic(spec);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to load RSA public key", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JWKSource<SecurityContext> jwkSource(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
|
||||
JWK jwk = new RSAKey.Builder(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.keyID("healthsync-key-id")
|
||||
.build();
|
||||
|
||||
JWKSet jwkSet = new JWKSet(jwk);
|
||||
return new ImmutableJWKSet<>(jwkSet);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
|
||||
return new NimbusJwtEncoder(jwkSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder(RSAPublicKey publicKey) {
|
||||
return NimbusJwtDecoder.withPublicKey(publicKey).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.healthsync.user.config;
|
||||
|
||||
import com.healthsync.user.service.Oauth.OAuth2UserService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class UserSecurityConfig {
|
||||
|
||||
private final OAuth2UserService oAuth2UserService;
|
||||
private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
|
||||
|
||||
public UserSecurityConfig(OAuth2UserService oAuth2UserService, OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler) {
|
||||
this.oAuth2UserService = oAuth2UserService;
|
||||
this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// 공개 접근 허용
|
||||
.requestMatchers(
|
||||
"/",
|
||||
"/swagger-ui/**",
|
||||
"/swagger-ui.html",
|
||||
"/v3/api-docs/**",
|
||||
"/swagger-resources/**",
|
||||
"/webjars/**"
|
||||
).permitAll()
|
||||
// OAuth2 및 인증 관련
|
||||
.requestMatchers(
|
||||
"/login/**",
|
||||
"/oauth2/**",
|
||||
"/api/auth/refresh"
|
||||
).permitAll()
|
||||
// 인증이 필요한 API
|
||||
.requestMatchers("/api/users/**").authenticated()
|
||||
.requestMatchers("/api/auth/logout/**").authenticated()
|
||||
// 나머지는 인증 필요
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2Login(oauth2 -> oauth2
|
||||
.userInfoEndpoint(userInfo -> userInfo.userService(oAuth2UserService))
|
||||
.successHandler(oAuth2LoginSuccessHandler) // 커스텀 Success Handler 사용
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.jwt(jwt -> jwt
|
||||
.jwtAuthenticationConverter(jwtAuthenticationConverter())
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
authoritiesConverter.setAuthorityPrefix("");
|
||||
authoritiesConverter.setAuthoritiesClaimName("role");
|
||||
|
||||
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.healthsync.user.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class UserSwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI healthServiceOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(apiInfo())
|
||||
.servers(List.of(
|
||||
new Server().url("http://localhost:8081").description("개발 서버"),
|
||||
new Server().url("https://api.healthsync.com").description("운영 서버")
|
||||
))
|
||||
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("Bearer Authentication",
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
.description("JWT 토큰을 입력하세요. 'Bearer ' 접두사는 자동으로 추가됩니다.")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private Info apiInfo() {
|
||||
return new Info()
|
||||
.title("HealthSync User API")
|
||||
.description("HealthSync 사용자 관리 및 인증 API 문서")
|
||||
.version("1.0.0")
|
||||
.contact(new Contact()
|
||||
.name("HealthSync Team")
|
||||
.email("support@healthsync.com")
|
||||
.url("https://healthsync.com")
|
||||
)
|
||||
.license(new License()
|
||||
.name("MIT License")
|
||||
.url("https://opensource.org/licenses/MIT")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// src/main/java/com/healthsync/user/controller/AuthController.java - 수정된 버전
|
||||
package com.healthsync.user.controller;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import com.healthsync.user.domain.Oauth.RefreshToken;
|
||||
import com.healthsync.user.dto.Oauth.TokenResponse;
|
||||
import com.healthsync.user.dto.Oauth.TokenRefreshRequest;
|
||||
import com.healthsync.user.service.Oauth.JwtTokenService;
|
||||
import com.healthsync.user.service.UserProfile.UserService;
|
||||
import com.healthsync.user.service.Oauth.RefreshTokenService;
|
||||
import com.healthsync.user.exception.UserNotFoundException;
|
||||
import com.healthsync.user.exception.AuthenticationException;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@Tag(name = "인증", description = "사용자 인증 관련 API")
|
||||
public class AuthController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
|
||||
|
||||
private final UserService userService;
|
||||
private final JwtTokenService jwtTokenService;
|
||||
private final RefreshTokenService refreshTokenService;
|
||||
|
||||
public AuthController(UserService userService, JwtTokenService jwtTokenService, RefreshTokenService refreshTokenService) {
|
||||
this.userService = userService;
|
||||
this.jwtTokenService = jwtTokenService;
|
||||
this.refreshTokenService = refreshTokenService;
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
@Operation(
|
||||
summary = "토큰 갱신",
|
||||
description = "리프레시 토큰을 사용하여 새로운 액세스 토큰과 리프레시 토큰을 발급받습니다."
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "토큰 갱신 성공",
|
||||
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||
@ApiResponse(responseCode = "401", description = "유효하지 않거나 만료된 리프레시 토큰"),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 형식"),
|
||||
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<TokenResponse>> refreshToken(
|
||||
@Parameter(description = "리프레시 토큰 요청 객체", required = true)
|
||||
@Valid @RequestBody TokenRefreshRequest request) {
|
||||
|
||||
logger.info("토큰 갱신 요청 - Refresh Token: {}", request.getRefreshToken().substring(0, 8) + "...");
|
||||
|
||||
String requestRefreshToken = request.getRefreshToken();
|
||||
|
||||
RefreshToken refreshToken = refreshTokenService.findByToken(requestRefreshToken)
|
||||
.orElseThrow(() -> {
|
||||
logger.warn("유효하지 않은 리프레시 토큰: {}", requestRefreshToken.substring(0, 8) + "...");
|
||||
return new AuthenticationException("유효하지 않은 리프레시 토큰입니다");
|
||||
});
|
||||
|
||||
refreshToken = refreshTokenService.verifyExpiration(refreshToken);
|
||||
|
||||
RefreshToken finalRefreshToken = refreshToken;
|
||||
User user = userService.findById(refreshToken.getMemberSerialNumber())
|
||||
.orElseThrow(() -> {
|
||||
logger.error("리프레시 토큰의 사용자를 찾을 수 없음 - Member Serial Number: {}", finalRefreshToken.getMemberSerialNumber());
|
||||
return new UserNotFoundException("사용자를 찾을 수 없습니다");
|
||||
});
|
||||
|
||||
logger.info("토큰 갱신 대상 사용자 - Member Serial Number: {}, Google ID: {}", user.getMemberSerialNumber(), user.getGoogleId());
|
||||
|
||||
String newAccessToken = jwtTokenService.generateAccessToken(user);
|
||||
RefreshToken newRefreshToken = refreshTokenService.createRefreshToken(user.getMemberSerialNumber());
|
||||
|
||||
TokenResponse tokenResponse = new TokenResponse(
|
||||
newAccessToken,
|
||||
newRefreshToken.getToken()
|
||||
);
|
||||
|
||||
logger.info("토큰 갱신 완료 - Member Serial Number: {}", user.getMemberSerialNumber());
|
||||
|
||||
return com.healthsync.common.response.ResponseHelper.success(tokenResponse, "토큰 갱신 성공");
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@Operation(
|
||||
summary = "로그아웃",
|
||||
description = "현재 사용자를 로그아웃합니다. 모든 리프레시 토큰이 삭제됩니다."
|
||||
)
|
||||
@SecurityRequirement(name = "Bearer Authentication")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "로그아웃 성공"),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<Void>> logout(@AuthenticationPrincipal Jwt jwt) {
|
||||
Long memberSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
String googleId = jwt.getClaimAsString("googleId");
|
||||
|
||||
logger.info("로그아웃 요청 - Member Serial Number: {}, Google ID: {}", memberSerialNumber, googleId);
|
||||
|
||||
// 해당 사용자의 모든 리프레시 토큰 삭제
|
||||
refreshTokenService.deleteByMemberSerialNumber(memberSerialNumber);
|
||||
|
||||
logger.info("로그아웃 완료 - Member Serial Number: {}", memberSerialNumber);
|
||||
|
||||
return com.healthsync.common.response.ResponseHelper.success(null, "로그아웃 성공");
|
||||
}
|
||||
|
||||
@PostMapping("/logout/{memberSerialNumber}")
|
||||
@Operation(
|
||||
summary = "특정 사용자 강제 로그아웃",
|
||||
description = "관리자가 특정 사용자를 강제 로그아웃합니다. 해당 사용자의 모든 리프레시 토큰이 삭제됩니다.",
|
||||
hidden = true
|
||||
)
|
||||
@SecurityRequirement(name = "Bearer Authentication")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "강제 로그아웃 성공"),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패"),
|
||||
@ApiResponse(responseCode = "403", description = "권한 없음"),
|
||||
@ApiResponse(responseCode = "404", description = "대상 사용자를 찾을 수 없음")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<Void>> forceLogout(
|
||||
@AuthenticationPrincipal Jwt jwt,
|
||||
@Parameter(description = "강제 로그아웃할 사용자의 회원 일련번호", required = true, example = "1")
|
||||
@PathVariable Long memberSerialNumber) {
|
||||
|
||||
Long currentUserSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
String currentGoogleId = jwt.getClaimAsString("googleId");
|
||||
|
||||
logger.info("강제 로그아웃 요청 - 요청자: {} ({}), 대상: {}", currentUserSerialNumber, currentGoogleId, memberSerialNumber);
|
||||
|
||||
// 대상 사용자가 존재하는지 확인
|
||||
User targetUser = userService.findById(memberSerialNumber)
|
||||
.orElseThrow(() -> new UserNotFoundException("대상 사용자를 찾을 수 없습니다: " + memberSerialNumber));
|
||||
|
||||
// 자기 자신을 로그아웃하는 경우 방지 (일반 로그아웃 사용 권장)
|
||||
if (currentUserSerialNumber.equals(memberSerialNumber)) {
|
||||
logger.warn("자기 자신에 대한 강제 로그아웃 시도 - Member Serial Number: {}", currentUserSerialNumber);
|
||||
return com.healthsync.common.response.ResponseHelper.badRequest(
|
||||
"자기 자신에 대해서는 일반 로그아웃을 사용해주세요",
|
||||
"SELF_LOGOUT_NOT_ALLOWED"
|
||||
);
|
||||
}
|
||||
|
||||
// 대상 사용자의 모든 리프레시 토큰 삭제
|
||||
refreshTokenService.deleteByMemberSerialNumber(memberSerialNumber);
|
||||
|
||||
logger.info("강제 로그아웃 완료 - 대상: {} ({})", targetUser.getMemberSerialNumber(), targetUser.getGoogleId());
|
||||
|
||||
return com.healthsync.common.response.ResponseHelper.success(null, "강제 로그아웃 성공");
|
||||
}
|
||||
|
||||
@GetMapping("/verify")
|
||||
@Operation(
|
||||
summary = "토큰 검증",
|
||||
description = "현재 JWT 토큰의 유효성을 검증하고 사용자 정보를 반환합니다."
|
||||
)
|
||||
@SecurityRequirement(name = "Bearer Authentication")
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "토큰 검증 성공"),
|
||||
@ApiResponse(responseCode = "401", description = "유효하지 않은 토큰")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<TokenVerificationResponse>> verifyToken(@AuthenticationPrincipal Jwt jwt) {
|
||||
Long memberSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
String googleId = jwt.getClaimAsString("googleId");
|
||||
String name = jwt.getClaimAsString("name");
|
||||
|
||||
logger.debug("토큰 검증 요청 - Member Serial Number: {}, Google ID: {}", memberSerialNumber, googleId);
|
||||
|
||||
// 사용자 존재 여부 확인 (DB에서 삭제된 사용자의 토큰인지 체크)
|
||||
boolean userExists = userService.findById(memberSerialNumber).isPresent();
|
||||
|
||||
if (!userExists) {
|
||||
logger.warn("삭제된 사용자의 토큰 - Member Serial Number: {}", memberSerialNumber);
|
||||
throw new UserNotFoundException("사용자가 존재하지 않습니다");
|
||||
}
|
||||
|
||||
TokenVerificationResponse response = new TokenVerificationResponse(
|
||||
memberSerialNumber,
|
||||
googleId,
|
||||
name,
|
||||
jwt.getIssuedAt(),
|
||||
jwt.getExpiresAt()
|
||||
);
|
||||
|
||||
return com.healthsync.common.response.ResponseHelper.success(response, "토큰 검증 성공");
|
||||
}
|
||||
|
||||
// 토큰 검증 응답 DTO
|
||||
public static class TokenVerificationResponse {
|
||||
private Long memberSerialNumber;
|
||||
private String googleId;
|
||||
private String name;
|
||||
private java.time.Instant issuedAt;
|
||||
private java.time.Instant expiresAt;
|
||||
|
||||
public TokenVerificationResponse(Long memberSerialNumber, String googleId, String name,
|
||||
java.time.Instant issuedAt, java.time.Instant expiresAt) {
|
||||
this.memberSerialNumber = memberSerialNumber;
|
||||
this.googleId = googleId;
|
||||
this.name = name;
|
||||
this.issuedAt = issuedAt;
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public String getGoogleId() { return googleId; }
|
||||
public String getName() { return name; }
|
||||
public java.time.Instant getIssuedAt() { return issuedAt; }
|
||||
public java.time.Instant getExpiresAt() { return expiresAt; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.healthsync.user.controller;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import com.healthsync.user.dto.UserProfile.UserProfileResponse;
|
||||
import com.healthsync.user.dto.UserProfile.UserUpdateRequest;
|
||||
import com.healthsync.user.dto.UserProfile.OccupationDto;
|
||||
import com.healthsync.user.service.UserProfile.UserService;
|
||||
import com.healthsync.user.exception.UserNotFoundException;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
@Tag(name = "사용자", description = "사용자 관리 API")
|
||||
@SecurityRequirement(name = "Bearer Authentication")
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
@Operation(
|
||||
summary = "내 정보 조회",
|
||||
description = "현재 로그인한 사용자의 프로필 정보를 조회합니다. 직업은 코드가 아닌 직업명으로 반환됩니다."
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "사용자 정보 조회 성공",
|
||||
content = @Content(schema = @Schema(implementation = UserProfileResponse.class))),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패 - 유효하지 않은 토큰"),
|
||||
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<UserProfileResponse>> getCurrentUser(@AuthenticationPrincipal Jwt jwt) {
|
||||
Long memberSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
|
||||
User user = userService.findById(memberSerialNumber)
|
||||
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다: " + memberSerialNumber));
|
||||
|
||||
UserProfileResponse response = new UserProfileResponse(user);
|
||||
return com.healthsync.common.response.ResponseHelper.success(response, "사용자 정보 조회 성공");
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(
|
||||
summary = "사용자 정보 조회 : 안 씀.",
|
||||
description = "특정 사용자의 프로필 정보를 조회합니다. 직업은 코드가 아닌 직업명으로 반환됩니다."
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "사용자 정보 조회 성공",
|
||||
content = @Content(schema = @Schema(implementation = UserProfileResponse.class))),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패"),
|
||||
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<UserProfileResponse>> getUserById(
|
||||
@Parameter(description = "조회할 사용자의 회원 일련번호", required = true, example = "1")
|
||||
@PathVariable Long id) {
|
||||
User user = userService.findById(id)
|
||||
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다: " + id));
|
||||
|
||||
UserProfileResponse response = new UserProfileResponse(user);
|
||||
return com.healthsync.common.response.ResponseHelper.success(response, "사용자 정보 조회 성공");
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
@Operation(
|
||||
summary = "내 정보 수정",
|
||||
description = "현재 로그인한 사용자의 프로필 정보를 수정합니다. " +
|
||||
"이름, 생년월일, 직업을 수정할 수 있습니다. " +
|
||||
"직업은 직업명으로 입력하면 자동으로 코드로 변환되어 저장됩니다."
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "사용자 정보 수정 성공",
|
||||
content = @Content(schema = @Schema(implementation = UserProfileResponse.class))),
|
||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 - 입력값 검증 실패"),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패"),
|
||||
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<UserProfileResponse>> updateCurrentUser(
|
||||
@AuthenticationPrincipal Jwt jwt,
|
||||
@Parameter(description = "사용자 정보 수정 요청 객체", required = true)
|
||||
@Valid @RequestBody UserUpdateRequest request) {
|
||||
|
||||
Long memberSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
|
||||
User user = userService.findById(memberSerialNumber)
|
||||
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다: " + memberSerialNumber));
|
||||
|
||||
user.setName(request.getName());
|
||||
user.setBirthDate(request.getBirthDate());
|
||||
// occupation은 프론트에서 직업명으로 온 것을 서비스에서 코드로 변환하여 저장
|
||||
user.setOccupation(request.getOccupation());
|
||||
|
||||
// updateUser 메서드에서 직업명 -> 코드 변환 후 저장, 응답 시 코드 -> 직업명 변환
|
||||
User updatedUser = userService.updateUser(user);
|
||||
UserProfileResponse response = new UserProfileResponse(updatedUser);
|
||||
|
||||
return com.healthsync.common.response.ResponseHelper.success(response, "사용자 정보 업데이트 성공");
|
||||
}
|
||||
|
||||
@GetMapping("/occupations")
|
||||
@Operation(
|
||||
summary = "직업 목록 조회",
|
||||
description = "사용자가 선택할 수 있는 직업 목록을 조회합니다. " +
|
||||
"프론트엔드에서 직업 선택 드롭다운 등에 사용할 수 있습니다."
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "직업 목록 조회 성공"),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패")
|
||||
})
|
||||
public ResponseEntity<com.healthsync.common.dto.ApiResponse<List<OccupationDto>>> getOccupations() {
|
||||
List<OccupationDto> occupations = userService.getAllOccupations();
|
||||
return com.healthsync.common.response.ResponseHelper.success(occupations, "직업 목록 조회 성공");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.healthsync.user.domain.HealthCheck;
|
||||
|
||||
public enum Gender {
|
||||
MALE(1, "남성"),
|
||||
FEMALE(2, "여성");
|
||||
|
||||
private final int code;
|
||||
private final String description;
|
||||
|
||||
Gender(int code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public static Gender fromCode(Integer code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Gender gender : Gender.values()) {
|
||||
if (gender.code == code) {
|
||||
return gender;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package com.healthsync.user.domain.HealthCheck;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class HealthCheckupRaw {
|
||||
private Long rawId;
|
||||
private Integer referenceYear;
|
||||
private LocalDate birthDate;
|
||||
private String name;
|
||||
private Integer regionCode;
|
||||
private Integer genderCode;
|
||||
private Integer age;
|
||||
private Integer height;
|
||||
private Integer weight;
|
||||
private Integer waistCircumference;
|
||||
private BigDecimal visualAcuityLeft;
|
||||
private BigDecimal visualAcuityRight;
|
||||
private Integer hearingLeft;
|
||||
private Integer hearingRight;
|
||||
private Integer systolicBp;
|
||||
private Integer diastolicBp;
|
||||
private Integer fastingGlucose;
|
||||
private Integer totalCholesterol;
|
||||
private Integer triglyceride;
|
||||
private Integer hdlCholesterol;
|
||||
private Integer ldlCholesterol;
|
||||
private BigDecimal hemoglobin;
|
||||
private Integer urineProtein;
|
||||
private BigDecimal serumCreatinine;
|
||||
private Integer ast;
|
||||
private Integer alt;
|
||||
private Integer gammaGtp;
|
||||
private Integer smokingStatus;
|
||||
private Integer drinkingStatus;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public HealthCheckupRaw() {}
|
||||
|
||||
// BMI 계산 메서드
|
||||
public BigDecimal calculateBMI() {
|
||||
if (height != null && weight != null && height > 0) {
|
||||
double heightInM = height / 100.0;
|
||||
double bmi = weight / (heightInM * heightInM);
|
||||
return BigDecimal.valueOf(bmi).setScale(1, BigDecimal.ROUND_HALF_UP);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 혈압 문자열 반환 메서드
|
||||
public String getBloodPressureString() {
|
||||
if (systolicBp != null && diastolicBp != null) {
|
||||
return systolicBp + "/" + diastolicBp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getRawId() { return rawId; }
|
||||
public void setRawId(Long rawId) { this.rawId = rawId; }
|
||||
|
||||
public Integer getReferenceYear() { return referenceYear; }
|
||||
public void setReferenceYear(Integer referenceYear) { this.referenceYear = referenceYear; }
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public Integer getRegionCode() { return regionCode; }
|
||||
public void setRegionCode(Integer regionCode) { this.regionCode = regionCode; }
|
||||
|
||||
public Integer getGenderCode() { return genderCode; }
|
||||
public void setGenderCode(Integer genderCode) { this.genderCode = genderCode; }
|
||||
|
||||
public Integer getAge() { return age; }
|
||||
public void setAge(Integer age) { this.age = age; }
|
||||
|
||||
public Integer getHeight() { return height; }
|
||||
public void setHeight(Integer height) { this.height = height; }
|
||||
|
||||
public Integer getWeight() { return weight; }
|
||||
public void setWeight(Integer weight) { this.weight = weight; }
|
||||
|
||||
public Integer getWaistCircumference() { return waistCircumference; }
|
||||
public void setWaistCircumference(Integer waistCircumference) { this.waistCircumference = waistCircumference; }
|
||||
|
||||
public BigDecimal getVisualAcuityLeft() { return visualAcuityLeft; }
|
||||
public void setVisualAcuityLeft(BigDecimal visualAcuityLeft) { this.visualAcuityLeft = visualAcuityLeft; }
|
||||
|
||||
public BigDecimal getVisualAcuityRight() { return visualAcuityRight; }
|
||||
public void setVisualAcuityRight(BigDecimal visualAcuityRight) { this.visualAcuityRight = visualAcuityRight; }
|
||||
|
||||
public Integer getHearingLeft() { return hearingLeft; }
|
||||
public void setHearingLeft(Integer hearingLeft) { this.hearingLeft = hearingLeft; }
|
||||
|
||||
public Integer getHearingRight() { return hearingRight; }
|
||||
public void setHearingRight(Integer hearingRight) { this.hearingRight = hearingRight; }
|
||||
|
||||
public Integer getSystolicBp() { return systolicBp; }
|
||||
public void setSystolicBp(Integer systolicBp) { this.systolicBp = systolicBp; }
|
||||
|
||||
public Integer getDiastolicBp() { return diastolicBp; }
|
||||
public void setDiastolicBp(Integer diastolicBp) { this.diastolicBp = diastolicBp; }
|
||||
|
||||
public Integer getFastingGlucose() { return fastingGlucose; }
|
||||
public void setFastingGlucose(Integer fastingGlucose) { this.fastingGlucose = fastingGlucose; }
|
||||
|
||||
public Integer getTotalCholesterol() { return totalCholesterol; }
|
||||
public void setTotalCholesterol(Integer totalCholesterol) { this.totalCholesterol = totalCholesterol; }
|
||||
|
||||
public Integer getTriglyceride() { return triglyceride; }
|
||||
public void setTriglyceride(Integer triglyceride) { this.triglyceride = triglyceride; }
|
||||
|
||||
public Integer getHdlCholesterol() { return hdlCholesterol; }
|
||||
public void setHdlCholesterol(Integer hdlCholesterol) { this.hdlCholesterol = hdlCholesterol; }
|
||||
|
||||
public Integer getLdlCholesterol() { return ldlCholesterol; }
|
||||
public void setLdlCholesterol(Integer ldlCholesterol) { this.ldlCholesterol = ldlCholesterol; }
|
||||
|
||||
public BigDecimal getHemoglobin() { return hemoglobin; }
|
||||
public void setHemoglobin(BigDecimal hemoglobin) { this.hemoglobin = hemoglobin; }
|
||||
|
||||
public Integer getUrineProtein() { return urineProtein; }
|
||||
public void setUrineProtein(Integer urineProtein) { this.urineProtein = urineProtein; }
|
||||
|
||||
public BigDecimal getSerumCreatinine() { return serumCreatinine; }
|
||||
public void setSerumCreatinine(BigDecimal serumCreatinine) { this.serumCreatinine = serumCreatinine; }
|
||||
|
||||
public Integer getAst() { return ast; }
|
||||
public void setAst(Integer ast) { this.ast = ast; }
|
||||
|
||||
public Integer getAlt() { return alt; }
|
||||
public void setAlt(Integer alt) { this.alt = alt; }
|
||||
|
||||
public Integer getGammaGtp() { return gammaGtp; }
|
||||
public void setGammaGtp(Integer gammaGtp) { this.gammaGtp = gammaGtp; }
|
||||
|
||||
public Integer getSmokingStatus() { return smokingStatus; }
|
||||
public void setSmokingStatus(Integer smokingStatus) { this.smokingStatus = smokingStatus; }
|
||||
|
||||
public Integer getDrinkingStatus() { return drinkingStatus; }
|
||||
public void setDrinkingStatus(Integer drinkingStatus) { this.drinkingStatus = drinkingStatus; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
// 성별 관련 메서드 추가
|
||||
public Gender getGender() {
|
||||
return Gender.fromCode(this.genderCode);
|
||||
}
|
||||
|
||||
public String getGenderDescription() {
|
||||
Gender gender = getGender();
|
||||
return gender != null ? gender.getDescription() : "미상";
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.healthsync.user.domain.HealthCheck;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class HealthNormalRange {
|
||||
private Integer rangeId;
|
||||
private String healthItemCode;
|
||||
private String healthItemName;
|
||||
private Integer genderCode;
|
||||
private String unit;
|
||||
private String normalRange;
|
||||
private String warningRange;
|
||||
private String dangerRange;
|
||||
private String note;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public HealthNormalRange() {}
|
||||
|
||||
// Getters and Setters
|
||||
public Integer getRangeId() { return rangeId; }
|
||||
public void setRangeId(Integer rangeId) { this.rangeId = rangeId; }
|
||||
|
||||
public String getHealthItemCode() { return healthItemCode; }
|
||||
public void setHealthItemCode(String healthItemCode) { this.healthItemCode = healthItemCode; }
|
||||
|
||||
public String getHealthItemName() { return healthItemName; }
|
||||
public void setHealthItemName(String healthItemName) { this.healthItemName = healthItemName; }
|
||||
|
||||
public Integer getGenderCode() { return genderCode; }
|
||||
public void setGenderCode(Integer genderCode) { this.genderCode = genderCode; }
|
||||
|
||||
public String getUnit() { return unit; }
|
||||
public void setUnit(String unit) { this.unit = unit; }
|
||||
|
||||
public String getNormalRange() { return normalRange; }
|
||||
public void setNormalRange(String normalRange) { this.normalRange = normalRange; }
|
||||
|
||||
public String getWarningRange() { return warningRange; }
|
||||
public void setWarningRange(String warningRange) { this.warningRange = warningRange; }
|
||||
|
||||
public String getDangerRange() { return dangerRange; }
|
||||
public void setDangerRange(String dangerRange) { this.dangerRange = dangerRange; }
|
||||
|
||||
public String getNote() { return note; }
|
||||
public void setNote(String note) { this.note = note; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.healthsync.user.domain.Oauth;
|
||||
|
||||
public enum JobCategory {
|
||||
DEVELOPER(1, "개발"),
|
||||
PM(2, "PM"),
|
||||
MARKETING(3, "마케팅"),
|
||||
SALES(4, "영업"),
|
||||
INFRA_OPERATION(5, "인프라운영"),
|
||||
CUSTOMER_SERVICE(6, "고객상담"),
|
||||
ETC(7, "기타");
|
||||
|
||||
private final int code;
|
||||
private final String name;
|
||||
|
||||
JobCategory(int code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static JobCategory fromCode(int code) {
|
||||
for (JobCategory category : JobCategory.values()) {
|
||||
if (category.code == code) {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
return ETC; // 기본값
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.healthsync.user.domain.Oauth;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class RefreshToken {
|
||||
private Long id;
|
||||
private String token;
|
||||
private Long memberSerialNumber; // memberId -> memberSerialNumber로 변경
|
||||
private LocalDateTime expiryDate;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public RefreshToken() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public RefreshToken(String token, Long memberSerialNumber, LocalDateTime expiryDate) {
|
||||
this();
|
||||
this.token = token;
|
||||
this.memberSerialNumber = memberSerialNumber;
|
||||
this.expiryDate = expiryDate;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return LocalDateTime.now().isAfter(expiryDate);
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getToken() { return token; }
|
||||
public void setToken(String token) { this.token = token; }
|
||||
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) { this.memberSerialNumber = memberSerialNumber; }
|
||||
|
||||
public LocalDateTime getExpiryDate() { return expiryDate; }
|
||||
public void setExpiryDate(LocalDateTime expiryDate) { this.expiryDate = expiryDate; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.healthsync.user.domain.Oauth;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class User {
|
||||
private Long memberSerialNumber; // ID는 DB에서 자동 생성
|
||||
private String googleId;
|
||||
private String name;
|
||||
private LocalDate birthDate;
|
||||
private String occupation;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
public User() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// 신규 사용자 생성용 생성자 (ID 없음)
|
||||
public User(String googleId, String name, LocalDate birthDate, String occupation) {
|
||||
this(); // 기본 생성자 호출
|
||||
this.googleId = googleId;
|
||||
this.name = name;
|
||||
this.birthDate = birthDate;
|
||||
this.occupation = occupation;
|
||||
this.lastLoginAt = LocalDateTime.now();
|
||||
// memberSerialNumber는 설정하지 않음 - DB에서 자동 생성
|
||||
}
|
||||
|
||||
// 로그인 시간 업데이트
|
||||
public void updateLastLoginAt() {
|
||||
this.lastLoginAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) { this.memberSerialNumber = memberSerialNumber; }
|
||||
|
||||
public String getGoogleId() { return googleId; }
|
||||
public void setGoogleId(String googleId) { this.googleId = googleId; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getOccupation() { return occupation; }
|
||||
public void setOccupation(String occupation) {
|
||||
this.occupation = occupation;
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public LocalDateTime getLastLoginAt() { return lastLoginAt; }
|
||||
public void setLastLoginAt(LocalDateTime lastLoginAt) { this.lastLoginAt = lastLoginAt; }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.healthsync.user.domain.Oauth;
|
||||
|
||||
public enum UserRole {
|
||||
USER, ADMIN
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.healthsync.user.dto.HealthCheck;
|
||||
|
||||
import com.healthsync.user.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.user.domain.HealthCheck.HealthNormalRange;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HealthCheckupHistoryResponse {
|
||||
private HealthCheckupRaw recentCheckup;
|
||||
private List<HealthNormalRange> normalRanges;
|
||||
private String statusNote;
|
||||
|
||||
public HealthCheckupHistoryResponse() {}
|
||||
|
||||
public HealthCheckupHistoryResponse(HealthCheckupRaw recentCheckup,
|
||||
List<HealthNormalRange> normalRanges,
|
||||
String statusNote) {
|
||||
this.recentCheckup = recentCheckup;
|
||||
this.normalRanges = normalRanges;
|
||||
this.statusNote = statusNote;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public HealthCheckupRaw getRecentCheckup() { return recentCheckup; }
|
||||
public void setRecentCheckup(HealthCheckupRaw recentCheckup) { this.recentCheckup = recentCheckup; }
|
||||
|
||||
public List<HealthNormalRange> getNormalRanges() { return normalRanges; }
|
||||
public void setNormalRanges(List<HealthNormalRange> normalRanges) { this.normalRanges = normalRanges; }
|
||||
|
||||
public String getStatusNote() { return statusNote; }
|
||||
public void setStatusNote(String statusNote) { this.statusNote = statusNote; }
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.healthsync.user.dto.HealthCheck;
|
||||
|
||||
import com.healthsync.user.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.user.domain.HealthCheck.HealthNormalRange;
|
||||
import com.healthsync.user.domain.HealthCheck.Gender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HealthProfileSummaryResponse {
|
||||
private HealthCheckupRaw recentCheckup;
|
||||
private List<HealthNormalRange> normalRanges;
|
||||
private Gender gender;
|
||||
private String genderDescription;
|
||||
|
||||
public HealthProfileSummaryResponse() {}
|
||||
|
||||
|
||||
public HealthProfileSummaryResponse(HealthCheckupRaw recentCheckup) {
|
||||
this.recentCheckup = recentCheckup;
|
||||
}
|
||||
|
||||
public HealthProfileSummaryResponse(HealthCheckupRaw recentCheckup, List<HealthNormalRange> normalRanges) {
|
||||
this.recentCheckup = recentCheckup;
|
||||
this.normalRanges = normalRanges;
|
||||
this.gender = recentCheckup.getGender();
|
||||
this.genderDescription = recentCheckup.getGenderDescription();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public HealthCheckupRaw getRecentCheckup() { return recentCheckup; }
|
||||
public void setRecentCheckup(HealthCheckupRaw recentCheckup) {
|
||||
this.recentCheckup = recentCheckup;
|
||||
if (recentCheckup != null) {
|
||||
this.gender = recentCheckup.getGender();
|
||||
this.genderDescription = recentCheckup.getGenderDescription();
|
||||
}
|
||||
}
|
||||
|
||||
public List<HealthNormalRange> getNormalRanges() { return normalRanges; }
|
||||
public void setNormalRanges(List<HealthNormalRange> normalRanges) { this.normalRanges = normalRanges; }
|
||||
|
||||
public Gender getGender() { return gender; }
|
||||
public void setGender(Gender gender) { this.gender = gender; }
|
||||
|
||||
public String getGenderDescription() { return genderDescription; }
|
||||
public void setGenderDescription(String genderDescription) { this.genderDescription = genderDescription; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.healthsync.user.dto.Oauth;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class OAuth2UserInfo {
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
public OAuth2UserInfo(Map<String, Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return (String) attributes.get("sub");
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return (String) attributes.get("name");
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return (String) attributes.get("email");
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return (String) attributes.get("picture");
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.healthsync.user.dto.Oauth;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class TokenRefreshRequest {
|
||||
@NotBlank(message = "리프레시 토큰은 필수입니다")
|
||||
private String refreshToken;
|
||||
|
||||
public TokenRefreshRequest() {}
|
||||
|
||||
public TokenRefreshRequest(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getRefreshToken() { return refreshToken; }
|
||||
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.healthsync.user.dto.Oauth;
|
||||
|
||||
public class TokenResponse {
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
private String tokenType = "Bearer";
|
||||
|
||||
public TokenResponse() {}
|
||||
|
||||
public TokenResponse(String accessToken, String refreshToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getAccessToken() { return accessToken; }
|
||||
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
|
||||
|
||||
public String getRefreshToken() { return refreshToken; }
|
||||
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
|
||||
|
||||
public String getTokenType() { return tokenType; }
|
||||
public void setTokenType(String tokenType) { this.tokenType = tokenType; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.healthsync.user.dto.UserProfile;
|
||||
|
||||
public class LoginResponse {
|
||||
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
private UserProfileResponse user;
|
||||
private boolean isNewUser;
|
||||
|
||||
public LoginResponse() {}
|
||||
|
||||
public LoginResponse(String accessToken, String refreshToken, UserProfileResponse user, boolean isNewUser) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
this.user = user;
|
||||
this.isNewUser = isNewUser;
|
||||
}
|
||||
|
||||
public String getAccessToken() { return accessToken; }
|
||||
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
|
||||
|
||||
public String getRefreshToken() { return refreshToken; }
|
||||
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
|
||||
|
||||
public UserProfileResponse getUser() { return user; }
|
||||
public void setUser(UserProfileResponse user) { this.user = user; }
|
||||
|
||||
public boolean isNewUser() { return isNewUser; }
|
||||
public void setNewUser(boolean isNewUser) { this.isNewUser = isNewUser; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.healthsync.user.dto.UserProfile;
|
||||
|
||||
/**
|
||||
* 직업 정보 DTO
|
||||
*/
|
||||
public class OccupationDto {
|
||||
private String code;
|
||||
private String name;
|
||||
private String category;
|
||||
|
||||
public OccupationDto() {}
|
||||
|
||||
public OccupationDto(String code, String name, String category) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.healthsync.user.dto.UserProfile;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class UserProfileResponse {
|
||||
private Long memberSerialNumber;
|
||||
private String googleId;
|
||||
private String name;
|
||||
private LocalDate birthDate;
|
||||
private String occupation;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
public UserProfileResponse() {}
|
||||
|
||||
public UserProfileResponse(User user) {
|
||||
this.memberSerialNumber = user.getMemberSerialNumber();
|
||||
this.googleId = user.getGoogleId();
|
||||
this.name = user.getName();
|
||||
this.birthDate = user.getBirthDate();
|
||||
this.occupation = user.getOccupation();
|
||||
this.createdAt = user.getCreatedAt();
|
||||
this.lastLoginAt = user.getLastLoginAt();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) { this.memberSerialNumber = memberSerialNumber; }
|
||||
|
||||
public String getGoogleId() { return googleId; }
|
||||
public void setGoogleId(String googleId) { this.googleId = googleId; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
|
||||
|
||||
public String getOccupation() { return occupation; }
|
||||
public void setOccupation(String occupation) { this.occupation = occupation; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getLastLoginAt() { return lastLoginAt; }
|
||||
public void setLastLoginAt(LocalDateTime lastLoginAt) { this.lastLoginAt = lastLoginAt; }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.healthsync.user.dto.UserProfile;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Past;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class UserUpdateRequest {
|
||||
|
||||
@NotBlank(message = "이름은 필수입니다")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "생년월일은 필수입니다")
|
||||
private String birthDate;
|
||||
|
||||
private String occupation;
|
||||
|
||||
public UserUpdateRequest() {
|
||||
}
|
||||
|
||||
public UserUpdateRequest(String name, String birthDate, String occupation) {
|
||||
this.name = name;
|
||||
this.birthDate = birthDate;
|
||||
this.occupation = occupation;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public LocalDate getBirthDate() {
|
||||
if (birthDate == null || birthDate.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return LocalDate.parse(birthDate, DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
}
|
||||
|
||||
public String getBirthDateString() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public void setBirthDate(String birthDate) {
|
||||
this.birthDate = birthDate;
|
||||
}
|
||||
|
||||
public String getOccupation() {
|
||||
return occupation;
|
||||
}
|
||||
|
||||
public void setOccupation(String occupation) {
|
||||
this.occupation = occupation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.user.exception;
|
||||
|
||||
import com.healthsync.common.exception.CustomException;
|
||||
|
||||
public class AuthenticationException extends CustomException {
|
||||
public AuthenticationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.user.exception;
|
||||
|
||||
import com.healthsync.common.exception.CustomException;
|
||||
|
||||
public class TokenExpiredException extends CustomException {
|
||||
public TokenExpiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TokenExpiredException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.user.exception;
|
||||
|
||||
import com.healthsync.common.exception.CustomException;
|
||||
|
||||
public class UserNotFoundException extends CustomException {
|
||||
public UserNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UserNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
package com.healthsync.user.repository.entity;
|
||||
|
||||
import com.healthsync.user.domain.HealthCheck.HealthCheckupRaw;
|
||||
import jakarta.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "health_checkup_raw", schema = "health_service")
|
||||
public class HealthCheckupRawEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "raw_id")
|
||||
private Long rawId;
|
||||
|
||||
@Column(name = "reference_year", nullable = false)
|
||||
private Integer referenceYear;
|
||||
|
||||
@Column(name = "birth_date", nullable = false)
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Column(name = "name", nullable = false, length = 50)
|
||||
private String name;
|
||||
|
||||
@Column(name = "region_code")
|
||||
private Integer regionCode;
|
||||
|
||||
@Column(name = "gender_code")
|
||||
private Integer genderCode;
|
||||
|
||||
@Column(name = "age")
|
||||
private Integer age;
|
||||
|
||||
@Column(name = "height")
|
||||
private Integer height;
|
||||
|
||||
@Column(name = "weight")
|
||||
private Integer weight;
|
||||
|
||||
@Column(name = "waist_circumference")
|
||||
private Integer waistCircumference;
|
||||
|
||||
@Column(name = "visual_acuity_left", precision = 3, scale = 1)
|
||||
private BigDecimal visualAcuityLeft;
|
||||
|
||||
@Column(name = "visual_acuity_right", precision = 3, scale = 1)
|
||||
private BigDecimal visualAcuityRight;
|
||||
|
||||
@Column(name = "hearing_left")
|
||||
private Integer hearingLeft;
|
||||
|
||||
@Column(name = "hearing_right")
|
||||
private Integer hearingRight;
|
||||
|
||||
@Column(name = "systolic_bp")
|
||||
private Integer systolicBp;
|
||||
|
||||
@Column(name = "diastolic_bp")
|
||||
private Integer diastolicBp;
|
||||
|
||||
@Column(name = "fasting_glucose")
|
||||
private Integer fastingGlucose;
|
||||
|
||||
@Column(name = "total_cholesterol")
|
||||
private Integer totalCholesterol;
|
||||
|
||||
@Column(name = "triglyceride")
|
||||
private Integer triglyceride;
|
||||
|
||||
@Column(name = "hdl_cholesterol")
|
||||
private Integer hdlCholesterol;
|
||||
|
||||
@Column(name = "ldl_cholesterol")
|
||||
private Integer ldlCholesterol;
|
||||
|
||||
@Column(name = "hemoglobin", precision = 4, scale = 1)
|
||||
private BigDecimal hemoglobin;
|
||||
|
||||
@Column(name = "urine_protein")
|
||||
private Integer urineProtein;
|
||||
|
||||
@Column(name = "serum_creatinine", precision = 4, scale = 1)
|
||||
private BigDecimal serumCreatinine;
|
||||
|
||||
@Column(name = "ast")
|
||||
private Integer ast;
|
||||
|
||||
@Column(name = "alt")
|
||||
private Integer alt;
|
||||
|
||||
@Column(name = "gamma_gtp")
|
||||
private Integer gammaGtp;
|
||||
|
||||
@Column(name = "smoking_status")
|
||||
private Integer smokingStatus;
|
||||
|
||||
@Column(name = "drinking_status")
|
||||
private Integer drinkingStatus;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
protected HealthCheckupRawEntity() {}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getRawId() { return rawId; }
|
||||
public void setRawId(Long rawId) { this.rawId = rawId; }
|
||||
|
||||
public Integer getReferenceYear() { return referenceYear; }
|
||||
public void setReferenceYear(Integer referenceYear) { this.referenceYear = referenceYear; }
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public Integer getRegionCode() { return regionCode; }
|
||||
public void setRegionCode(Integer regionCode) { this.regionCode = regionCode; }
|
||||
|
||||
public Integer getGenderCode() { return genderCode; }
|
||||
public void setGenderCode(Integer genderCode) { this.genderCode = genderCode; }
|
||||
|
||||
public Integer getAge() { return age; }
|
||||
public void setAge(Integer age) { this.age = age; }
|
||||
|
||||
public Integer getHeight() { return height; }
|
||||
public void setHeight(Integer height) { this.height = height; }
|
||||
|
||||
public Integer getWeight() { return weight; }
|
||||
public void setWeight(Integer weight) { this.weight = weight; }
|
||||
|
||||
public Integer getWaistCircumference() { return waistCircumference; }
|
||||
public void setWaistCircumference(Integer waistCircumference) { this.waistCircumference = waistCircumference; }
|
||||
|
||||
public BigDecimal getVisualAcuityLeft() { return visualAcuityLeft; }
|
||||
public void setVisualAcuityLeft(BigDecimal visualAcuityLeft) { this.visualAcuityLeft = visualAcuityLeft; }
|
||||
|
||||
public BigDecimal getVisualAcuityRight() { return visualAcuityRight; }
|
||||
public void setVisualAcuityRight(BigDecimal visualAcuityRight) { this.visualAcuityRight = visualAcuityRight; }
|
||||
|
||||
public Integer getHearingLeft() { return hearingLeft; }
|
||||
public void setHearingLeft(Integer hearingLeft) { this.hearingLeft = hearingLeft; }
|
||||
|
||||
public Integer getHearingRight() { return hearingRight; }
|
||||
public void setHearingRight(Integer hearingRight) { this.hearingRight = hearingRight; }
|
||||
|
||||
public Integer getSystolicBp() { return systolicBp; }
|
||||
public void setSystolicBp(Integer systolicBp) { this.systolicBp = systolicBp; }
|
||||
|
||||
public Integer getDiastolicBp() { return diastolicBp; }
|
||||
public void setDiastolicBp(Integer diastolicBp) { this.diastolicBp = diastolicBp; }
|
||||
|
||||
public Integer getFastingGlucose() { return fastingGlucose; }
|
||||
public void setFastingGlucose(Integer fastingGlucose) { this.fastingGlucose = fastingGlucose; }
|
||||
|
||||
public Integer getTotalCholesterol() { return totalCholesterol; }
|
||||
public void setTotalCholesterol(Integer totalCholesterol) { this.totalCholesterol = totalCholesterol; }
|
||||
|
||||
public Integer getTriglyceride() { return triglyceride; }
|
||||
public void setTriglyceride(Integer triglyceride) { this.triglyceride = triglyceride; }
|
||||
|
||||
public Integer getHdlCholesterol() { return hdlCholesterol; }
|
||||
public void setHdlCholesterol(Integer hdlCholesterol) { this.hdlCholesterol = hdlCholesterol; }
|
||||
|
||||
public Integer getLdlCholesterol() { return ldlCholesterol; }
|
||||
public void setLdlCholesterol(Integer ldlCholesterol) { this.ldlCholesterol = ldlCholesterol; }
|
||||
|
||||
public BigDecimal getHemoglobin() { return hemoglobin; }
|
||||
public void setHemoglobin(BigDecimal hemoglobin) { this.hemoglobin = hemoglobin; }
|
||||
|
||||
public Integer getUrineProtein() { return urineProtein; }
|
||||
public void setUrineProtein(Integer urineProtein) { this.urineProtein = urineProtein; }
|
||||
|
||||
public BigDecimal getSerumCreatinine() { return serumCreatinine; }
|
||||
public void setSerumCreatinine(BigDecimal serumCreatinine) { this.serumCreatinine = serumCreatinine; }
|
||||
|
||||
public Integer getAst() { return ast; }
|
||||
public void setAst(Integer ast) { this.ast = ast; }
|
||||
|
||||
public Integer getAlt() { return alt; }
|
||||
public void setAlt(Integer alt) { this.alt = alt; }
|
||||
|
||||
public Integer getGammaGtp() { return gammaGtp; }
|
||||
public void setGammaGtp(Integer gammaGtp) { this.gammaGtp = gammaGtp; }
|
||||
|
||||
public Integer getSmokingStatus() { return smokingStatus; }
|
||||
public void setSmokingStatus(Integer smokingStatus) { this.smokingStatus = smokingStatus; }
|
||||
|
||||
public Integer getDrinkingStatus() { return drinkingStatus; }
|
||||
public void setDrinkingStatus(Integer drinkingStatus) { this.drinkingStatus = drinkingStatus; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
// Entity ↔ Domain 변환 메서드
|
||||
public static HealthCheckupRawEntity fromDomain(HealthCheckupRaw healthCheckupRaw) {
|
||||
if (healthCheckupRaw == null) return null;
|
||||
|
||||
HealthCheckupRawEntity entity = new HealthCheckupRawEntity();
|
||||
entity.rawId = healthCheckupRaw.getRawId();
|
||||
entity.referenceYear = healthCheckupRaw.getReferenceYear();
|
||||
entity.birthDate = healthCheckupRaw.getBirthDate();
|
||||
entity.name = healthCheckupRaw.getName();
|
||||
entity.regionCode = healthCheckupRaw.getRegionCode();
|
||||
entity.genderCode = healthCheckupRaw.getGenderCode();
|
||||
entity.age = healthCheckupRaw.getAge();
|
||||
entity.height = healthCheckupRaw.getHeight();
|
||||
entity.weight = healthCheckupRaw.getWeight();
|
||||
entity.waistCircumference = healthCheckupRaw.getWaistCircumference();
|
||||
entity.visualAcuityLeft = healthCheckupRaw.getVisualAcuityLeft();
|
||||
entity.visualAcuityRight = healthCheckupRaw.getVisualAcuityRight();
|
||||
entity.hearingLeft = healthCheckupRaw.getHearingLeft();
|
||||
entity.hearingRight = healthCheckupRaw.getHearingRight();
|
||||
entity.systolicBp = healthCheckupRaw.getSystolicBp();
|
||||
entity.diastolicBp = healthCheckupRaw.getDiastolicBp();
|
||||
entity.fastingGlucose = healthCheckupRaw.getFastingGlucose();
|
||||
entity.totalCholesterol = healthCheckupRaw.getTotalCholesterol();
|
||||
entity.triglyceride = healthCheckupRaw.getTriglyceride();
|
||||
entity.hdlCholesterol = healthCheckupRaw.getHdlCholesterol();
|
||||
entity.ldlCholesterol = healthCheckupRaw.getLdlCholesterol();
|
||||
entity.hemoglobin = healthCheckupRaw.getHemoglobin();
|
||||
entity.urineProtein = healthCheckupRaw.getUrineProtein();
|
||||
entity.serumCreatinine = healthCheckupRaw.getSerumCreatinine();
|
||||
entity.ast = healthCheckupRaw.getAst();
|
||||
entity.alt = healthCheckupRaw.getAlt();
|
||||
entity.gammaGtp = healthCheckupRaw.getGammaGtp();
|
||||
entity.smokingStatus = healthCheckupRaw.getSmokingStatus();
|
||||
entity.drinkingStatus = healthCheckupRaw.getDrinkingStatus();
|
||||
entity.createdAt = healthCheckupRaw.getCreatedAt();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public HealthCheckupRaw toDomain() {
|
||||
HealthCheckupRaw domain = new HealthCheckupRaw();
|
||||
domain.setRawId(this.rawId);
|
||||
domain.setReferenceYear(this.referenceYear);
|
||||
domain.setBirthDate(this.birthDate);
|
||||
domain.setName(this.name);
|
||||
domain.setRegionCode(this.regionCode);
|
||||
domain.setGenderCode(this.genderCode);
|
||||
domain.setAge(this.age);
|
||||
domain.setHeight(this.height);
|
||||
domain.setWeight(this.weight);
|
||||
domain.setWaistCircumference(this.waistCircumference);
|
||||
domain.setVisualAcuityLeft(this.visualAcuityLeft);
|
||||
domain.setVisualAcuityRight(this.visualAcuityRight);
|
||||
domain.setHearingLeft(this.hearingLeft);
|
||||
domain.setHearingRight(this.hearingRight);
|
||||
domain.setSystolicBp(this.systolicBp);
|
||||
domain.setDiastolicBp(this.diastolicBp);
|
||||
domain.setFastingGlucose(this.fastingGlucose);
|
||||
domain.setTotalCholesterol(this.totalCholesterol);
|
||||
domain.setTriglyceride(this.triglyceride);
|
||||
domain.setHdlCholesterol(this.hdlCholesterol);
|
||||
domain.setLdlCholesterol(this.ldlCholesterol);
|
||||
domain.setHemoglobin(this.hemoglobin);
|
||||
domain.setUrineProtein(this.urineProtein);
|
||||
domain.setSerumCreatinine(this.serumCreatinine);
|
||||
domain.setAst(this.ast);
|
||||
domain.setAlt(this.alt);
|
||||
domain.setGammaGtp(this.gammaGtp);
|
||||
domain.setSmokingStatus(this.smokingStatus);
|
||||
domain.setDrinkingStatus(this.drinkingStatus);
|
||||
domain.setCreatedAt(this.createdAt);
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package com.healthsync.user.repository.entity;
|
||||
|
||||
import com.healthsync.user.domain.HealthCheck.HealthNormalRange;
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "health_normal_range", schema = "health_service")
|
||||
public class HealthNormalRangeEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "range_id")
|
||||
private Integer rangeId;
|
||||
|
||||
@Column(name = "health_item_code", length = 25)
|
||||
private String healthItemCode;
|
||||
|
||||
@Column(name = "health_item_name", length = 30)
|
||||
private String healthItemName;
|
||||
|
||||
@Column(name = "gender_code")
|
||||
private Integer genderCode;
|
||||
|
||||
@Column(name = "unit", length = 10)
|
||||
private String unit;
|
||||
|
||||
@Column(name = "normal_range", length = 15)
|
||||
private String normalRange;
|
||||
|
||||
@Column(name = "warning_range", length = 15)
|
||||
private String warningRange;
|
||||
|
||||
@Column(name = "danger_range", length = 15)
|
||||
private String dangerRange;
|
||||
|
||||
@Column(name = "note", length = 50)
|
||||
private String note;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
protected HealthNormalRangeEntity() {}
|
||||
|
||||
// Getters and Setters
|
||||
public Integer getRangeId() { return rangeId; }
|
||||
public void setRangeId(Integer rangeId) { this.rangeId = rangeId; }
|
||||
|
||||
public String getHealthItemCode() { return healthItemCode; }
|
||||
public void setHealthItemCode(String healthItemCode) { this.healthItemCode = healthItemCode; }
|
||||
|
||||
public String getHealthItemName() { return healthItemName; }
|
||||
public void setHealthItemName(String healthItemName) { this.healthItemName = healthItemName; }
|
||||
|
||||
public Integer getGenderCode() { return genderCode; }
|
||||
public void setGenderCode(Integer genderCode) { this.genderCode = genderCode; }
|
||||
|
||||
public String getUnit() { return unit; }
|
||||
public void setUnit(String unit) { this.unit = unit; }
|
||||
|
||||
public String getNormalRange() { return normalRange; }
|
||||
public void setNormalRange(String normalRange) { this.normalRange = normalRange; }
|
||||
|
||||
public String getWarningRange() { return warningRange; }
|
||||
public void setWarningRange(String warningRange) { this.warningRange = warningRange; }
|
||||
|
||||
public String getDangerRange() { return dangerRange; }
|
||||
public void setDangerRange(String dangerRange) { this.dangerRange = dangerRange; }
|
||||
|
||||
public String getNote() { return note; }
|
||||
public void setNote(String note) { this.note = note; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
// Entity ↔ Domain 변환 메서드
|
||||
public static HealthNormalRangeEntity fromDomain(HealthNormalRange healthNormalRange) {
|
||||
if (healthNormalRange == null) return null;
|
||||
|
||||
HealthNormalRangeEntity entity = new HealthNormalRangeEntity();
|
||||
entity.rangeId = healthNormalRange.getRangeId();
|
||||
entity.healthItemCode = healthNormalRange.getHealthItemCode();
|
||||
entity.healthItemName = healthNormalRange.getHealthItemName();
|
||||
entity.genderCode = healthNormalRange.getGenderCode();
|
||||
entity.unit = healthNormalRange.getUnit();
|
||||
entity.normalRange = healthNormalRange.getNormalRange();
|
||||
entity.warningRange = healthNormalRange.getWarningRange();
|
||||
entity.dangerRange = healthNormalRange.getDangerRange();
|
||||
entity.note = healthNormalRange.getNote();
|
||||
entity.createdAt = healthNormalRange.getCreatedAt();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public HealthNormalRange toDomain() {
|
||||
HealthNormalRange domain = new HealthNormalRange();
|
||||
domain.setRangeId(this.rangeId);
|
||||
domain.setHealthItemCode(this.healthItemCode);
|
||||
domain.setHealthItemName(this.healthItemName);
|
||||
domain.setGenderCode(this.genderCode);
|
||||
domain.setUnit(this.unit);
|
||||
domain.setNormalRange(this.normalRange);
|
||||
domain.setWarningRange(this.warningRange);
|
||||
domain.setDangerRange(this.dangerRange);
|
||||
domain.setNote(this.note);
|
||||
domain.setCreatedAt(this.createdAt);
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.healthsync.user.repository.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
/**
|
||||
* 직업 유형 정보를 담는 엔티티
|
||||
* occupation_type 테이블과 매핑
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "occupation_type", schema = "user_service")
|
||||
public class OccupationTypeEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "occupation_code", length = 20)
|
||||
private String occupationCode;
|
||||
|
||||
@Column(name = "occupation_name", length = 100, nullable = false)
|
||||
private String occupationName;
|
||||
|
||||
@Column(name = "category", length = 50)
|
||||
private String category;
|
||||
|
||||
protected OccupationTypeEntity() {}
|
||||
|
||||
public OccupationTypeEntity(String occupationCode, String occupationName, String category) {
|
||||
this.occupationCode = occupationCode;
|
||||
this.occupationName = occupationName;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getOccupationCode() {
|
||||
return occupationCode;
|
||||
}
|
||||
|
||||
public void setOccupationCode(String occupationCode) {
|
||||
this.occupationCode = occupationCode;
|
||||
}
|
||||
|
||||
public String getOccupationName() {
|
||||
return occupationName;
|
||||
}
|
||||
|
||||
public void setOccupationName(String occupationName) {
|
||||
this.occupationName = occupationName;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.healthsync.user.repository.entity;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.RefreshToken;
|
||||
import jakarta.persistence.*;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "refresh_tokens", schema = "user_service")
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class RefreshTokenEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false, length = 500)
|
||||
private String token;
|
||||
|
||||
@Column(name = "member_serial_number", nullable = false)
|
||||
private Long memberSerialNumber;
|
||||
|
||||
@Column(name = "expiry_date", nullable = false)
|
||||
private LocalDateTime expiryDate;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
protected RefreshTokenEntity() {}
|
||||
|
||||
public RefreshTokenEntity(String token, Long memberSerialNumber, LocalDateTime expiryDate) {
|
||||
this.token = token;
|
||||
this.memberSerialNumber = memberSerialNumber;
|
||||
this.expiryDate = expiryDate;
|
||||
}
|
||||
|
||||
public static RefreshTokenEntity fromDomain(RefreshToken refreshToken) {
|
||||
RefreshTokenEntity entity = new RefreshTokenEntity();
|
||||
entity.id = refreshToken.getId();
|
||||
entity.token = refreshToken.getToken();
|
||||
entity.memberSerialNumber = refreshToken.getMemberSerialNumber();
|
||||
entity.expiryDate = refreshToken.getExpiryDate();
|
||||
entity.createdAt = refreshToken.getCreatedAt();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public RefreshToken toDomain() {
|
||||
RefreshToken refreshToken = new RefreshToken();
|
||||
refreshToken.setId(this.id);
|
||||
refreshToken.setToken(this.token);
|
||||
refreshToken.setMemberSerialNumber(this.memberSerialNumber);
|
||||
refreshToken.setExpiryDate(this.expiryDate);
|
||||
refreshToken.setCreatedAt(this.createdAt);
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getToken() { return token; }
|
||||
public void setToken(String token) { this.token = token; }
|
||||
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) { this.memberSerialNumber = memberSerialNumber; }
|
||||
|
||||
public LocalDateTime getExpiryDate() { return expiryDate; }
|
||||
public void setExpiryDate(LocalDateTime expiryDate) { this.expiryDate = expiryDate; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.healthsync.user.repository.entity;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "user", schema = "user_service")
|
||||
public class UserEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "member_serial_number")
|
||||
private Long memberSerialNumber;
|
||||
|
||||
@Column(name = "google_id", length = 255, unique = true, nullable = false)
|
||||
private String googleId;
|
||||
|
||||
@Column(name = "name", length = 100, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "birth_date", nullable = false)
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Column(name = "occupation", length = 50)
|
||||
private String occupation;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column(name = "last_login_at")
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
protected UserEntity() {}
|
||||
|
||||
public UserEntity(String googleId, String name, LocalDate birthDate, String occupation) {
|
||||
this.googleId = googleId;
|
||||
this.name = name;
|
||||
this.birthDate = birthDate;
|
||||
this.occupation = occupation;
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
this.lastLoginAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public static UserEntity fromDomain(User user) {
|
||||
UserEntity entity = new UserEntity();
|
||||
// 핵심 수정: 새 엔티티인 경우 ID를 설정하지 않음 (BIGSERIAL이 자동 생성)
|
||||
if (user.getMemberSerialNumber() != null) {
|
||||
entity.memberSerialNumber = user.getMemberSerialNumber();
|
||||
}
|
||||
entity.googleId = user.getGoogleId();
|
||||
entity.name = user.getName();
|
||||
entity.birthDate = user.getBirthDate();
|
||||
entity.occupation = user.getOccupation();
|
||||
entity.createdAt = user.getCreatedAt();
|
||||
entity.updatedAt = user.getUpdatedAt();
|
||||
entity.lastLoginAt = user.getLastLoginAt();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public User toDomain() {
|
||||
User user = new User();
|
||||
user.setMemberSerialNumber(this.memberSerialNumber);
|
||||
user.setGoogleId(this.googleId);
|
||||
user.setName(this.name);
|
||||
user.setBirthDate(this.birthDate);
|
||||
user.setOccupation(this.occupation);
|
||||
user.setCreatedAt(this.createdAt);
|
||||
user.setUpdatedAt(this.updatedAt);
|
||||
user.setLastLoginAt(this.lastLoginAt);
|
||||
return user;
|
||||
}
|
||||
|
||||
public void updateLastLoginAt() {
|
||||
this.lastLoginAt = LocalDateTime.now();
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) { this.memberSerialNumber = memberSerialNumber; }
|
||||
|
||||
public String getGoogleId() { return googleId; }
|
||||
public void setGoogleId(String googleId) { this.googleId = googleId; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public LocalDate getBirthDate() { return birthDate; }
|
||||
public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; }
|
||||
|
||||
public String getOccupation() { return occupation; }
|
||||
public void setOccupation(String occupation) { this.occupation = occupation; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public LocalDateTime getLastLoginAt() { return lastLoginAt; }
|
||||
public void setLastLoginAt(LocalDateTime lastLoginAt) { this.lastLoginAt = lastLoginAt; }
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.healthsync.user.repository.jpa;
|
||||
|
||||
import com.healthsync.user.repository.entity.HealthCheckupRawEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface HealthCheckupRawRepository extends JpaRepository<HealthCheckupRawEntity, Long> {
|
||||
|
||||
// 이름과 생년월일로 최근 건강검진 데이터 조회 (가장 최근 연도)
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate " +
|
||||
"ORDER BY h.referenceYear DESC, h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findByNameAndBirthDateOrderByReferenceYearDesc(
|
||||
@Param("name") String name,
|
||||
@Param("birthDate") LocalDate birthDate);
|
||||
|
||||
// 이름과 생년월일로 최근 건강검진 데이터 1개만 조회 (첫 번째 결과만)
|
||||
@Query(value = "SELECT * FROM health_service.health_checkup_raw h " +
|
||||
"WHERE h.name = :name AND h.birth_date = :birthDate " +
|
||||
"ORDER BY h.reference_year DESC, h.created_at DESC " +
|
||||
"LIMIT 1", nativeQuery = true)
|
||||
Optional<HealthCheckupRawEntity> findMostRecentByNameAndBirthDate(
|
||||
@Param("name") String name,
|
||||
@Param("birthDate") LocalDate birthDate);
|
||||
|
||||
// 특정 연도의 건강검진 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate AND h.referenceYear = :year " +
|
||||
"ORDER BY h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findByNameAndBirthDateAndYear(
|
||||
@Param("name") String name,
|
||||
@Param("birthDate") LocalDate birthDate,
|
||||
@Param("year") Integer year);
|
||||
|
||||
// 이름과 생년월일로 모든 건강검진 이력 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate " +
|
||||
"ORDER BY h.referenceYear DESC, h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findAllByNameAndBirthDate(
|
||||
@Param("name") String name,
|
||||
@Param("birthDate") LocalDate birthDate);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.healthsync.user.repository.jpa;
|
||||
|
||||
import com.healthsync.user.repository.entity.HealthNormalRangeEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface HealthNormalRangeRepository extends JpaRepository<HealthNormalRangeEntity, Integer> {
|
||||
|
||||
// 모든 정상 범위 데이터 조회
|
||||
List<HealthNormalRangeEntity> findAll();
|
||||
|
||||
// 특정 건강 항목 코드로 조회
|
||||
Optional<HealthNormalRangeEntity> findByHealthItemCode(String healthItemCode);
|
||||
|
||||
// 성별 코드로 필터링하여 조회
|
||||
List<HealthNormalRangeEntity> findByGenderCode(Integer genderCode);
|
||||
|
||||
// 특정 건강 항목 코드와 성별 코드로 조회
|
||||
@Query("SELECT h FROM HealthNormalRangeEntity h " +
|
||||
"WHERE h.healthItemCode = :healthItemCode " +
|
||||
"AND (h.genderCode = :genderCode OR h.genderCode IS NULL)")
|
||||
Optional<HealthNormalRangeEntity> findByHealthItemCodeAndGenderCode(
|
||||
@Param("healthItemCode") String healthItemCode,
|
||||
@Param("genderCode") Integer genderCode);
|
||||
|
||||
// 건강 항목명으로 조회
|
||||
List<HealthNormalRangeEntity> findByHealthItemName(String healthItemName);
|
||||
|
||||
// 성별에 맞는 정상 범위 조회 (해당 성별 + 범용(null))
|
||||
@Query("SELECT h FROM HealthNormalRangeEntity h " +
|
||||
"WHERE h.genderCode = :genderCode OR h.genderCode IS NULL " +
|
||||
"ORDER BY h.healthItemCode, h.genderCode DESC")
|
||||
List<HealthNormalRangeEntity> findRelevantByGenderCode(@Param("genderCode") Integer genderCode);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.healthsync.user.repository.jpa;
|
||||
|
||||
import com.healthsync.user.repository.entity.OccupationTypeEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 직업 유형 정보 조회를 위한 리포지토리
|
||||
*/
|
||||
@Repository
|
||||
public interface OccupationTypeRepository extends JpaRepository<OccupationTypeEntity, String> {
|
||||
|
||||
/**
|
||||
* 직업 코드로 직업 정보 조회 (조회 시 사용)
|
||||
*/
|
||||
Optional<OccupationTypeEntity> findByOccupationCode(String occupationCode);
|
||||
|
||||
/**
|
||||
* 직업명으로 직업 정보 조회 (저장 시 사용)
|
||||
*/
|
||||
Optional<OccupationTypeEntity> findByOccupationName(String occupationName);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.healthsync.user.repository.jpa;
|
||||
|
||||
import com.healthsync.user.repository.entity.RefreshTokenEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> {
|
||||
Optional<RefreshTokenEntity> findByToken(String token);
|
||||
Optional<RefreshTokenEntity> findByMemberSerialNumber(Long memberSerialNumber);
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM RefreshTokenEntity r WHERE r.memberSerialNumber = :memberSerialNumber")
|
||||
void deleteByMemberSerialNumber(@Param("memberSerialNumber") Long memberSerialNumber);
|
||||
|
||||
@Modifying
|
||||
@Query("DELETE FROM RefreshTokenEntity r WHERE r.expiryDate < :now")
|
||||
void deleteExpiredTokens(@Param("now") LocalDateTime now);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.healthsync.user.repository.jpa;
|
||||
|
||||
import com.healthsync.user.repository.entity.UserEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<UserEntity, Long> {
|
||||
|
||||
// 기본 조회 메서드
|
||||
Optional<UserEntity> findByGoogleId(String googleId);
|
||||
boolean existsByGoogleId(String googleId);
|
||||
|
||||
// 이름으로 검색
|
||||
List<UserEntity> findByNameContaining(String name);
|
||||
|
||||
// 직업으로 검색
|
||||
List<UserEntity> findByOccupation(String occupation);
|
||||
List<UserEntity> findByOccupationContaining(String occupation);
|
||||
|
||||
// 생년월일 범위 검색
|
||||
List<UserEntity> findByBirthDateBetween(LocalDate startDate, LocalDate endDate);
|
||||
|
||||
// 가입일 범위 검색
|
||||
List<UserEntity> findByCreatedAtBetween(LocalDateTime startDate, LocalDateTime endDate);
|
||||
|
||||
// 최근 로그인한 사용자들
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.lastLoginAt >= :since ORDER BY u.lastLoginAt DESC")
|
||||
List<UserEntity> findRecentlyLoggedInUsers(@Param("since") LocalDateTime since);
|
||||
|
||||
// 특정 기간 동안 로그인하지 않은 사용자들
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.lastLoginAt < :before OR u.lastLoginAt IS NULL")
|
||||
List<UserEntity> findInactiveUsers(@Param("before") LocalDateTime before);
|
||||
|
||||
// 로그인 시간 업데이트
|
||||
@Modifying
|
||||
@Query("UPDATE UserEntity u SET u.lastLoginAt = :lastLoginAt, u.updatedAt = :updatedAt WHERE u.memberSerialNumber = :memberSerialNumber")
|
||||
void updateLastLoginAt(@Param("memberSerialNumber") Long memberSerialNumber,
|
||||
@Param("lastLoginAt") LocalDateTime lastLoginAt,
|
||||
@Param("updatedAt") LocalDateTime updatedAt);
|
||||
|
||||
// 사용자 정보 부분 업데이트
|
||||
@Modifying
|
||||
@Query("UPDATE UserEntity u SET u.name = :name, u.updatedAt = :updatedAt WHERE u.memberSerialNumber = :memberSerialNumber")
|
||||
void updateUserName(@Param("memberSerialNumber") Long memberSerialNumber,
|
||||
@Param("name") String name,
|
||||
@Param("updatedAt") LocalDateTime updatedAt);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE UserEntity u SET u.birthDate = :birthDate, u.updatedAt = :updatedAt WHERE u.memberSerialNumber = :memberSerialNumber")
|
||||
void updateUserBirthDate(@Param("memberSerialNumber") Long memberSerialNumber,
|
||||
@Param("birthDate") LocalDate birthDate,
|
||||
@Param("updatedAt") LocalDateTime updatedAt);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE UserEntity u SET u.occupation = :occupation, u.updatedAt = :updatedAt WHERE u.memberSerialNumber = :memberSerialNumber")
|
||||
void updateUserOccupation(@Param("memberSerialNumber") Long memberSerialNumber,
|
||||
@Param("occupation") String occupation,
|
||||
@Param("updatedAt") LocalDateTime updatedAt);
|
||||
|
||||
// 통계 관련 쿼리
|
||||
@Query("SELECT COUNT(u) FROM UserEntity u WHERE u.createdAt >= :startDate")
|
||||
long countNewUsersFrom(@Param("startDate") LocalDateTime startDate);
|
||||
|
||||
@Query("SELECT COUNT(u) FROM UserEntity u WHERE u.lastLoginAt >= :startDate")
|
||||
long countActiveUsersFrom(@Param("startDate") LocalDateTime startDate);
|
||||
|
||||
@Query("SELECT u.occupation, COUNT(u) FROM UserEntity u WHERE u.occupation IS NOT NULL GROUP BY u.occupation")
|
||||
List<Object[]> countUsersByOccupation();
|
||||
|
||||
// 생년월일이 설정되지 않은 사용자들 (임시값 사용자들)
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.birthDate = :defaultDate")
|
||||
List<UserEntity> findUsersWithDefaultBirthDate(@Param("defaultDate") LocalDate defaultDate);
|
||||
|
||||
// 프로필이 완성되지 않은 사용자들
|
||||
@Query("SELECT u FROM UserEntity u WHERE u.birthDate = :defaultDate OR u.occupation IS NULL")
|
||||
List<UserEntity> findIncompleteProfiles(@Param("defaultDate") LocalDate defaultDate);
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.healthsync.user.service.HealthProfile;
|
||||
|
||||
import com.healthsync.user.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.user.domain.HealthCheck.HealthNormalRange;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface HealthProfileService {
|
||||
|
||||
/**
|
||||
* 이름과 생년월일로 최근 건강검진 데이터 조회
|
||||
*/
|
||||
Optional<HealthCheckupRaw> getMostRecentHealthCheckup(String name, LocalDate birthDate);
|
||||
|
||||
/**
|
||||
* 이름과 생년월일로 모든 건강검진 이력 조회
|
||||
*/
|
||||
List<HealthCheckupRaw> getHealthCheckupHistory(String name, LocalDate birthDate);
|
||||
|
||||
/**
|
||||
* 모든 건강 정상 범위 데이터 조회
|
||||
*/
|
||||
List<HealthNormalRange> getAllHealthNormalRanges();
|
||||
|
||||
/**
|
||||
* 성별 코드에 따른 건강 정상 범위 데이터 조회
|
||||
*/
|
||||
List<HealthNormalRange> getHealthNormalRangesByGender(Integer genderCode);
|
||||
|
||||
/**
|
||||
* 특정 건강 항목의 정상 범위 조회
|
||||
*/
|
||||
Optional<HealthNormalRange> getHealthNormalRangeByItemCode(String healthItemCode, Integer genderCode);
|
||||
|
||||
/**
|
||||
* 성별에 맞는 건강 정상 범위 데이터 조회 (null 허용하는 범용 범위 포함)
|
||||
*/
|
||||
List<HealthNormalRange> getRelevantHealthNormalRanges(Integer genderCode);
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.healthsync.user.service.HealthProfile;
|
||||
|
||||
import com.healthsync.user.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.user.domain.HealthCheck.HealthNormalRange;
|
||||
import com.healthsync.user.repository.entity.HealthCheckupRawEntity;
|
||||
import com.healthsync.user.repository.entity.HealthNormalRangeEntity;
|
||||
import com.healthsync.user.repository.jpa.HealthCheckupRawRepository;
|
||||
import com.healthsync.user.repository.jpa.HealthNormalRangeRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
public class HealthProfileServiceImpl implements HealthProfileService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HealthProfileServiceImpl.class);
|
||||
|
||||
private final HealthCheckupRawRepository healthCheckupRawRepository;
|
||||
private final HealthNormalRangeRepository healthNormalRangeRepository;
|
||||
|
||||
public HealthProfileServiceImpl(HealthCheckupRawRepository healthCheckupRawRepository,
|
||||
HealthNormalRangeRepository healthNormalRangeRepository) {
|
||||
this.healthCheckupRawRepository = healthCheckupRawRepository;
|
||||
this.healthNormalRangeRepository = healthNormalRangeRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HealthCheckupRaw> getMostRecentHealthCheckup(String name, LocalDate birthDate) {
|
||||
logger.info("최근 건강검진 데이터 조회 - 이름: {}, 생년월일: {}", name, birthDate);
|
||||
|
||||
Optional<HealthCheckupRawEntity> entity = healthCheckupRawRepository
|
||||
.findMostRecentByNameAndBirthDate(name, birthDate);
|
||||
|
||||
if (entity.isPresent()) {
|
||||
logger.info("건강검진 데이터 발견 - 검진년도: {}, Raw ID: {}, 성별 코드: {}",
|
||||
entity.get().getReferenceYear(), entity.get().getRawId(), entity.get().getGenderCode());
|
||||
return Optional.of(entity.get().toDomain());
|
||||
} else {
|
||||
logger.warn("해당 사용자의 건강검진 데이터를 찾을 수 없음 - 이름: {}, 생년월일: {}", name, birthDate);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthCheckupRaw> getHealthCheckupHistory(String name, LocalDate birthDate) {
|
||||
logger.info("건강검진 이력 조회 - 이름: {}, 생년월일: {}", name, birthDate);
|
||||
|
||||
List<HealthCheckupRawEntity> entities = healthCheckupRawRepository
|
||||
.findAllByNameAndBirthDate(name, birthDate);
|
||||
|
||||
logger.info("건강검진 이력 {}건 발견", entities.size());
|
||||
|
||||
return entities.stream()
|
||||
.map(HealthCheckupRawEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthNormalRange> getAllHealthNormalRanges() {
|
||||
logger.info("모든 건강 정상 범위 데이터 조회");
|
||||
|
||||
List<HealthNormalRangeEntity> entities = healthNormalRangeRepository.findAll();
|
||||
|
||||
logger.info("건강 정상 범위 데이터 {}건 조회됨", entities.size());
|
||||
|
||||
return entities.stream()
|
||||
.map(HealthNormalRangeEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthNormalRange> getHealthNormalRangesByGender(Integer genderCode) {
|
||||
logger.info("성별별 건강 정상 범위 데이터 조회 - 성별 코드: {}", genderCode);
|
||||
|
||||
List<HealthNormalRangeEntity> entities = healthNormalRangeRepository.findByGenderCode(genderCode);
|
||||
|
||||
logger.info("성별별 건강 정상 범위 데이터 {}건 조회됨", entities.size());
|
||||
|
||||
return entities.stream()
|
||||
.map(HealthNormalRangeEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HealthNormalRange> getHealthNormalRangeByItemCode(String healthItemCode, Integer genderCode) {
|
||||
logger.info("특정 건강 항목 정상 범위 조회 - 항목 코드: {}, 성별 코드: {}", healthItemCode, genderCode);
|
||||
|
||||
Optional<HealthNormalRangeEntity> entity = healthNormalRangeRepository
|
||||
.findByHealthItemCodeAndGenderCode(healthItemCode, genderCode);
|
||||
|
||||
if (entity.isPresent()) {
|
||||
logger.info("건강 항목 정상 범위 발견 - 항목명: {}", entity.get().getHealthItemName());
|
||||
return Optional.of(entity.get().toDomain());
|
||||
} else {
|
||||
logger.warn("해당 건강 항목의 정상 범위를 찾을 수 없음 - 항목 코드: {}, 성별 코드: {}",
|
||||
healthItemCode, genderCode);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthNormalRange> getRelevantHealthNormalRanges(Integer genderCode) {
|
||||
logger.info("성별에 맞는 건강 정상 범위 데이터 조회 - 성별 코드: {}", genderCode);
|
||||
|
||||
List<HealthNormalRangeEntity> entities = healthNormalRangeRepository
|
||||
.findRelevantByGenderCode(genderCode);
|
||||
|
||||
logger.info("관련 건강 정상 범위 데이터 {}건 조회됨", entities.size());
|
||||
|
||||
return entities.stream()
|
||||
.map(HealthNormalRangeEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.healthsync.user.service.Oauth;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
@Service
|
||||
public class JwtTokenService {
|
||||
|
||||
private final JwtEncoder jwtEncoder;
|
||||
private final long accessTokenExpiration;
|
||||
private final long refreshTokenExpiration;
|
||||
|
||||
public JwtTokenService(JwtEncoder jwtEncoder,
|
||||
@Value("${jwt.access-token-expiration}") long accessTokenExpiration,
|
||||
@Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration) {
|
||||
this.jwtEncoder = jwtEncoder;
|
||||
this.accessTokenExpiration = accessTokenExpiration;
|
||||
this.refreshTokenExpiration = refreshTokenExpiration;
|
||||
}
|
||||
|
||||
public String generateAccessToken(User user) {
|
||||
Instant now = Instant.now();
|
||||
Instant expiry = now.plus(accessTokenExpiration, ChronoUnit.MILLIS);
|
||||
|
||||
JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder()
|
||||
.issuer("healthsync")
|
||||
.issuedAt(now)
|
||||
.expiresAt(expiry)
|
||||
.subject(user.getMemberSerialNumber().toString()) // memberSerialNumber를 subject로
|
||||
.claim("googleId", user.getGoogleId()) // googleId
|
||||
.claim("name", user.getName()); // name
|
||||
|
||||
// 생년월일 추가 (null 체크)
|
||||
if (user.getBirthDate() != null) {
|
||||
claimsBuilder.claim("birthDate", user.getBirthDate().toString());
|
||||
}
|
||||
|
||||
JwtClaimsSet claims = claimsBuilder.build();
|
||||
|
||||
return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
|
||||
}
|
||||
|
||||
public long getAccessTokenExpirationTime() {
|
||||
return accessTokenExpiration;
|
||||
}
|
||||
|
||||
public long getRefreshTokenExpirationTime() {
|
||||
return refreshTokenExpiration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.healthsync.user.service.Oauth;
|
||||
|
||||
import com.healthsync.user.dto.Oauth.OAuth2UserInfo;
|
||||
import com.healthsync.user.repository.jpa.UserRepository;
|
||||
import com.healthsync.user.repository.entity.UserEntity;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class OAuth2UserService extends DefaultOAuth2UserService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OAuth2UserService.class);
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public OAuth2UserService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
OAuth2User oauth2User = super.loadUser(userRequest);
|
||||
|
||||
logger.info("OAuth2 사용자 정보 로드 시작");
|
||||
logger.info("사용자 속성: {}", oauth2User.getAttributes());
|
||||
|
||||
return processOAuth2User(userRequest, oauth2User);
|
||||
}
|
||||
|
||||
private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oauth2User) {
|
||||
OAuth2UserInfo oauth2UserInfo = new OAuth2UserInfo(oauth2User.getAttributes());
|
||||
|
||||
String googleId = oauth2UserInfo.getId();
|
||||
|
||||
logger.info("처리할 사용자 정보 - Google ID: {}", googleId);
|
||||
|
||||
// 기존 사용자 확인만 수행 (생성은 Handler에서 처리)
|
||||
Optional<UserEntity> existingUser = userRepository.findByGoogleId(googleId);
|
||||
|
||||
if (existingUser.isPresent()) {
|
||||
logger.info("기존 사용자 확인됨 - ID: {}", existingUser.get().getMemberSerialNumber());
|
||||
} else {
|
||||
logger.info("신규 사용자 - Handler에서 생성될 예정");
|
||||
}
|
||||
|
||||
// role 정보 없이 기본 권한만 부여
|
||||
return new DefaultOAuth2User(
|
||||
Collections.singleton(() -> "ROLE_USER"),
|
||||
oauth2User.getAttributes(),
|
||||
"sub"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.user.service.Oauth;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.RefreshToken;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface RefreshTokenService {
|
||||
RefreshToken createRefreshToken(Long memberSerialNumber);
|
||||
Optional<RefreshToken> findByToken(String token);
|
||||
RefreshToken verifyExpiration(RefreshToken token);
|
||||
void deleteByMemberSerialNumber(Long memberSerialNumber);
|
||||
void deleteExpiredTokens();
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.healthsync.user.service.Oauth;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.RefreshToken;
|
||||
import com.healthsync.user.repository.entity.RefreshTokenEntity;
|
||||
import com.healthsync.user.repository.jpa.RefreshTokenRepository;
|
||||
import com.healthsync.user.exception.AuthenticationException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class RefreshTokenServiceImpl implements RefreshTokenService {
|
||||
|
||||
private final RefreshTokenRepository refreshTokenRepository;
|
||||
private final long refreshTokenExpiration;
|
||||
|
||||
public RefreshTokenServiceImpl(RefreshTokenRepository refreshTokenRepository,
|
||||
@Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration) {
|
||||
this.refreshTokenRepository = refreshTokenRepository;
|
||||
this.refreshTokenExpiration = refreshTokenExpiration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshToken createRefreshToken(Long memberSerialNumber) {
|
||||
// 기존 리프레시 토큰 삭제
|
||||
refreshTokenRepository.deleteByMemberSerialNumber(memberSerialNumber);
|
||||
|
||||
// 새 리프레시 토큰 생성
|
||||
String token = UUID.randomUUID().toString();
|
||||
LocalDateTime expiryDate = LocalDateTime.now().plusSeconds(refreshTokenExpiration / 1000);
|
||||
|
||||
RefreshToken refreshToken = new RefreshToken(token, memberSerialNumber, expiryDate);
|
||||
RefreshTokenEntity entity = RefreshTokenEntity.fromDomain(refreshToken);
|
||||
RefreshTokenEntity savedEntity = refreshTokenRepository.save(entity);
|
||||
|
||||
return savedEntity.toDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<RefreshToken> findByToken(String token) {
|
||||
return refreshTokenRepository.findByToken(token)
|
||||
.map(RefreshTokenEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefreshToken verifyExpiration(RefreshToken token) {
|
||||
if (token.isExpired()) {
|
||||
refreshTokenRepository.deleteByMemberSerialNumber(token.getMemberSerialNumber());
|
||||
throw new AuthenticationException("리프레시 토큰이 만료되었습니다. 다시 로그인해주세요.");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByMemberSerialNumber(Long memberSerialNumber) {
|
||||
refreshTokenRepository.deleteByMemberSerialNumber(memberSerialNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredTokens() {
|
||||
refreshTokenRepository.deleteExpiredTokens(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.healthsync.user.service.UserProfile;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import com.healthsync.user.dto.UserProfile.OccupationDto;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
|
||||
public interface UserService {
|
||||
|
||||
// 기존 메서드들
|
||||
User saveUser(User user);
|
||||
Optional<User> findByGoogleId(String googleId);
|
||||
Optional<User> findById(Long memberSerialNumber);
|
||||
User updateUser(User user);
|
||||
void updateLastLoginAt(Long memberSerialNumber);
|
||||
boolean existsByGoogleId(String googleId);
|
||||
|
||||
// 직업 코드 변환 메서드 추가
|
||||
/**
|
||||
* 직업 코드를 직업명으로 변환 (조회 시 사용)
|
||||
* @param occupationCode 직업 코드
|
||||
* @return 직업명
|
||||
*/
|
||||
String convertOccupationCodeToName(String occupationCode);
|
||||
|
||||
/**
|
||||
* 직업명을 직업 코드로 변환 (저장 시 사용)
|
||||
* @param occupationName 직업명
|
||||
* @return 직업 코드
|
||||
*/
|
||||
String convertOccupationNameToCode(String occupationName);
|
||||
|
||||
/**
|
||||
* 모든 직업 목록 조회 (선택사항)
|
||||
* @return 직업 목록
|
||||
*/
|
||||
List<OccupationDto> getAllOccupations();
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package com.healthsync.user.service.UserProfile;
|
||||
|
||||
import com.healthsync.user.domain.Oauth.User;
|
||||
import com.healthsync.user.repository.jpa.UserRepository;
|
||||
import com.healthsync.user.repository.jpa.OccupationTypeRepository;
|
||||
import com.healthsync.user.repository.entity.UserEntity;
|
||||
import com.healthsync.user.repository.entity.OccupationTypeEntity;
|
||||
import com.healthsync.user.dto.UserProfile.OccupationDto;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final OccupationTypeRepository occupationTypeRepository;
|
||||
|
||||
public UserServiceImpl(UserRepository userRepository,
|
||||
OccupationTypeRepository occupationTypeRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.occupationTypeRepository = occupationTypeRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertOccupationCodeToName(String occupationCode) {
|
||||
if (occupationCode == null || occupationCode.trim().isEmpty()) {
|
||||
return "정보 없음";
|
||||
}
|
||||
|
||||
logger.debug("직업 코드를 이름으로 변환: {}", occupationCode);
|
||||
|
||||
return occupationTypeRepository.findByOccupationCode(occupationCode)
|
||||
.map(entity -> {
|
||||
logger.debug("직업 코드 변환 성공: {} -> {}", occupationCode, entity.getOccupationName());
|
||||
return entity.getOccupationName();
|
||||
})
|
||||
.orElseGet(() -> {
|
||||
logger.warn("직업 코드를 찾을 수 없음: {}", occupationCode);
|
||||
return occupationCode; // 코드가 없으면 원래 값 반환
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertOccupationNameToCode(String occupationName) {
|
||||
if (occupationName == null || occupationName.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.debug("직업명을 코드로 변환: {}", occupationName);
|
||||
|
||||
return occupationTypeRepository.findByOccupationName(occupationName)
|
||||
.map(entity -> {
|
||||
logger.debug("직업명 변환 성공: {} -> {}", occupationName, entity.getOccupationCode());
|
||||
return entity.getOccupationCode();
|
||||
})
|
||||
.orElseGet(() -> {
|
||||
logger.warn("직업명을 찾을 수 없음: {}", occupationName);
|
||||
return occupationName; // 매칭되는 코드가 없으면 원래 값 반환
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<User> findById(Long memberSerialNumber) {
|
||||
return userRepository.findById(memberSerialNumber)
|
||||
.map(entity -> {
|
||||
User user = entity.toDomain();
|
||||
// 조회할 때 occupation 코드를 이름으로 변환
|
||||
if (user.getOccupation() != null) {
|
||||
String occupationName = convertOccupationCodeToName(user.getOccupation());
|
||||
user.setOccupation(occupationName);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<User> findByGoogleId(String googleId) {
|
||||
return userRepository.findByGoogleId(googleId)
|
||||
.map(entity -> {
|
||||
User user = entity.toDomain();
|
||||
// 조회할 때 occupation 코드를 이름으로 변환
|
||||
if (user.getOccupation() != null) {
|
||||
String occupationName = convertOccupationCodeToName(user.getOccupation());
|
||||
user.setOccupation(occupationName);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public User saveUser(User user) {
|
||||
logger.info("새 사용자 저장");
|
||||
|
||||
// 저장하기 전에 occupation 이름을 코드로 변환
|
||||
String originalOccupation = user.getOccupation();
|
||||
if (originalOccupation != null) {
|
||||
String occupationCode = convertOccupationNameToCode(originalOccupation);
|
||||
user.setOccupation(occupationCode);
|
||||
logger.debug("직업 정보 변환하여 저장: {} -> {}", originalOccupation, occupationCode);
|
||||
}
|
||||
|
||||
UserEntity entity = UserEntity.fromDomain(user);
|
||||
UserEntity savedEntity = userRepository.save(entity);
|
||||
|
||||
// 저장 후 다시 조회해서 코드를 이름으로 변환하여 반환
|
||||
User savedUser = savedEntity.toDomain();
|
||||
if (savedUser.getOccupation() != null) {
|
||||
String occupationName = convertOccupationCodeToName(savedUser.getOccupation());
|
||||
savedUser.setOccupation(occupationName);
|
||||
}
|
||||
|
||||
logger.info("새 사용자 저장 완료: {}", savedUser.getMemberSerialNumber());
|
||||
return savedUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User updateUser(User user) {
|
||||
logger.info("사용자 정보 업데이트: {}", user.getMemberSerialNumber());
|
||||
|
||||
// 저장하기 전에 occupation 이름을 코드로 변환
|
||||
String originalOccupation = user.getOccupation();
|
||||
if (originalOccupation != null) {
|
||||
String occupationCode = convertOccupationNameToCode(originalOccupation);
|
||||
user.setOccupation(occupationCode);
|
||||
logger.debug("직업 정보 변환하여 저장: {} -> {}", originalOccupation, occupationCode);
|
||||
}
|
||||
|
||||
// 기존 구현 유지
|
||||
user.setUpdatedAt(LocalDateTime.now());
|
||||
UserEntity entity = UserEntity.fromDomain(user);
|
||||
UserEntity savedEntity = userRepository.save(entity);
|
||||
|
||||
// 저장 후 다시 조회해서 코드를 이름으로 변환하여 반환
|
||||
User savedUser = savedEntity.toDomain();
|
||||
if (savedUser.getOccupation() != null) {
|
||||
String occupationName = convertOccupationCodeToName(savedUser.getOccupation());
|
||||
savedUser.setOccupation(occupationName);
|
||||
}
|
||||
|
||||
logger.info("사용자 정보 업데이트 완료: {}", savedUser.getMemberSerialNumber());
|
||||
return savedUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLastLoginAt(Long memberSerialNumber) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
userRepository.updateLastLoginAt(memberSerialNumber, now, now);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public boolean existsByGoogleId(String googleId) {
|
||||
return userRepository.existsByGoogleId(googleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<OccupationDto> getAllOccupations() {
|
||||
logger.info("모든 직업 목록 조회");
|
||||
|
||||
return occupationTypeRepository.findAll()
|
||||
.stream()
|
||||
.map(entity -> new OccupationDto(
|
||||
entity.getOccupationCode(),
|
||||
entity.getOccupationName(),
|
||||
entity.getCategory()
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
spring:
|
||||
application:
|
||||
name: user-service
|
||||
|
||||
datasource:
|
||||
url: ${DB_URL:jdbc:postgresql://psql-digitalgarage-01.postgres.database.azure.com:5432/healthsync_db}
|
||||
username: ${DB_USERNAME:team1tier}
|
||||
password: ${DB_PASSWORD:Hi5Jessica!}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
hikari:
|
||||
connection-timeout: 20000
|
||||
maximum-pool-size: 10
|
||||
minimum-idle: 5
|
||||
idle-timeout: 300000
|
||||
max-lifetime: 1200000
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
show-sql: ${SHOW_SQL:false}
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
format_sql: true
|
||||
jdbc:
|
||||
time_zone: UTC
|
||||
|
||||
# JWT 설정
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
google:
|
||||
client-id: ${GOOGLE_CLIENT_ID:67517118327-m9mqpr78k24f5j2iillj2ovjdt3f2vt4.apps.googleusercontent.com}
|
||||
client-secret: ${GOOGLE_CLIENT_SECRET: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}
|
||||
scope:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
redirect-uri: ${GOOGLE_REDIRECT_ID:http://localhost:8081/login/oauth2/code/google}
|
||||
# redirect-uri: "http://team1tier.20.214.196.128.nip.io/login/oauth2/code/google"
|
||||
provider:
|
||||
google:
|
||||
authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
|
||||
token-uri: https://www.googleapis.com/oauth2/v4/token
|
||||
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
|
||||
user-name-attribute: sub
|
||||
resource server:
|
||||
jwt:
|
||||
issuer-uri: "http://team1tier.20.214.196.128.nip.io"
|
||||
mvc:
|
||||
log-request-details: true # 요청 파라미터와 헤더 로깅 활성화
|
||||
|
||||
|
||||
server:
|
||||
port: ${SERVER_PORT:8081}
|
||||
|
||||
|
||||
|
||||
jwt:
|
||||
private-key: "-----BEGIN PRIVATE KEY-----MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCnHuDmeTbJcxNtY/VJbCktLaqEwCEJStwY32A24xh6jMjz0km9JNpWXpVUtAuAXy+g9omD0u+38E5CdFu6INgykmLeSsm2h8GftiDPX8Nf2NF8HcikFD7N1G2X8loswSTvg40hzPnHllOJr0ZmXAoN4Z55ndkPdPUj8qB364nqx9Yv0natlm9fu0a7WOPB7PtWY5qZa9uCDZ4rn1xaVgHTT89lEqvPVvbnrpbgtyRKK7DEj3AaAsXMHoxYgqyM+h3U3HZjWAdsC50Stl/4Snz+kYbEcsp3HJUbo627Tn2rxssvxktTiYuW0c1RHqZXIXumi2AmLqeRqt0VCBMR+0HDAgMBAAECggEAFPpMy9FqXaYqyZ/zAcjocEnbrjc5zmNNtnePqcQe5f83GFgMtofiOlY8E3pYOUB5h5B62YfIXIP3JuNZQkduLAbxDys/H8Dxvp0LiExih2z9esF4VpRN/+NK8HhU9mo2OzR9qkEDF5kYml9cjGvAPVbVYDm+rfCF9wG1P+hakxRXY5dua3J+Z7KBGYWfTGRLdb8/xl07s7eb4CDDmEWAae+LwLIUXWY1Gh82lp+AhgkWv6Q2ohxqFA6cOR5lScRqLAN7be6pbvIvKlNYFdectaTaRuD7m4rCfQnNpX+TyX7Q53qSzTPddvcymkKaUwaXw2jXSPhgdrUJ9PKbhJ0z/QKBgQDmonJTGZ+gq1RvIWC9IuVUyIwXk3umN4EP6qI/4BxargO4sVAOOS6m61HugpSLehTp9XL09GFHKFsQd9yT0B+hqIIF+BTWXlvbQ2eEzFsIhCc2yAeV414sLWRAIfllMPKk3rc+m5bD81KgPW5yrwJtMgxgoGtj6ETH3BPg0/oIpwKBgQC5gC/Lm8cTWN5AfK30DW/fYD7/RxHsaYSuAN0hLOBGBoGbfqk0f6AG6IOIxYJsU7/uKtXlDNvSPdvXYCupe39S8WDKB1YxpDCODwYWVqfRTEuJzD0SbF3stcn2wwpWyxDRtep947o/P+VCePVgZtoWf8340n89h1bIZ+EXHXqFhQKBgHcwqp6RlnJFOMx51nHIb/ZB8kxY1sUO2C8ulg0mt+CRH7E6SWIgYSC4ak41w6jVPauvQmqfRQquK2m2WBM3srEr0Y5eJ/6lIxmMmxoBNmaPTWi9NVZb+5YfGzkdlbKa+jsEMnUzmVXJEQFo3gR8t2dRPx5MqVMnfSxAazF8uzHvAoGAGQKTbxw9pvogXQlyWqlFIBTV6Y0neXxwixVKuyJVypst9k0Jey6J4OSQd2xJvVk9U1srI4qsSJhWf59Tw7IG5KPurM54bJD6iuyzoWdlkO58cMO8qDM8JqIL7N03E6SlS+D/EKIXhleTDXdJfgnf9ZCdsKKQzTbmGHcI/hjXYBECgYEAiTW83qOqgioI0kJen5cM9/bTkMgYU22I2p2fFdnYEtOn2tIrTJ198fwr1hpWqm/rwK3mig4DyM2/G7cNyJTqexM0gxKqluRPM+ebzyDY9crF4A34lhKsf267tQbasr9uaVrPM23a8CflTrZGAtTMVtJKtHKNFBFxV+p5o6LRDb8=-----END PRIVATE KEY-----"
|
||||
public-key: "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApx7g5nk2yXMTbWP1SWwpLS2qhMAhCUrcGN9gNuMYeozI89JJvSTaVl6VVLQLgF8voPaJg9Lvt/BOQnRbuiDYMpJi3krJtofBn7Ygz1/DX9jRfB3IpBQ+zdRtl/JaLMEk74ONIcz5x5ZTia9GZlwKDeGeeZ3ZD3T1I/Kgd+uJ6sfWL9J2rZZvX7tGu1jjwez7VmOamWvbgg2eK59cWlYB00/PZRKrz1b2566W4LckSiuwxI9wGgLFzB6MWIKsjPod1Nx2Y1gHbAudErZf+Ep8/pGGxHLKdxyVG6Otu059q8bLL8ZLU4mLltHNUR6mVyF7potgJi6nkardFQgTEftBwwIDAQAB-----END PUBLIC KEY-----"
|
||||
access-token-expiration: 1800000 # 30 minutes in milliseconds
|
||||
refresh-token-expiration: 604800000 # 7 days in milliseconds
|
||||
|
||||
# 외부 서비스 URL
|
||||
services:
|
||||
health-service:
|
||||
url: ${HEALTH_SERVICE_URL:http://localhost:8082}
|
||||
goal-service:
|
||||
url: ${GOAL_SERVICE_URL:http://localhost:8084}
|
||||
|
||||
|
||||
# 로깅 설정
|
||||
logging:
|
||||
level:
|
||||
com.healthsync.user: ${LOG_LEVEL:TRACE}
|
||||
org.springframework.web: ${WEB_LOG_LEVEL:TRACE}
|
||||
org.springframework.security: ${SECURITY_LOG_LEVEL:TRACE}
|
||||
org.springframework.data.jpa: ${JPA_LOG_LEVEL:TRACE}
|
||||
# OAuth2 관련 로깅 추가
|
||||
org.springframework.security.oauth2: TRACE
|
||||
org.springframework.security.oauth2.client: TRACE
|
||||
org.springframework.security.oauth2.server.resource: TRACE
|
||||
org.springframework.security.jwt: TRACE
|
||||
org.springframework.security.web.authentication: TRACE
|
||||
org.springframework.security.web.FilterChainProxy: TRACE
|
||||
org.apache.http: TRACE
|
||||
org.apache.http.wire: TRACE # HTTP 와이어 레벨 로깅 (실제 HTTP 메시지)
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
|
||||
# Management endpoints
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
enabled: true
|
||||
|
||||
app:
|
||||
oauth2:
|
||||
redirect-url: ${OAUTH2_REDIRECT_URL:http://localhost:3000/login}
|
||||
Reference in New Issue
Block a user