feat : initial commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
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'
|
||||
}
|
||||
|
||||
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 HealthServiceApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HealthServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.healthsync.common.dto;
|
||||
|
||||
public class CusApiResponse<T> {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private T data;
|
||||
private String error;
|
||||
|
||||
public CusApiResponse() {}
|
||||
|
||||
public CusApiResponse(boolean success, String message, T data) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public CusApiResponse(boolean success, String message, String error) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public static <T> CusApiResponse<T> success(T data, String message) {
|
||||
return new CusApiResponse<>(true, message, data);
|
||||
}
|
||||
|
||||
public static <T> CusApiResponse<T> error(String message, String error) {
|
||||
return new CusApiResponse<>(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.CusApiResponse;
|
||||
import com.healthsync.common.response.ResponseHelper;
|
||||
import com.healthsync.health.exception.AuthenticationException;
|
||||
import com.healthsync.health.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<CusApiResponse<Void>> handleUserNotFoundException(UserNotFoundException e) {
|
||||
logger.error("User not found: {}", e.getMessage());
|
||||
return ResponseHelper.notFound("사용자를 찾을 수 없습니다", e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(AuthenticationException.class)
|
||||
public ResponseEntity<CusApiResponse<Void>> handleAuthenticationException(AuthenticationException e) {
|
||||
logger.error("Authentication error: {}", e.getMessage());
|
||||
return ResponseHelper.unauthorized("인증에 실패했습니다", e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public ResponseEntity<CusApiResponse<Void>> handleAccessDeniedException(AccessDeniedException e) {
|
||||
logger.error("Access denied: {}", e.getMessage());
|
||||
return ResponseHelper.forbidden("접근이 거부되었습니다", e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<CusApiResponse<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.CusApiResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
public class ResponseHelper {
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> success(T data, String message) {
|
||||
return ResponseEntity.ok(CusApiResponse.success(data, message));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> created(T data, String message) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(CusApiResponse.success(data, message));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> badRequest(String message, String error) {
|
||||
return ResponseEntity.badRequest().body(CusApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> unauthorized(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(CusApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> forbidden(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(CusApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> notFound(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(CusApiResponse.error(message, error));
|
||||
}
|
||||
|
||||
public static <T> ResponseEntity<CusApiResponse<T>> internalServerError(String message, String error) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(CusApiResponse.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,91 @@
|
||||
package com.healthsync.health.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 HealthJwtConfig {
|
||||
|
||||
@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,73 @@
|
||||
package com.healthsync.health.config;
|
||||
|
||||
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 HealthSecurityConfig {
|
||||
|
||||
|
||||
@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()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
|
||||
.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.health.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 HealthSwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI healthOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(apiInfo())
|
||||
.servers(List.of(
|
||||
new Server().url("http://localhost:8082").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,17 @@
|
||||
package com.healthsync.health.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;
|
||||
}
|
||||
}
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
package com.healthsync.health.controller;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
import com.healthsync.health.domain.Oauth.User;
|
||||
import com.healthsync.health.dto.HealthCheck.HealthCheckupSyncResult;
|
||||
import com.healthsync.health.dto.HealthCheck.HealthProfileHistoryResponse;
|
||||
import com.healthsync.health.service.HealthProfile.HealthProfileService;
|
||||
import com.healthsync.health.service.HealthProfile.RealisticHealthMockDataGenerator;
|
||||
import com.healthsync.health.service.UserProfile.UserService;
|
||||
import com.healthsync.common.util.JwtUtil;
|
||||
import com.healthsync.common.dto.CusApiResponse;
|
||||
import com.healthsync.common.response.ResponseHelper;
|
||||
import com.healthsync.common.exception.BusinessException;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
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 io.swagger.v3.oas.annotations.Parameter;
|
||||
|
||||
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 org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/health")
|
||||
@Tag(name = "건강 프로필", description = "건강 프로필 관리 API")
|
||||
@SecurityRequirement(name = "Bearer Authentication")
|
||||
public class HealthCheckupController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HealthCheckupController.class);
|
||||
|
||||
private final HealthProfileService healthProfileService;
|
||||
private final UserService userService;
|
||||
private final JwtUtil jwtUtil;
|
||||
private final RealisticHealthMockDataGenerator realisticMockGenerator;
|
||||
|
||||
// Mock 데이터 생성 활성화 여부
|
||||
@Value("${health.mock.enabled:true}")
|
||||
private boolean mockDataEnabled;
|
||||
|
||||
public HealthCheckupController(HealthProfileService healthProfileService,
|
||||
UserService userService,
|
||||
JwtUtil jwtUtil,
|
||||
RealisticHealthMockDataGenerator realisticMockGenerator) {
|
||||
this.healthProfileService = healthProfileService;
|
||||
this.userService = userService;
|
||||
this.jwtUtil = jwtUtil;
|
||||
this.realisticMockGenerator = realisticMockGenerator;
|
||||
}
|
||||
|
||||
@GetMapping("/checkup/history")
|
||||
@Operation(
|
||||
summary = "건강검진 이력 조회",
|
||||
description = "기존 로직을 완전히 따르는 건강검진 데이터 조회:\n" +
|
||||
"1. health_checkup_raw 테이블에서 데이터 조회\n" +
|
||||
"2. 데이터가 없으면 Mock Raw 데이터를 health_checkup_raw에 저장\n" +
|
||||
"3. 저장된 Raw 데이터를 기존 로직으로 health_checkup에 처리\n" +
|
||||
"4. 응답 생성"
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "건강검진 이력 조회 성공 (실제 데이터)"),
|
||||
@ApiResponse(responseCode = "200", description = "건강검진 이력 조회 성공 (Mock 데이터 생성 및 처리)"),
|
||||
@ApiResponse(responseCode = "200", description = "건강검진 이력 조회 성공 (데이터 없음)"),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패"),
|
||||
@ApiResponse(responseCode = "400", description = "사용자 정보 부족")
|
||||
})
|
||||
public ResponseEntity<CusApiResponse<HealthProfileHistoryResponse>> getHealthCheckupHistory(
|
||||
@AuthenticationPrincipal Jwt jwt,
|
||||
@Parameter(description = "Mock 데이터 생성 강제 여부", example = "false")
|
||||
@RequestParam(name = "forceMock", defaultValue = "false") boolean forceMock) {
|
||||
|
||||
logger.info("건강검진 이력 조회 요청 - Mock 강제: {}", forceMock);
|
||||
|
||||
try {
|
||||
// 1. JWT에서 memberSerialNumber 추출
|
||||
Long memberSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
logger.debug("회원 일련번호: {}", memberSerialNumber);
|
||||
|
||||
// 2. 사용자 정보 조회
|
||||
User user = userService.findById(memberSerialNumber)
|
||||
.orElseThrow(() -> new BusinessException("USER_NOT_FOUND", "사용자를 찾을 수 없습니다: " + memberSerialNumber));
|
||||
|
||||
// 3. 생년월일 검증
|
||||
LocalDate birthDate = user.getBirthDate();
|
||||
if (birthDate == null) {
|
||||
logger.warn("사용자 생년월일 정보 없음 - Member Serial Number: {}", memberSerialNumber);
|
||||
return ResponseHelper.badRequest("사용자 생년월일 정보가 필요합니다.", "BIRTH_DATE_REQUIRED");
|
||||
}
|
||||
|
||||
// === 기존 로직 완전히 따르기 ===
|
||||
|
||||
// 4. health_checkup_raw 테이블에서 데이터 조회 (기존 로직 1단계)
|
||||
List<HealthCheckupRaw> rawHealthProfiles = healthProfileService
|
||||
.getHealthCheckupHistory5years(user.getName(), birthDate);
|
||||
|
||||
// 5. Mock 강제 사용이 아니고 Raw 데이터가 있는 경우 - 기존 로직 그대로 진행
|
||||
if (!forceMock && !rawHealthProfiles.isEmpty()) {
|
||||
logger.info("실제 Raw 데이터 발견 - {} 건", rawHealthProfiles.size());
|
||||
return processExistingRawData(user, birthDate, memberSerialNumber, rawHealthProfiles);
|
||||
}
|
||||
|
||||
// 6. Raw 데이터가 없거나 Mock 강제 사용인 경우 - 현실적인 Mock 데이터 생성
|
||||
if (mockDataEnabled || forceMock) {
|
||||
logger.info("Raw 데이터 없음, 현실적인 다중 연도 Mock 데이터 생성 - Member: {}", memberSerialNumber);
|
||||
|
||||
// 성별 정보 추정
|
||||
Integer genderCode = estimateGenderCode(user.getName());
|
||||
|
||||
// 🎯 실제 User 객체를 전달하여 일관된 개인정보 사용
|
||||
CusApiResponse<HealthProfileHistoryResponse> mockResponse =
|
||||
realisticMockGenerator.generateRealisticMockData(
|
||||
user, // ✅ User 객체 전체 전달
|
||||
genderCode,
|
||||
memberSerialNumber,
|
||||
5 // 기본 5년 데이터 생성
|
||||
);
|
||||
|
||||
logger.info("현실적인 Mock 데이터 생성 및 처리 완료 - 사용자: {} ({}세), Member: {}",
|
||||
user.getName(), Period.between(user.getBirthDate(), LocalDate.now()).getYears(), memberSerialNumber);
|
||||
return ResponseEntity.ok(mockResponse);
|
||||
}
|
||||
|
||||
// 7. Mock 데이터도 비활성화된 경우 빈 응답
|
||||
logger.info("건강검진 데이터 없음 & Mock 비활성화 - Member: {}", memberSerialNumber);
|
||||
return ResponseHelper.badRequest("BUSINESS_ERROR", "BUSINESS_ERROR");
|
||||
|
||||
} catch (BusinessException e) {
|
||||
logger.error("비즈니스 로직 오류: {}", e.getMessage());
|
||||
return ResponseHelper.badRequest(e.getMessage(), "BUSINESS_ERROR");
|
||||
} catch (Exception e) {
|
||||
logger.error("건강검진 이력 조회 중 오류 발생", e);
|
||||
return ResponseHelper.internalServerError(
|
||||
"건강검진 이력 조회 중 오류가 발생했습니다.",
|
||||
"HISTORY_ERROR"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 Raw 데이터가 있는 경우 기존 로직으로 처리
|
||||
*
|
||||
* 기존 로직:
|
||||
* 1. Raw 데이터 조회됨
|
||||
* 2. 가공된 데이터 조회 및 동기화
|
||||
* 3. 응답 생성
|
||||
*/
|
||||
private ResponseEntity<CusApiResponse<HealthProfileHistoryResponse>> processExistingRawData(
|
||||
User user, LocalDate birthDate, Long memberSerialNumber, List<HealthCheckupRaw> rawHealthProfiles) {
|
||||
|
||||
logger.info("기존 로직으로 실제 Raw 데이터 처리 - {} 건", rawHealthProfiles.size());
|
||||
|
||||
try {
|
||||
// ✅ 이 부분이 누락되어 있음 - Raw 데이터를 health_checkup에 동기화
|
||||
HealthCheckupSyncResult syncResult = healthProfileService
|
||||
.syncHealthCheckupData(rawHealthProfiles, memberSerialNumber);
|
||||
|
||||
logger.info("Raw 데이터 동기화 완료 - 신규: {}, 갱신: {}, 건너뜀: {}",
|
||||
syncResult.getNewCount(), syncResult.getUpdatedCount(), syncResult.getSkippedCount());
|
||||
|
||||
// 기존 로직 2단계: 가공된 데이터 조회 및 동기화
|
||||
Optional<HealthCheckup> processedHealthProfile = healthProfileService
|
||||
.getLatestProcessedHealthCheckup(memberSerialNumber);
|
||||
|
||||
// 사용자 정보 구성
|
||||
HealthProfileHistoryResponse.UserInfo userInfo = createUserInfo(user, birthDate, rawHealthProfiles);
|
||||
|
||||
HealthProfileHistoryResponse response;
|
||||
|
||||
if (processedHealthProfile.isPresent()) {
|
||||
// 가공된 데이터가 있는 경우
|
||||
HealthCheckup processed = processedHealthProfile.get();
|
||||
HealthCheckupRaw recentHealthProfile = findCorrespondingRawData(processed, rawHealthProfiles);
|
||||
|
||||
// ✅ 수정: 전체 rawHealthProfiles 사용
|
||||
response = new HealthProfileHistoryResponse(userInfo, recentHealthProfile, rawHealthProfiles);
|
||||
|
||||
logger.info("가공된 데이터 응답 - Member: {}, 검진년도: {}",
|
||||
memberSerialNumber, processed.getReferenceYear());
|
||||
|
||||
return ResponseHelper.success(response, "건강검진 이력 조회 성공");
|
||||
} else {
|
||||
// 가공된 데이터가 없으면 Raw 데이터만 응답
|
||||
HealthCheckupRaw recentRaw = rawHealthProfiles.get(0);
|
||||
response = new HealthProfileHistoryResponse(userInfo, recentRaw, rawHealthProfiles);
|
||||
|
||||
logger.info("Raw 데이터 응답 - Member: {}, 검진년도: {}",
|
||||
memberSerialNumber, recentRaw.getReferenceYear());
|
||||
|
||||
return ResponseHelper.success(response, "건강검진 이력 조회 성공 (Raw 데이터)");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("기존 Raw 데이터 처리 중 오류 - Member: {}", memberSerialNumber, e);
|
||||
|
||||
// 오류 발생 시 사용자 정보만 포함된 응답
|
||||
HealthProfileHistoryResponse.UserInfo userInfo = createUserInfo(user, birthDate, rawHealthProfiles);
|
||||
HealthProfileHistoryResponse response = new HealthProfileHistoryResponse(userInfo);
|
||||
|
||||
CusApiResponse<HealthProfileHistoryResponse> apiResponse =
|
||||
new CusApiResponse<>(false, "건강검진 데이터 처리 중 오류가 발생했습니다.", response);
|
||||
return ResponseEntity.ok(apiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock 데이터 생성용 별도 엔드포인트 (개발/테스트용)
|
||||
*/
|
||||
@GetMapping("/checkup/mock")
|
||||
@Operation(
|
||||
summary = "현실적인 Mock 건강검진 데이터 생성 (개발용)",
|
||||
description = "현실적인 건강 변화 패턴을 반영한 다중 연도 Mock 데이터를 생성합니다:\n" +
|
||||
"1. 개인별 건강 베이스라인 생성 (건강형/평균형/주의형)\n" +
|
||||
"2. 연도별 비선형적 건강 변화 패턴 적용\n" +
|
||||
"3. health_checkup_raw 테이블에 다중 연도 데이터 저장\n" +
|
||||
"4. 기존 syncHealthCheckupData로 health_checkup에 처리"
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(responseCode = "200", description = "현실적인 Mock 데이터 생성 및 처리 성공"),
|
||||
@ApiResponse(responseCode = "403", description = "Mock 데이터 생성이 비활성화됨"),
|
||||
@ApiResponse(responseCode = "401", description = "인증 실패")
|
||||
})
|
||||
public ResponseEntity<CusApiResponse<HealthProfileHistoryResponse>> generateMockHealthData(
|
||||
@AuthenticationPrincipal Jwt jwt,
|
||||
@Parameter(description = "성별 코드 (1: 남성, 2: 여성)", example = "1")
|
||||
@RequestParam(name = "gender", defaultValue = "1") Integer genderCode,
|
||||
@Parameter(description = "생성할 연도 수 (1~5년)", example = "3")
|
||||
@RequestParam(name = "yearCount", defaultValue = "3") Integer yearCount) {
|
||||
|
||||
if (!mockDataEnabled) {
|
||||
logger.warn("Mock 데이터 생성이 비활성화됨");
|
||||
return ResponseHelper.forbidden("Mock 데이터 생성이 비활성화되어 있습니다.", "MOCK_DISABLED");
|
||||
}
|
||||
|
||||
logger.info("현실적인 Mock 건강검진 데이터 생성 요청 - 연도 수: {}", yearCount);
|
||||
|
||||
try {
|
||||
// JWT에서 memberSerialNumber 추출
|
||||
Long memberSerialNumber = Long.valueOf(jwt.getSubject());
|
||||
|
||||
// 사용자 정보 조회
|
||||
User user = userService.findById(memberSerialNumber)
|
||||
.orElseThrow(() -> new BusinessException("USER_NOT_FOUND", "사용자를 찾을 수 없습니다: " + memberSerialNumber));
|
||||
|
||||
// 생년월일 검증
|
||||
LocalDate birthDate = user.getBirthDate();
|
||||
if (birthDate == null) {
|
||||
// 기본 생년월일 설정 (30세 기준)
|
||||
birthDate = LocalDate.now().minusYears(30);
|
||||
logger.warn("생년월일 정보 없음, 기본값 설정: {}", birthDate);
|
||||
}
|
||||
|
||||
// 현실적인 다중 연도 Mock 데이터 생성 및 처리
|
||||
CusApiResponse<HealthProfileHistoryResponse> mockResponse =
|
||||
realisticMockGenerator.generateRealisticMockData(
|
||||
user, // ✅ User 객체 전체 전달
|
||||
genderCode,
|
||||
memberSerialNumber,
|
||||
yearCount
|
||||
);
|
||||
|
||||
logger.info("현실적인 Mock 건강검진 데이터 생성 및 처리 완료 - 사용자: {} ({}세), Member: {}, 연도 수: {}",
|
||||
user.getName(), Period.between(user.getBirthDate(), LocalDate.now()).getYears(),
|
||||
memberSerialNumber, yearCount);
|
||||
return ResponseEntity.ok(mockResponse);
|
||||
|
||||
} catch (BusinessException e) {
|
||||
logger.error("비즈니스 로직 오류: {}", e.getMessage());
|
||||
return ResponseHelper.badRequest(e.getMessage(), "BUSINESS_ERROR");
|
||||
} catch (Exception e) {
|
||||
logger.error("현실적인 Mock 건강검진 데이터 생성 중 오류 발생", e);
|
||||
return ResponseHelper.internalServerError(
|
||||
"현실적인 Mock 건강검진 데이터 생성 중 오류가 발생했습니다.",
|
||||
"MOCK_ERROR"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 정보 생성
|
||||
*/
|
||||
private HealthProfileHistoryResponse.UserInfo createUserInfo(User user, LocalDate birthDate,
|
||||
List<HealthCheckupRaw> rawData) {
|
||||
// 현재 나이 계산
|
||||
int age = Period.between(birthDate, LocalDate.now()).getYears();
|
||||
|
||||
// 성별 변환 (Raw 데이터에서 추출)
|
||||
String gender = "정보 없음";
|
||||
if (!rawData.isEmpty()) {
|
||||
HealthCheckupRaw latestRaw = rawData.get(0);
|
||||
gender = convertGenderCodeToString(latestRaw.getGenderCode());
|
||||
}
|
||||
|
||||
return new HealthProfileHistoryResponse.UserInfo(
|
||||
user.getName(),
|
||||
age,
|
||||
gender,
|
||||
user.getOccupation() != null ? user.getOccupation() : "정보 없음"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 가공된 데이터에 해당하는 Raw 데이터 찾기
|
||||
*/
|
||||
private HealthCheckupRaw findCorrespondingRawData(HealthCheckup processed, List<HealthCheckupRaw> rawDataList) {
|
||||
// 같은 raw_id의 원본 Raw 데이터 찾기
|
||||
Optional<HealthCheckupRaw> correspondingRaw = rawDataList.stream()
|
||||
.filter(raw -> raw.getRawId().equals(processed.getRawId()))
|
||||
.findFirst();
|
||||
|
||||
if (correspondingRaw.isPresent()) {
|
||||
logger.debug("해당하는 Raw 데이터 발견 - Raw ID: {}", processed.getRawId());
|
||||
return correspondingRaw.get();
|
||||
} else {
|
||||
logger.debug("해당하는 Raw 데이터 없음, 최신 Raw 데이터 사용 - Raw ID: {}", processed.getRawId());
|
||||
return rawDataList.get(0); // 최신 Raw 데이터 사용
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 성별 코드 변환
|
||||
*/
|
||||
private String convertGenderCodeToString(Integer genderCode) {
|
||||
if (genderCode == null) return "정보 없음";
|
||||
|
||||
switch (genderCode) {
|
||||
case 1: return "남성";
|
||||
case 2: return "여성";
|
||||
default: return "정보 없음";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이름을 기반으로 성별 코드 추정 (간단한 로직)
|
||||
*/
|
||||
private Integer estimateGenderCode(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return 1; // 기본값: 남성
|
||||
}
|
||||
|
||||
// 간단한 한국 이름 성별 추정
|
||||
String[] femaleEndings = {"영", "희", "순", "미", "정", "은", "아", "자", "경", "지", "현", "수", "혜", "윤", "민"};
|
||||
String lastName = name.substring(name.length() - 1);
|
||||
|
||||
for (String ending : femaleEndings) {
|
||||
if (lastName.equals(ending)) {
|
||||
return 2; // 여성
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // 기본값: 남성
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.healthsync.health.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;
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package com.healthsync.health.domain.HealthCheck;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class HealthCheckup {
|
||||
private Long checkupId;
|
||||
private Long memberSerialNumber;
|
||||
private Long rawId;
|
||||
private Integer referenceYear;
|
||||
private Integer age;
|
||||
private Integer height;
|
||||
private Integer weight;
|
||||
private BigDecimal bmi;
|
||||
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 processedAt;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public HealthCheckup() {}
|
||||
|
||||
// HealthCheckupRaw에서 HealthCheckup으로 변환하는 정적 팩토리 메서드
|
||||
public static HealthCheckup fromRaw(HealthCheckupRaw rawData, Long memberSerialNumber) {
|
||||
HealthCheckup checkup = new HealthCheckup();
|
||||
|
||||
checkup.memberSerialNumber = memberSerialNumber;
|
||||
checkup.rawId = rawData.getRawId();
|
||||
checkup.referenceYear = rawData.getReferenceYear();
|
||||
checkup.age = rawData.getAge();
|
||||
checkup.height = rawData.getHeight();
|
||||
checkup.weight = rawData.getWeight();
|
||||
|
||||
// BMI 계산 (원천 데이터에서 계산된 값 사용)
|
||||
checkup.bmi = rawData.calculateBMI();
|
||||
|
||||
checkup.waistCircumference = rawData.getWaistCircumference();
|
||||
checkup.visualAcuityLeft = rawData.getVisualAcuityLeft();
|
||||
checkup.visualAcuityRight = rawData.getVisualAcuityRight();
|
||||
checkup.hearingLeft = rawData.getHearingLeft();
|
||||
checkup.hearingRight = rawData.getHearingRight();
|
||||
checkup.systolicBp = rawData.getSystolicBp();
|
||||
checkup.diastolicBp = rawData.getDiastolicBp();
|
||||
checkup.fastingGlucose = rawData.getFastingGlucose();
|
||||
checkup.totalCholesterol = rawData.getTotalCholesterol();
|
||||
checkup.triglyceride = rawData.getTriglyceride();
|
||||
checkup.hdlCholesterol = rawData.getHdlCholesterol();
|
||||
checkup.ldlCholesterol = rawData.getLdlCholesterol();
|
||||
checkup.hemoglobin = rawData.getHemoglobin();
|
||||
checkup.urineProtein = rawData.getUrineProtein();
|
||||
checkup.serumCreatinine = rawData.getSerumCreatinine();
|
||||
checkup.ast = rawData.getAst();
|
||||
checkup.alt = rawData.getAlt();
|
||||
checkup.gammaGtp = rawData.getGammaGtp();
|
||||
checkup.smokingStatus = rawData.getSmokingStatus();
|
||||
checkup.drinkingStatus = rawData.getDrinkingStatus();
|
||||
|
||||
checkup.processedAt = LocalDateTime.now();
|
||||
checkup.createdAt = rawData.getCreatedAt();
|
||||
|
||||
return checkup;
|
||||
}
|
||||
|
||||
// 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(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 혈압 문자열 반환 메서드
|
||||
public String getBloodPressureString() {
|
||||
if (systolicBp != null && diastolicBp != null) {
|
||||
return systolicBp + "/" + diastolicBp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// BMI 상태 반환 메서드
|
||||
public String getBmiStatus() {
|
||||
if (bmi == null) return "정보 없음";
|
||||
|
||||
double bmiValue = bmi.doubleValue();
|
||||
if (bmiValue < 18.5) return "저체중";
|
||||
else if (bmiValue < 25.0) return "정상";
|
||||
else if (bmiValue < 30.0) return "과체중";
|
||||
else return "비만";
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getCheckupId() { return checkupId; }
|
||||
public void setCheckupId(Long checkupId) { this.checkupId = checkupId; }
|
||||
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) { this.memberSerialNumber = memberSerialNumber; }
|
||||
|
||||
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 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 BigDecimal getBmi() { return bmi; }
|
||||
public void setBmi(BigDecimal bmi) { this.bmi = bmi; }
|
||||
|
||||
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 getProcessedAt() { return processedAt; }
|
||||
public void setProcessedAt(LocalDateTime processedAt) { this.processedAt = processedAt; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package com.healthsync.health.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.health.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.health.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.health.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.health.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.health.domain.Oauth;
|
||||
|
||||
public enum UserRole {
|
||||
USER, ADMIN
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package com.healthsync.health.dto.HealthCheck;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class HealthCheckupResponse {
|
||||
private Long checkupId;
|
||||
private Integer referenceYear;
|
||||
private Integer age;
|
||||
private Integer height;
|
||||
private Integer weight;
|
||||
private BigDecimal bmi;
|
||||
private String bmiStatus;
|
||||
private Integer waistCircumference;
|
||||
private BigDecimal visualAcuityLeft;
|
||||
private BigDecimal visualAcuityRight;
|
||||
private Integer hearingLeft;
|
||||
private Integer hearingRight;
|
||||
private Integer systolicBp;
|
||||
private Integer diastolicBp;
|
||||
private String bloodPressure;
|
||||
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 processedAt;
|
||||
|
||||
public HealthCheckupResponse() {}
|
||||
|
||||
public HealthCheckupResponse(HealthCheckup healthCheckup) {
|
||||
this.checkupId = healthCheckup.getCheckupId();
|
||||
this.referenceYear = healthCheckup.getReferenceYear();
|
||||
this.age = healthCheckup.getAge();
|
||||
this.height = healthCheckup.getHeight();
|
||||
this.weight = healthCheckup.getWeight();
|
||||
this.bmi = healthCheckup.getBmi();
|
||||
this.bmiStatus = healthCheckup.getBmiStatus();
|
||||
this.waistCircumference = healthCheckup.getWaistCircumference();
|
||||
this.visualAcuityLeft = healthCheckup.getVisualAcuityLeft();
|
||||
this.visualAcuityRight = healthCheckup.getVisualAcuityRight();
|
||||
this.hearingLeft = healthCheckup.getHearingLeft();
|
||||
this.hearingRight = healthCheckup.getHearingRight();
|
||||
this.systolicBp = healthCheckup.getSystolicBp();
|
||||
this.diastolicBp = healthCheckup.getDiastolicBp();
|
||||
this.bloodPressure = healthCheckup.getBloodPressureString();
|
||||
this.fastingGlucose = healthCheckup.getFastingGlucose();
|
||||
this.totalCholesterol = healthCheckup.getTotalCholesterol();
|
||||
this.triglyceride = healthCheckup.getTriglyceride();
|
||||
this.hdlCholesterol = healthCheckup.getHdlCholesterol();
|
||||
this.ldlCholesterol = healthCheckup.getLdlCholesterol();
|
||||
this.hemoglobin = healthCheckup.getHemoglobin();
|
||||
this.urineProtein = healthCheckup.getUrineProtein();
|
||||
this.serumCreatinine = healthCheckup.getSerumCreatinine();
|
||||
this.ast = healthCheckup.getAst();
|
||||
this.alt = healthCheckup.getAlt();
|
||||
this.gammaGtp = healthCheckup.getGammaGtp();
|
||||
this.smokingStatus = healthCheckup.getSmokingStatus();
|
||||
this.drinkingStatus = healthCheckup.getDrinkingStatus();
|
||||
this.processedAt = healthCheckup.getProcessedAt();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getCheckupId() { return checkupId; }
|
||||
public void setCheckupId(Long checkupId) { this.checkupId = checkupId; }
|
||||
|
||||
public Integer getReferenceYear() { return referenceYear; }
|
||||
public void setReferenceYear(Integer referenceYear) { this.referenceYear = referenceYear; }
|
||||
|
||||
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 BigDecimal getBmi() { return bmi; }
|
||||
public void setBmi(BigDecimal bmi) { this.bmi = bmi; }
|
||||
|
||||
public String getBmiStatus() { return bmiStatus; }
|
||||
public void setBmiStatus(String bmiStatus) { this.bmiStatus = bmiStatus; }
|
||||
|
||||
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 String getBloodPressure() { return bloodPressure; }
|
||||
public void setBloodPressure(String bloodPressure) { this.bloodPressure = bloodPressure; }
|
||||
|
||||
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 getProcessedAt() { return processedAt; }
|
||||
public void setProcessedAt(LocalDateTime processedAt) { this.processedAt = processedAt; }
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.healthsync.health.dto.HealthCheck;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
|
||||
public class HealthCheckupSyncResult {
|
||||
private int totalCount; // 총 처리된 데이터 수
|
||||
private int newCount; // 새로 추가된 데이터 수
|
||||
private int updatedCount; // 업데이트된 데이터 수
|
||||
private int skippedCount; // 건너뛴 데이터 수 (이미 존재)
|
||||
private HealthCheckup latestCheckup; // 최신 건강검진 데이터
|
||||
|
||||
public HealthCheckupSyncResult() {}
|
||||
|
||||
public HealthCheckupSyncResult(int totalCount, int newCount, int updatedCount,
|
||||
int skippedCount, HealthCheckup latestCheckup) {
|
||||
this.totalCount = totalCount;
|
||||
this.newCount = newCount;
|
||||
this.updatedCount = updatedCount;
|
||||
this.skippedCount = skippedCount;
|
||||
this.latestCheckup = latestCheckup;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public int getTotalCount() { return totalCount; }
|
||||
public void setTotalCount(int totalCount) { this.totalCount = totalCount; }
|
||||
|
||||
public int getNewCount() { return newCount; }
|
||||
public void setNewCount(int newCount) { this.newCount = newCount; }
|
||||
|
||||
public int getUpdatedCount() { return updatedCount; }
|
||||
public void setUpdatedCount(int updatedCount) { this.updatedCount = updatedCount; }
|
||||
|
||||
public int getSkippedCount() { return skippedCount; }
|
||||
public void setSkippedCount(int skippedCount) { this.skippedCount = skippedCount; }
|
||||
|
||||
public HealthCheckup getLatestCheckup() { return latestCheckup; }
|
||||
public void setLatestCheckup(HealthCheckup latestCheckup) { this.latestCheckup = latestCheckup; }
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package com.healthsync.health.dto.HealthCheck;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class HealthProfileDto {
|
||||
@JsonProperty("rawId")
|
||||
private Long rawId;
|
||||
|
||||
@JsonProperty("referenceYear")
|
||||
private Integer referenceYear;
|
||||
|
||||
@JsonProperty("birthDate")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("regionCode")
|
||||
private Integer regionCode;
|
||||
|
||||
@JsonProperty("genderCode")
|
||||
private Integer genderCode;
|
||||
|
||||
@JsonProperty("age")
|
||||
private Integer age;
|
||||
|
||||
@JsonProperty("height")
|
||||
private Integer height;
|
||||
|
||||
@JsonProperty("weight")
|
||||
private Integer weight;
|
||||
|
||||
@JsonProperty("waistCircumference")
|
||||
private Integer waistCircumference;
|
||||
|
||||
@JsonProperty("visualAcuityLeft")
|
||||
private BigDecimal visualAcuityLeft;
|
||||
|
||||
@JsonProperty("visualAcuityRight")
|
||||
private BigDecimal visualAcuityRight;
|
||||
|
||||
@JsonProperty("hearingLeft")
|
||||
private Integer hearingLeft;
|
||||
|
||||
@JsonProperty("hearingRight")
|
||||
private Integer hearingRight;
|
||||
|
||||
@JsonProperty("systolicBp")
|
||||
private Integer systolicBp;
|
||||
|
||||
@JsonProperty("diastolicBp")
|
||||
private Integer diastolicBp;
|
||||
|
||||
@JsonProperty("fastingGlucose")
|
||||
private Integer fastingGlucose;
|
||||
|
||||
@JsonProperty("totalCholesterol")
|
||||
private Integer totalCholesterol;
|
||||
|
||||
@JsonProperty("triglyceride")
|
||||
private Integer triglyceride;
|
||||
|
||||
@JsonProperty("hdlCholesterol")
|
||||
private Integer hdlCholesterol;
|
||||
|
||||
@JsonProperty("ldlCholesterol")
|
||||
private Integer ldlCholesterol;
|
||||
|
||||
@JsonProperty("hemoglobin")
|
||||
private BigDecimal hemoglobin;
|
||||
|
||||
@JsonProperty("urineProtein")
|
||||
private Integer urineProtein;
|
||||
|
||||
@JsonProperty("serumCreatinine")
|
||||
private BigDecimal serumCreatinine;
|
||||
|
||||
@JsonProperty("ast")
|
||||
private Integer ast;
|
||||
|
||||
@JsonProperty("alt")
|
||||
private Integer alt;
|
||||
|
||||
@JsonProperty("gammaGtp")
|
||||
private Integer gammaGtp;
|
||||
|
||||
@JsonProperty("smokingStatus")
|
||||
private Integer smokingStatus;
|
||||
|
||||
@JsonProperty("drinkingStatus")
|
||||
private Integer drinkingStatus;
|
||||
|
||||
@JsonProperty("createdAt")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
public HealthProfileDto() {}
|
||||
|
||||
// HealthCheckupRaw로부터 변환하는 정적 메서드
|
||||
public static HealthProfileDto fromDomain(HealthCheckupRaw domain) {
|
||||
if (domain == null) return null;
|
||||
|
||||
HealthProfileDto dto = new HealthProfileDto();
|
||||
dto.rawId = domain.getRawId();
|
||||
dto.referenceYear = domain.getReferenceYear();
|
||||
dto.birthDate = domain.getBirthDate();
|
||||
dto.name = domain.getName();
|
||||
dto.regionCode = domain.getRegionCode();
|
||||
dto.genderCode = domain.getGenderCode();
|
||||
dto.age = domain.getAge();
|
||||
dto.height = domain.getHeight();
|
||||
dto.weight = domain.getWeight();
|
||||
dto.waistCircumference = domain.getWaistCircumference();
|
||||
dto.visualAcuityLeft = domain.getVisualAcuityLeft();
|
||||
dto.visualAcuityRight = domain.getVisualAcuityRight();
|
||||
dto.hearingLeft = domain.getHearingLeft();
|
||||
dto.hearingRight = domain.getHearingRight();
|
||||
dto.systolicBp = domain.getSystolicBp();
|
||||
dto.diastolicBp = domain.getDiastolicBp();
|
||||
dto.fastingGlucose = domain.getFastingGlucose();
|
||||
dto.totalCholesterol = domain.getTotalCholesterol();
|
||||
dto.triglyceride = domain.getTriglyceride();
|
||||
dto.hdlCholesterol = domain.getHdlCholesterol();
|
||||
dto.ldlCholesterol = domain.getLdlCholesterol();
|
||||
dto.hemoglobin = domain.getHemoglobin();
|
||||
dto.urineProtein = domain.getUrineProtein();
|
||||
dto.serumCreatinine = domain.getSerumCreatinine();
|
||||
dto.ast = domain.getAst();
|
||||
dto.alt = domain.getAlt();
|
||||
dto.gammaGtp = domain.getGammaGtp();
|
||||
dto.smokingStatus = domain.getSmokingStatus();
|
||||
dto.drinkingStatus = domain.getDrinkingStatus();
|
||||
dto.createdAt = domain.getCreatedAt();
|
||||
return dto;
|
||||
}
|
||||
|
||||
// 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; }
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package com.healthsync.health.dto.HealthCheck;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class HealthProfileHistoryResponse {
|
||||
|
||||
@JsonProperty("userInfo")
|
||||
private UserInfo userInfo;
|
||||
|
||||
@JsonProperty("recentHealthProfile")
|
||||
private HealthProfileDto recentHealthProfile;
|
||||
|
||||
@JsonProperty("healthProfiles")
|
||||
private List<HealthProfileDto> healthProfiles;
|
||||
|
||||
public HealthProfileHistoryResponse() {
|
||||
this.healthProfiles = new ArrayList<>();
|
||||
}
|
||||
|
||||
public HealthProfileHistoryResponse(UserInfo userInfo,
|
||||
HealthCheckupRaw recentHealthProfile,
|
||||
List<HealthCheckupRaw> healthProfiles) {
|
||||
this.userInfo = userInfo;
|
||||
this.recentHealthProfile = HealthProfileDto.fromDomain(recentHealthProfile);
|
||||
this.healthProfiles = healthProfiles != null ?
|
||||
healthProfiles.stream()
|
||||
.map(HealthProfileDto::fromDomain)
|
||||
.collect(Collectors.toList()) : new ArrayList<>();
|
||||
}
|
||||
|
||||
// 데이터가 없을 때 생성자 (userInfo만 포함)
|
||||
public HealthProfileHistoryResponse(UserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
this.recentHealthProfile = null;
|
||||
this.healthProfiles = new ArrayList<>();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public UserInfo getUserInfo() { return userInfo; }
|
||||
public void setUserInfo(UserInfo userInfo) { this.userInfo = userInfo; }
|
||||
|
||||
public HealthProfileDto getRecentHealthProfile() { return recentHealthProfile; }
|
||||
public void setRecentHealthProfile(HealthProfileDto recentHealthProfile) { this.recentHealthProfile = recentHealthProfile; }
|
||||
|
||||
public List<HealthProfileDto> getHealthProfiles() { return healthProfiles; }
|
||||
public void setHealthProfiles(List<HealthProfileDto> healthProfiles) {
|
||||
this.healthProfiles = healthProfiles != null ? healthProfiles : new ArrayList<>();
|
||||
}
|
||||
|
||||
// 내부 UserInfo 클래스
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class UserInfo {
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("age")
|
||||
private int age;
|
||||
|
||||
@JsonProperty("gender")
|
||||
private String gender;
|
||||
|
||||
@JsonProperty("occupation")
|
||||
private String occupation;
|
||||
|
||||
public UserInfo() {}
|
||||
|
||||
public UserInfo(String name, int age, String gender, String occupation) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.gender = gender;
|
||||
this.occupation = occupation;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public int getAge() { return age; }
|
||||
public void setAge(int age) { this.age = age; }
|
||||
|
||||
public String getGender() { return gender; }
|
||||
public void setGender(String gender) { this.gender = gender; }
|
||||
|
||||
public String getOccupation() { return occupation; }
|
||||
public void setOccupation(String occupation) { this.occupation = occupation; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.healthsync.health.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.health.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.health.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.health.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; }
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.healthsync.health.dto.UserProfile;
|
||||
|
||||
import com.healthsync.health.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; }
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.healthsync.health.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;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.health.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);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.health.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);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.health.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);
|
||||
}
|
||||
}
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
package com.healthsync.health.repository.entity;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
import jakarta.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "health_checkup", schema = "health_service")
|
||||
public class HealthCheckupEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "checkup_id")
|
||||
private Long checkupId; // member_serial_number와 동일한 값으로 수동 설정
|
||||
|
||||
@Column(name = "member_serial_number", nullable = false)
|
||||
private Long memberSerialNumber;
|
||||
|
||||
@Column(name = "raw_id", nullable = false)
|
||||
private Long rawId;
|
||||
|
||||
@Column(name = "reference_year", nullable = false)
|
||||
private Integer referenceYear;
|
||||
|
||||
@Column(name = "age")
|
||||
private Integer age;
|
||||
|
||||
@Column(name = "height")
|
||||
private Integer height;
|
||||
|
||||
@Column(name = "weight")
|
||||
private Integer weight;
|
||||
|
||||
@Column(name = "bmi", precision = 5, scale = 2)
|
||||
private BigDecimal bmi;
|
||||
|
||||
@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 = "processed_at")
|
||||
private LocalDateTime processedAt;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
// 기본 생성자
|
||||
protected HealthCheckupEntity() {}
|
||||
|
||||
// 생성자 - checkup_id를 member_serial_number와 동일하게 설정
|
||||
public HealthCheckupEntity(Long memberSerialNumber, Long rawId, Integer referenceYear) {
|
||||
this.checkupId = memberSerialNumber; // 핵심: checkup_id = member_serial_number
|
||||
this.memberSerialNumber = memberSerialNumber;
|
||||
this.rawId = rawId;
|
||||
this.referenceYear = referenceYear;
|
||||
this.processedAt = LocalDateTime.now();
|
||||
this.createdAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// PrePersist로 생성 시간 자동 설정
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (this.createdAt == null) {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
}
|
||||
if (this.processedAt == null) {
|
||||
this.processedAt = LocalDateTime.now();
|
||||
}
|
||||
// checkup_id가 설정되지 않았다면 member_serial_number로 설정
|
||||
if (this.checkupId == null && this.memberSerialNumber != null) {
|
||||
this.checkupId = this.memberSerialNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// PreUpdate로 업데이트 시간 자동 설정
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
this.processedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// Entity ↔ Domain 변환 메서드
|
||||
public static HealthCheckupEntity fromDomain(HealthCheckup healthCheckup) {
|
||||
if (healthCheckup == null) return null;
|
||||
|
||||
HealthCheckupEntity entity = new HealthCheckupEntity();
|
||||
|
||||
// checkup_id는 항상 member_serial_number와 동일
|
||||
entity.checkupId = healthCheckup.getMemberSerialNumber();
|
||||
entity.memberSerialNumber = healthCheckup.getMemberSerialNumber();
|
||||
entity.rawId = healthCheckup.getRawId();
|
||||
entity.referenceYear = healthCheckup.getReferenceYear();
|
||||
entity.age = healthCheckup.getAge();
|
||||
entity.height = healthCheckup.getHeight();
|
||||
entity.weight = healthCheckup.getWeight();
|
||||
entity.bmi = healthCheckup.getBmi();
|
||||
entity.waistCircumference = healthCheckup.getWaistCircumference();
|
||||
entity.visualAcuityLeft = healthCheckup.getVisualAcuityLeft();
|
||||
entity.visualAcuityRight = healthCheckup.getVisualAcuityRight();
|
||||
entity.hearingLeft = healthCheckup.getHearingLeft();
|
||||
entity.hearingRight = healthCheckup.getHearingRight();
|
||||
entity.systolicBp = healthCheckup.getSystolicBp();
|
||||
entity.diastolicBp = healthCheckup.getDiastolicBp();
|
||||
entity.fastingGlucose = healthCheckup.getFastingGlucose();
|
||||
entity.totalCholesterol = healthCheckup.getTotalCholesterol();
|
||||
entity.triglyceride = healthCheckup.getTriglyceride();
|
||||
entity.hdlCholesterol = healthCheckup.getHdlCholesterol();
|
||||
entity.ldlCholesterol = healthCheckup.getLdlCholesterol();
|
||||
entity.hemoglobin = healthCheckup.getHemoglobin();
|
||||
entity.urineProtein = healthCheckup.getUrineProtein();
|
||||
entity.serumCreatinine = healthCheckup.getSerumCreatinine();
|
||||
entity.ast = healthCheckup.getAst();
|
||||
entity.alt = healthCheckup.getAlt();
|
||||
entity.gammaGtp = healthCheckup.getGammaGtp();
|
||||
entity.smokingStatus = healthCheckup.getSmokingStatus();
|
||||
entity.drinkingStatus = healthCheckup.getDrinkingStatus();
|
||||
entity.processedAt = healthCheckup.getProcessedAt();
|
||||
entity.createdAt = healthCheckup.getCreatedAt();
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public HealthCheckup toDomain() {
|
||||
HealthCheckup domain = new HealthCheckup();
|
||||
domain.setCheckupId(this.checkupId);
|
||||
domain.setMemberSerialNumber(this.memberSerialNumber);
|
||||
domain.setRawId(this.rawId);
|
||||
domain.setReferenceYear(this.referenceYear);
|
||||
domain.setAge(this.age);
|
||||
domain.setHeight(this.height);
|
||||
domain.setWeight(this.weight);
|
||||
domain.setBmi(this.bmi);
|
||||
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.setProcessedAt(this.processedAt);
|
||||
domain.setCreatedAt(this.createdAt);
|
||||
return domain;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getCheckupId() { return checkupId; }
|
||||
public void setCheckupId(Long checkupId) { this.checkupId = checkupId; }
|
||||
|
||||
public Long getMemberSerialNumber() { return memberSerialNumber; }
|
||||
public void setMemberSerialNumber(Long memberSerialNumber) {
|
||||
this.memberSerialNumber = memberSerialNumber;
|
||||
// member_serial_number가 변경되면 checkup_id도 동일하게 설정
|
||||
this.checkupId = memberSerialNumber;
|
||||
}
|
||||
|
||||
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 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 BigDecimal getBmi() { return bmi; }
|
||||
public void setBmi(BigDecimal bmi) { this.bmi = bmi; }
|
||||
|
||||
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 getProcessedAt() { return processedAt; }
|
||||
public void setProcessedAt(LocalDateTime processedAt) { this.processedAt = processedAt; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
package com.healthsync.health.repository.entity;
|
||||
|
||||
import com.healthsync.health.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.health.repository.entity;
|
||||
|
||||
import com.healthsync.health.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.health.repository.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
/**
|
||||
* 직업 유형 정보를 담는 엔티티 (health-service용)
|
||||
* user_service.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.health.repository.entity;
|
||||
|
||||
import com.healthsync.health.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,113 @@
|
||||
package com.healthsync.health.repository.entity;
|
||||
|
||||
import com.healthsync.health.domain.Oauth.User;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 사용자 정보를 담는 엔티티 (health-service용)
|
||||
* user_service.user 테이블과 매핑
|
||||
*/
|
||||
@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; }
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.healthsync.health.repository.jpa;
|
||||
|
||||
import com.healthsync.health.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);
|
||||
|
||||
// 이름과 생년월일로 최근 5개 건강검진 데이터 조회 - **주요 메서드**
|
||||
@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 5", nativeQuery = true)
|
||||
List<HealthCheckupRawEntity> findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(
|
||||
@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);
|
||||
|
||||
// 특정 Raw ID로 조회
|
||||
Optional<HealthCheckupRawEntity> findByRawId(Long rawId);
|
||||
|
||||
// 특정 Raw ID 목록으로 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h WHERE h.rawId IN :rawIds")
|
||||
List<HealthCheckupRawEntity> findByRawIdIn(@Param("rawIds") List<Long> rawIds);
|
||||
|
||||
// 특정 연도 범위의 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate " +
|
||||
"AND h.referenceYear BETWEEN :startYear AND :endYear " +
|
||||
"ORDER BY h.referenceYear DESC, h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findByNameAndBirthDateAndReferenceYearBetween(
|
||||
@Param("name") String name,
|
||||
@Param("birthDate") LocalDate birthDate,
|
||||
@Param("startYear") Integer startYear,
|
||||
@Param("endYear") Integer endYear);
|
||||
|
||||
// 이름으로만 조회 (생년월일이 다른 동명이인 포함)
|
||||
List<HealthCheckupRawEntity> findByNameOrderByReferenceYearDescCreatedAtDesc(String name);
|
||||
|
||||
// 특정 성별의 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate AND h.genderCode = :genderCode " +
|
||||
"ORDER BY h.referenceYear DESC, h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findByNameAndBirthDateAndGenderCode(
|
||||
@Param("name") String name,
|
||||
@Param("birthDate") LocalDate birthDate,
|
||||
@Param("genderCode") Integer genderCode);
|
||||
|
||||
// 특정 지역의 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.regionCode = :regionCode " +
|
||||
"ORDER BY h.referenceYear DESC, h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findByRegionCodeOrderByReferenceYearDesc(@Param("regionCode") Integer regionCode);
|
||||
|
||||
// 특정 연령대의 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.age BETWEEN :minAge AND :maxAge " +
|
||||
"ORDER BY h.referenceYear DESC, h.createdAt DESC")
|
||||
List<HealthCheckupRawEntity> findByAgeBetweenOrderByReferenceYearDesc(
|
||||
@Param("minAge") Integer minAge,
|
||||
@Param("maxAge") Integer maxAge);
|
||||
|
||||
// 특정 사용자의 연도별 데이터 개수
|
||||
@Query("SELECT h.referenceYear, COUNT(h) FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate " +
|
||||
"GROUP BY h.referenceYear " +
|
||||
"ORDER BY h.referenceYear DESC")
|
||||
List<Object[]> countByYearForUser(@Param("name") String name, @Param("birthDate") LocalDate birthDate);
|
||||
|
||||
// 특정 사용자의 최신 검진 연도
|
||||
@Query("SELECT MAX(h.referenceYear) FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate")
|
||||
Optional<Integer> findLatestYearByUser(@Param("name") String name, @Param("birthDate") LocalDate birthDate);
|
||||
|
||||
// 특정 사용자의 가장 오래된 검진 연도
|
||||
@Query("SELECT MIN(h.referenceYear) FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name = :name AND h.birthDate = :birthDate")
|
||||
Optional<Integer> findOldestYearByUser(@Param("name") String name, @Param("birthDate") LocalDate birthDate);
|
||||
|
||||
@Query("SELECT COUNT(h) FROM HealthCheckupRawEntity h WHERE h.referenceYear = :year")
|
||||
long countByReferenceYear(@Param("year") Integer year);
|
||||
|
||||
// 데이터 품질 체크 - 필수 필드가 null인 데이터
|
||||
@Query("SELECT h FROM HealthCheckupRawEntity h " +
|
||||
"WHERE h.name IS NULL OR h.birthDate IS NULL OR h.referenceYear IS NULL")
|
||||
List<HealthCheckupRawEntity> findIncompleteData();
|
||||
|
||||
// 중복 데이터 체크 - 같은 사용자, 같은 연도에 여러 레코드
|
||||
@Query("SELECT h.name, h.birthDate, h.referenceYear, COUNT(h) " +
|
||||
"FROM HealthCheckupRawEntity h " +
|
||||
"GROUP BY h.name, h.birthDate, h.referenceYear " +
|
||||
"HAVING COUNT(h) > 1")
|
||||
List<Object[]> findDuplicateRecords();
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package com.healthsync.health.repository.jpa;
|
||||
|
||||
import com.healthsync.health.repository.entity.HealthCheckupEntity;
|
||||
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 HealthCheckupRepository extends JpaRepository<HealthCheckupEntity, Long> {
|
||||
|
||||
// 특정 회원의 건강검진 데이터 조회 (1개만 존재)
|
||||
// checkup_id = member_serial_number이므로 findById로도 조회 가능
|
||||
Optional<HealthCheckupEntity> findByMemberSerialNumber(Long memberSerialNumber);
|
||||
|
||||
// checkup_id로 조회 (member_serial_number와 동일)
|
||||
Optional<HealthCheckupEntity> findByCheckupId(Long checkupId);
|
||||
|
||||
// 회원 존재 여부 확인
|
||||
boolean existsByMemberSerialNumber(Long memberSerialNumber);
|
||||
|
||||
// checkup_id 존재 여부 확인 (member_serial_number와 동일)
|
||||
boolean existsByCheckupId(Long checkupId);
|
||||
|
||||
// 특정 Raw ID로 이미 처리된 데이터 확인 - **중복 방지 핵심**
|
||||
boolean existsByRawId(Long rawId);
|
||||
|
||||
// 특정 Raw ID로 가공된 데이터 조회
|
||||
Optional<HealthCheckupEntity> findByRawId(Long rawId);
|
||||
|
||||
// 여러 회원들의 건강검진 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.memberSerialNumber IN :memberSerialNumbers")
|
||||
List<HealthCheckupEntity> findByMemberSerialNumberIn(@Param("memberSerialNumbers") List<Long> memberSerialNumbers);
|
||||
|
||||
// 특정 연도의 건강검진 데이터를 가진 회원들 조회
|
||||
List<HealthCheckupEntity> findByReferenceYear(Integer referenceYear);
|
||||
|
||||
// 특정 연도 이후의 건강검진 데이터를 가진 회원들 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.referenceYear >= :fromYear")
|
||||
List<HealthCheckupEntity> findByReferenceYearGreaterThanEqual(@Param("fromYear") Integer fromYear);
|
||||
|
||||
// 특정 연도 범위의 건강검진 데이터를 가진 회원들 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.referenceYear BETWEEN :startYear AND :endYear")
|
||||
List<HealthCheckupEntity> findByReferenceYearBetween(@Param("startYear") Integer startYear, @Param("endYear") Integer endYear);
|
||||
|
||||
// 최근 처리된 순으로 N개 조회 (관리자용)
|
||||
@Query("SELECT h FROM HealthCheckupEntity h ORDER BY h.processedAt DESC")
|
||||
List<HealthCheckupEntity> findAllOrderByProcessedAtDesc();
|
||||
|
||||
// 특정 기간에 처리된 데이터 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.processedAt BETWEEN :startDate AND :endDate ORDER BY h.processedAt DESC")
|
||||
List<HealthCheckupEntity> findByProcessedAtBetween(@Param("startDate") java.time.LocalDateTime startDate,
|
||||
@Param("endDate") java.time.LocalDateTime endDate);
|
||||
|
||||
// 전체 건강검진 데이터 개수
|
||||
long count();
|
||||
|
||||
// 특정 연도의 건강검진 데이터 개수
|
||||
long countByReferenceYear(Integer referenceYear);
|
||||
|
||||
// Raw ID 목록으로 가공된 데이터들 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.rawId IN :rawIds")
|
||||
List<HealthCheckupEntity> findByRawIdIn(@Param("rawIds") List<Long> rawIds);
|
||||
|
||||
// 최신 건강검진 연도 조회 (전체)
|
||||
@Query("SELECT MAX(h.referenceYear) FROM HealthCheckupEntity h")
|
||||
Optional<Integer> findMaxReferenceYear();
|
||||
|
||||
// 가장 오래된 건강검진 연도 조회 (전체)
|
||||
@Query("SELECT MIN(h.referenceYear) FROM HealthCheckupEntity h")
|
||||
Optional<Integer> findMinReferenceYear();
|
||||
|
||||
// 특정 Raw ID가 이미 다른 회원에게 할당되었는지 확인 (데이터 무결성)
|
||||
@Query("SELECT h.memberSerialNumber FROM HealthCheckupEntity h WHERE h.rawId = :rawId")
|
||||
Optional<Long> findMemberSerialNumberByRawId(@Param("rawId") Long rawId);
|
||||
|
||||
// BMI 범위별 회원 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.bmi BETWEEN :minBmi AND :maxBmi")
|
||||
List<HealthCheckupEntity> findByBmiBetween(@Param("minBmi") java.math.BigDecimal minBmi,
|
||||
@Param("maxBmi") java.math.BigDecimal maxBmi);
|
||||
|
||||
// 혈압 범위별 회원 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.systolicBp >= :minSystolic OR h.diastolicBp >= :minDiastolic")
|
||||
List<HealthCheckupEntity> findByHighBloodPressure(@Param("minSystolic") Integer minSystolic,
|
||||
@Param("minDiastolic") Integer minDiastolic);
|
||||
|
||||
// 혈당 범위별 회원 조회
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.fastingGlucose >= :minGlucose")
|
||||
List<HealthCheckupEntity> findByHighGlucose(@Param("minGlucose") Integer minGlucose);
|
||||
|
||||
// 처리되지 않은 Raw 데이터 개수 확인 (전체 통계)
|
||||
@Query(value = "SELECT COUNT(*) FROM health_service.health_checkup_raw r " +
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM health_service.health_checkup h WHERE h.raw_id = r.raw_id)",
|
||||
nativeQuery = true)
|
||||
long countUnprocessedRawData();
|
||||
|
||||
// 특정 회원의 처리되지 않은 Raw 데이터 개수
|
||||
@Query(value = "SELECT COUNT(*) FROM health_service.health_checkup_raw r " +
|
||||
"WHERE r.name = :name AND r.birth_date = :birthDate " +
|
||||
"AND NOT EXISTS (SELECT 1 FROM health_service.health_checkup h WHERE h.raw_id = r.raw_id)",
|
||||
nativeQuery = true)
|
||||
long countUnprocessedRawDataByUser(@Param("name") String name, @Param("birthDate") java.time.LocalDate birthDate);
|
||||
|
||||
// 데이터 정합성 체크 - checkup_id와 member_serial_number가 다른 레코드
|
||||
@Query("SELECT h FROM HealthCheckupEntity h WHERE h.checkupId != h.memberSerialNumber")
|
||||
List<HealthCheckupEntity> findInconsistentData();
|
||||
|
||||
// 연령대별 통계
|
||||
@Query("SELECT " +
|
||||
"CASE " +
|
||||
" WHEN h.age < 30 THEN '20대' " +
|
||||
" WHEN h.age < 40 THEN '30대' " +
|
||||
" WHEN h.age < 50 THEN '40대' " +
|
||||
" WHEN h.age < 60 THEN '50대' " +
|
||||
" ELSE '60대 이상' " +
|
||||
"END as ageGroup, " +
|
||||
"COUNT(h) as count " +
|
||||
"FROM HealthCheckupEntity h " +
|
||||
"GROUP BY " +
|
||||
"CASE " +
|
||||
" WHEN h.age < 30 THEN '20대' " +
|
||||
" WHEN h.age < 40 THEN '30대' " +
|
||||
" WHEN h.age < 50 THEN '40대' " +
|
||||
" WHEN h.age < 60 THEN '50대' " +
|
||||
" ELSE '60대 이상' " +
|
||||
"END")
|
||||
List<Object[]> getAgeGroupStatistics();
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.healthsync.health.repository.jpa;
|
||||
|
||||
import com.healthsync.health.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.health.repository.jpa;
|
||||
|
||||
import com.healthsync.health.repository.entity.OccupationTypeEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 직업 유형 정보 조회를 위한 리포지토리 (health-service용)
|
||||
*/
|
||||
@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.health.repository.jpa;
|
||||
|
||||
import com.healthsync.health.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,90 @@
|
||||
package com.healthsync.health.repository.jpa;
|
||||
|
||||
import com.healthsync.health.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;
|
||||
|
||||
/**
|
||||
* 사용자 정보 조회를 위한 리포지토리 (health-service용)
|
||||
* user_service.user 테이블 접근
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.healthsync.health.service.HealthProfile;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthNormalRange;
|
||||
import com.healthsync.health.dto.HealthCheck.HealthCheckupSyncResult;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface HealthProfileService {
|
||||
|
||||
// ========== RAW 데이터 관련 메서드 ==========
|
||||
|
||||
/**
|
||||
* 이름과 생년월일로 최근 건강검진 원천 데이터 조회
|
||||
*/
|
||||
Optional<HealthCheckupRaw> getMostRecentHealthCheckup(String name, LocalDate birthDate);
|
||||
|
||||
/**
|
||||
* 이름과 생년월일로 5년간 건강검진 원천 데이터 조회
|
||||
*/
|
||||
List<HealthCheckupRaw> getHealthCheckupHistory5years(String name, LocalDate birthDate);
|
||||
|
||||
// ========== 가공된 데이터 관련 메서드 ==========
|
||||
|
||||
/**
|
||||
* raw 데이터를 가공하여 health_checkup 테이블에 저장 (기존 메서드)
|
||||
*/
|
||||
HealthCheckupSyncResult processAndSaveHealthCheckupData(List<HealthCheckupRaw> rawCheckupData, Long memberSerialNumber);
|
||||
|
||||
/**
|
||||
* Raw 데이터와 가공된 데이터를 동기화 (중복 방지) - 새로 추가
|
||||
*/
|
||||
HealthCheckupSyncResult syncHealthCheckupData(List<HealthCheckupRaw> rawData, Long memberSerialNumber);
|
||||
|
||||
/**
|
||||
* 특정 회원의 가공된 건강검진 이력 조회 (1개 레코드 방식에서는 최대 1개)
|
||||
*/
|
||||
List<HealthCheckup> getProcessedHealthCheckupHistory(Long memberSerialNumber);
|
||||
|
||||
/**
|
||||
* 특정 회원의 최신 가공된 건강검진 데이터 조회 (1개 레코드 방식에서는 유일한 데이터)
|
||||
*/
|
||||
Optional<HealthCheckup> getLatestProcessedHealthCheckup(Long memberSerialNumber);
|
||||
|
||||
/**
|
||||
* 특정 회원의 최근 N개 건강검진 데이터 조회 (1개 레코드 방식에서는 limit 무관하게 최대 1개)
|
||||
*/
|
||||
List<HealthCheckup> getRecentHealthCheckups(Long memberSerialNumber, int limit);
|
||||
|
||||
// ========== 정상 범위 관련 메서드 ==========
|
||||
|
||||
/**
|
||||
* 모든 건강 정상 범위 데이터 조회
|
||||
*/
|
||||
List<HealthNormalRange> getAllHealthNormalRanges();
|
||||
|
||||
/**
|
||||
* 성별 코드에 따른 건강 정상 범위 데이터 조회
|
||||
*/
|
||||
List<HealthNormalRange> getHealthNormalRangesByGender(Integer genderCode);
|
||||
|
||||
/**
|
||||
* 특정 건강 항목의 정상 범위 조회
|
||||
*/
|
||||
Optional<HealthNormalRange> getHealthNormalRangeByItemCode(String healthItemCode, Integer genderCode);
|
||||
|
||||
/**
|
||||
* 성별에 맞는 건강 정상 범위 데이터 조회 (null 허용하는 범용 범위 포함)
|
||||
*/
|
||||
List<HealthNormalRange> getRelevantHealthNormalRanges(Integer genderCode);
|
||||
}
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package com.healthsync.health.service.HealthProfile;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthNormalRange;
|
||||
import com.healthsync.health.dto.HealthCheck.HealthCheckupSyncResult;
|
||||
import com.healthsync.health.repository.entity.HealthCheckupRawEntity;
|
||||
import com.healthsync.health.repository.entity.HealthCheckupEntity;
|
||||
import com.healthsync.health.repository.entity.HealthNormalRangeEntity;
|
||||
import com.healthsync.health.repository.jpa.HealthCheckupRawRepository;
|
||||
import com.healthsync.health.repository.jpa.HealthCheckupRepository;
|
||||
import com.healthsync.health.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.time.LocalDateTime;
|
||||
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 HealthCheckupRepository healthCheckupRepository;
|
||||
private final HealthNormalRangeRepository healthNormalRangeRepository;
|
||||
|
||||
public HealthProfileServiceImpl(HealthCheckupRawRepository healthCheckupRawRepository,
|
||||
HealthCheckupRepository healthCheckupRepository,
|
||||
HealthNormalRangeRepository healthNormalRangeRepository) {
|
||||
this.healthCheckupRawRepository = healthCheckupRawRepository;
|
||||
this.healthCheckupRepository = healthCheckupRepository;
|
||||
this.healthNormalRangeRepository = healthNormalRangeRepository;
|
||||
}
|
||||
|
||||
// ========== RAW 데이터 관련 메서드 ==========
|
||||
|
||||
@Override
|
||||
public Optional<HealthCheckupRaw> getMostRecentHealthCheckup(String name, LocalDate birthDate) {
|
||||
logger.info("최근 건강검진 원천 데이터 조회 - 이름: {}, 생년월일: {}", name, birthDate);
|
||||
|
||||
List<HealthCheckupRawEntity> entities = healthCheckupRawRepository
|
||||
.findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(name, birthDate);
|
||||
|
||||
if (!entities.isEmpty()) {
|
||||
HealthCheckupRawEntity entity = entities.get(0); // 첫 번째(최신) 데이터
|
||||
logger.info("건강검진 원천 데이터 발견 - 검진년도: {}, Raw ID: {}, 성별 코드: {}",
|
||||
entity.getReferenceYear(), entity.getRawId(), entity.getGenderCode());
|
||||
return Optional.of(entity.toDomain());
|
||||
} else {
|
||||
logger.warn("해당 사용자의 건강검진 원천 데이터를 찾을 수 없음 - 이름: {}, 생년월일: {}", name, birthDate);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthCheckupRaw> getHealthCheckupHistory5years(String name, LocalDate birthDate) {
|
||||
logger.info("5년간 건강검진 원천 데이터 조회 - 이름: {}, 생년월일: {}", name, birthDate);
|
||||
|
||||
List<HealthCheckupRawEntity> entities = healthCheckupRawRepository
|
||||
.findTop5ByNameAndBirthDateOrderByReferenceYearDescCreatedAtDesc(name, birthDate);
|
||||
|
||||
logger.info("건강검진 원천 데이터 {}건 발견", entities.size());
|
||||
|
||||
return entities.stream()
|
||||
.map(HealthCheckupRawEntity::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ========== 가공된 데이터 관련 메서드 (1개 레코드 방식) ==========
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public HealthCheckupSyncResult syncHealthCheckupData(List<HealthCheckupRaw> rawData, Long memberSerialNumber) {
|
||||
logger.info("건강검진 데이터 동기화 시작 (1개 레코드 방식) - Member Serial Number: {}, Raw 데이터 수: {}",
|
||||
memberSerialNumber, rawData.size());
|
||||
|
||||
if (rawData.isEmpty()) {
|
||||
return new HealthCheckupSyncResult(0, 0, 0, 0, null);
|
||||
}
|
||||
|
||||
// 가장 최신 Raw 데이터 선택 (첫 번째가 가장 최신)
|
||||
HealthCheckupRaw latestRaw = rawData.get(0);
|
||||
|
||||
int totalCount = 1; // 1개만 처리
|
||||
int newCount = 0;
|
||||
int updatedCount = 0;
|
||||
int skippedCount = 0;
|
||||
HealthCheckup resultCheckup = null;
|
||||
|
||||
try {
|
||||
// 1. 현재 회원의 건강검진 데이터가 있는지 확인
|
||||
Optional<HealthCheckupEntity> existingEntity = healthCheckupRepository.findByMemberSerialNumber(memberSerialNumber);
|
||||
|
||||
if (existingEntity.isPresent()) {
|
||||
// 2-A. 기존 데이터가 있는 경우 - 업데이트 여부 결정
|
||||
HealthCheckupEntity entity = existingEntity.get();
|
||||
|
||||
// 2-A-1. 이미 같은 raw_id로 처리된 경우 건너뜀
|
||||
if (latestRaw.getRawId().equals(entity.getRawId())) {
|
||||
logger.debug("이미 같은 raw_id로 처리됨 - Raw ID: {}", latestRaw.getRawId());
|
||||
skippedCount++;
|
||||
resultCheckup = entity.toDomain();
|
||||
} else {
|
||||
// 2-A-2. 더 최신 데이터인지 확인 (연도 비교 우선, 그 다음 생성일시 비교)
|
||||
boolean shouldUpdate = latestRaw.getReferenceYear() > entity.getReferenceYear() ||
|
||||
(latestRaw.getReferenceYear().equals(entity.getReferenceYear()) &&
|
||||
latestRaw.getCreatedAt().isAfter(entity.getCreatedAt()));
|
||||
|
||||
if (shouldUpdate) {
|
||||
// 더 최신 데이터로 업데이트
|
||||
updateEntityFromRaw(entity, latestRaw);
|
||||
entity.setProcessedAt(LocalDateTime.now());
|
||||
healthCheckupRepository.save(entity);
|
||||
|
||||
logger.debug("기존 데이터 업데이트 - Member: {}, 새 Raw ID: {}, 새 연도: {}",
|
||||
memberSerialNumber, latestRaw.getRawId(), latestRaw.getReferenceYear());
|
||||
updatedCount++;
|
||||
resultCheckup = entity.toDomain();
|
||||
} else {
|
||||
// 더 오래된 데이터 → 건너뜀
|
||||
logger.debug("더 오래된 raw 데이터로 업데이트 안함 - Raw ID: {}, 연도: {}",
|
||||
latestRaw.getRawId(), latestRaw.getReferenceYear());
|
||||
skippedCount++;
|
||||
resultCheckup = entity.toDomain();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 2-B. 기존 데이터가 없는 경우 - 새로 생성
|
||||
HealthCheckup processedCheckup = HealthCheckup.fromRaw(latestRaw, memberSerialNumber);
|
||||
HealthCheckupEntity newEntity = HealthCheckupEntity.fromDomain(processedCheckup);
|
||||
|
||||
// checkup_id를 member_serial_number와 동일하게 설정 (Entity에서 자동 설정되지만 명시적으로)
|
||||
newEntity.setCheckupId(memberSerialNumber);
|
||||
|
||||
HealthCheckupEntity savedEntity = healthCheckupRepository.save(newEntity);
|
||||
|
||||
logger.debug("새 건강검진 데이터 저장 - Member: {}, Raw ID: {}, 연도: {}",
|
||||
memberSerialNumber, latestRaw.getRawId(), latestRaw.getReferenceYear());
|
||||
newCount++;
|
||||
resultCheckup = savedEntity.toDomain();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("건강검진 데이터 동기화 중 오류 - Member: {}, Raw ID: {}",
|
||||
memberSerialNumber, latestRaw.getRawId(), e);
|
||||
skippedCount++;
|
||||
}
|
||||
|
||||
HealthCheckupSyncResult result = new HealthCheckupSyncResult(
|
||||
totalCount, newCount, updatedCount, skippedCount, resultCheckup);
|
||||
|
||||
logger.info("건강검진 데이터 동기화 완료 (1개 레코드 방식) - Member: {}, " +
|
||||
"결과: 총 {}개 (신규: {}, 갱신: {}, 건너뜀: {})",
|
||||
memberSerialNumber, totalCount, newCount, updatedCount, skippedCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public HealthCheckupSyncResult processAndSaveHealthCheckupData(List<HealthCheckupRaw> rawCheckupData, Long memberSerialNumber) {
|
||||
// 1개 레코드 방식에서는 syncHealthCheckupData와 동일하게 동작
|
||||
return syncHealthCheckupData(rawCheckupData, memberSerialNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthCheckup> getProcessedHealthCheckupHistory(Long memberSerialNumber) {
|
||||
logger.info("가공된 건강검진 데이터 조회 - Member Serial Number: {}", memberSerialNumber);
|
||||
|
||||
Optional<HealthCheckupEntity> entity = healthCheckupRepository.findByMemberSerialNumber(memberSerialNumber);
|
||||
|
||||
if (entity.isPresent()) {
|
||||
return List.of(entity.get().toDomain()); // 1개만 반환
|
||||
} else {
|
||||
return List.of(); // 빈 리스트 반환
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HealthCheckup> getLatestProcessedHealthCheckup(Long memberSerialNumber) {
|
||||
logger.info("최신 가공된 건강검진 데이터 조회 - Member Serial Number: {}", memberSerialNumber);
|
||||
|
||||
Optional<HealthCheckupEntity> entity = healthCheckupRepository.findByMemberSerialNumber(memberSerialNumber);
|
||||
return entity.map(HealthCheckupEntity::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HealthCheckup> getRecentHealthCheckups(Long memberSerialNumber, int limit) {
|
||||
logger.info("최근 {}개 가공된 건강검진 데이터 조회 - Member Serial Number: {}", limit, memberSerialNumber);
|
||||
|
||||
// 1개 레코드 방식에서는 limit과 관계없이 최대 1개만 반환
|
||||
return getProcessedHealthCheckupHistory(memberSerialNumber);
|
||||
}
|
||||
|
||||
// ========== 정상 범위 관련 메서드 (기존과 동일) ==========
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
// ========== 헬퍼 메서드 ==========
|
||||
|
||||
/**
|
||||
* Raw 데이터로 기존 Entity 업데이트 (1개 레코드 방식)
|
||||
*/
|
||||
private void updateEntityFromRaw(HealthCheckupEntity entity, HealthCheckupRaw raw) {
|
||||
// 기본 정보 업데이트
|
||||
entity.setRawId(raw.getRawId());
|
||||
entity.setReferenceYear(raw.getReferenceYear());
|
||||
entity.setAge(raw.getAge());
|
||||
|
||||
// 신체 측정 정보
|
||||
entity.setHeight(raw.getHeight());
|
||||
entity.setWeight(raw.getWeight());
|
||||
entity.setBmi(raw.calculateBMI());
|
||||
entity.setWaistCircumference(raw.getWaistCircumference());
|
||||
|
||||
// 시력/청력
|
||||
entity.setVisualAcuityLeft(raw.getVisualAcuityLeft());
|
||||
entity.setVisualAcuityRight(raw.getVisualAcuityRight());
|
||||
entity.setHearingLeft(raw.getHearingLeft());
|
||||
entity.setHearingRight(raw.getHearingRight());
|
||||
|
||||
// 혈압
|
||||
entity.setSystolicBp(raw.getSystolicBp());
|
||||
entity.setDiastolicBp(raw.getDiastolicBp());
|
||||
|
||||
// 혈액검사
|
||||
entity.setFastingGlucose(raw.getFastingGlucose());
|
||||
entity.setTotalCholesterol(raw.getTotalCholesterol());
|
||||
entity.setTriglyceride(raw.getTriglyceride());
|
||||
entity.setHdlCholesterol(raw.getHdlCholesterol());
|
||||
entity.setLdlCholesterol(raw.getLdlCholesterol());
|
||||
entity.setHemoglobin(raw.getHemoglobin());
|
||||
|
||||
// 소변/혈청
|
||||
entity.setUrineProtein(raw.getUrineProtein());
|
||||
entity.setSerumCreatinine(raw.getSerumCreatinine());
|
||||
|
||||
// 간기능
|
||||
entity.setAst(raw.getAst());
|
||||
entity.setAlt(raw.getAlt());
|
||||
entity.setGammaGtp(raw.getGammaGtp());
|
||||
|
||||
// 생활습관
|
||||
entity.setSmokingStatus(raw.getSmokingStatus());
|
||||
entity.setDrinkingStatus(raw.getDrinkingStatus());
|
||||
|
||||
// 시간 정보 (created_at은 Raw 데이터의 것으로, processed_at은 현재 시간으로)
|
||||
entity.setCreatedAt(raw.getCreatedAt());
|
||||
|
||||
logger.debug("Entity 업데이트 완료 - Raw ID: {}, Reference Year: {}",
|
||||
raw.getRawId(), raw.getReferenceYear());
|
||||
}
|
||||
}
|
||||
+590
@@ -0,0 +1,590 @@
|
||||
package com.healthsync.health.service.HealthProfile;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
import com.healthsync.health.domain.Oauth.User;
|
||||
import com.healthsync.health.dto.HealthCheck.HealthProfileHistoryResponse;
|
||||
import com.healthsync.health.dto.HealthCheck.HealthCheckupSyncResult;
|
||||
import com.healthsync.health.repository.entity.HealthCheckupRawEntity;
|
||||
import com.healthsync.health.repository.jpa.HealthCheckupRawRepository;
|
||||
import com.healthsync.common.dto.CusApiResponse;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Period;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 현실적인 건강 변화 패턴을 반영한 다중 연도 Mock 데이터 생성기
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 2.0
|
||||
*/
|
||||
@Service
|
||||
public class RealisticHealthMockDataGenerator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RealisticHealthMockDataGenerator.class);
|
||||
private final Random random = new Random();
|
||||
private final HealthProfileService healthProfileService;
|
||||
private final HealthCheckupRawRepository healthCheckupRawRepository;
|
||||
|
||||
// 지역 코드
|
||||
private final List<Integer> regionCodes = Arrays.asList(11, 26, 27, 28, 29, 30, 31, 36, 41, 42, 43, 44, 45, 46, 47, 48, 50);
|
||||
|
||||
public RealisticHealthMockDataGenerator(HealthProfileService healthProfileService,
|
||||
HealthCheckupRawRepository healthCheckupRawRepository) {
|
||||
this.healthProfileService = healthProfileService;
|
||||
this.healthCheckupRawRepository = healthCheckupRawRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* ✨ 현실적인 다중 연도 Mock 데이터 생성 (메인 메서드)
|
||||
*
|
||||
* 🎯 적용 위치: HealthMockDataGenerator.generateAndProcessMockData() 대체
|
||||
*
|
||||
* @param user 사용자 정보 (이름, 생년월일, 직업 포함)
|
||||
* @param genderCode 성별 코드
|
||||
* @param memberSerialNumber 회원 일련번호
|
||||
* @param yearCount 생성할 연도 수 (1~5, 기본값 3)
|
||||
* @return 건강검진 이력 응답
|
||||
*/
|
||||
@Transactional
|
||||
public CusApiResponse<HealthProfileHistoryResponse> generateRealisticMockData(
|
||||
User user, Integer genderCode, Long memberSerialNumber, Integer yearCount) {
|
||||
|
||||
// 연도 수 검증 및 기본값 설정
|
||||
int validYearCount = (yearCount != null && yearCount >= 1 && yearCount <= 5) ? yearCount : 5;
|
||||
|
||||
// 사용자 정보 추출
|
||||
String userName = user.getName();
|
||||
LocalDate birthDate = user.getBirthDate();
|
||||
String occupation = user.getOccupation();
|
||||
|
||||
logger.info("현실적인 다중 연도 Mock 데이터 생성 시작 - 사용자: {}, 생년월일: {}, 연도 수: {}",
|
||||
userName, birthDate, validYearCount);
|
||||
|
||||
try {
|
||||
// 🎂 실제 생년월일 기반 나이 계산
|
||||
int currentAge = Period.between(birthDate, LocalDate.now()).getYears();
|
||||
int currentYear = LocalDate.now().getYear();
|
||||
|
||||
logger.debug("사용자 기본 정보 - 현재 나이: {}세, 성별: {}, 직업: {}",
|
||||
currentAge, genderCode == 1 ? "남성" : "여성", occupation);
|
||||
|
||||
// 🏥 개인별 건강 베이스라인 생성 (일관된 개인 특성)
|
||||
HealthBaseline baseline = generatePersonalHealthBaseline(currentAge, genderCode);
|
||||
|
||||
List<HealthCheckupRaw> mockDataList = new ArrayList<>();
|
||||
|
||||
// 📅 연도별 데이터 생성 (최신 → 과거 순서)
|
||||
for (int i = 0; i < validYearCount; i++) {
|
||||
int targetYear = currentYear - i;
|
||||
int ageAtTime = currentAge - i;
|
||||
|
||||
// 🚨 음수/0 나이 방지
|
||||
if (ageAtTime <= 0) {
|
||||
logger.warn("유효하지 않은 나이 - {}년 ({}세) 데이터 생성 중단",
|
||||
targetYear, ageAtTime);
|
||||
break; // 더 이상 과거로 가지 않음
|
||||
}
|
||||
|
||||
HealthCheckupRaw mockData = generateRealisticHealthDataForYear(
|
||||
userName, birthDate, ageAtTime, genderCode, targetYear, i, baseline
|
||||
);
|
||||
|
||||
mockDataList.add(mockData);
|
||||
}
|
||||
|
||||
// 💾 health_checkup_raw 테이블에 모든 데이터 저장
|
||||
List<HealthCheckupRaw> savedDataList = saveAllMockDataToDatabase(mockDataList);
|
||||
|
||||
// 🔄 최신 데이터를 기존 로직으로 health_checkup에 처리
|
||||
HealthCheckupRaw latestData = savedDataList.get(0);
|
||||
HealthCheckupSyncResult syncResult = processMockDataWithExistingLogic(latestData, memberSerialNumber);
|
||||
|
||||
// 📋 응답 생성 (실제 사용자 정보 활용)
|
||||
HealthProfileHistoryResponse response = createResponseWithAllData(
|
||||
userName, currentAge, genderCode, occupation, latestData, savedDataList
|
||||
);
|
||||
|
||||
logger.info("현실적인 다중 연도 Mock 데이터 생성 완료 - 사용자: {} ({}세), 생성: {}개, 처리결과: 신규{}/갱신{}",
|
||||
userName, currentAge, savedDataList.size(), syncResult.getNewCount(), syncResult.getUpdatedCount());
|
||||
|
||||
return new CusApiResponse<>(true,
|
||||
String.format("현실적인 건강검진 데이터 %d개 연도 생성 완료 (%d~%d년) - %s (%d세)",
|
||||
validYearCount, currentYear - (validYearCount-1), currentYear, userName, currentAge), response);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("현실적인 다중 연도 Mock 데이터 생성 중 오류 - 사용자: {} ({}세)",
|
||||
userName, Period.between(birthDate, LocalDate.now()).getYears(), e);
|
||||
return new CusApiResponse<>(false, "현실적인 Mock 데이터 생성 실패: " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔄 기존 API 호환성을 위한 오버로드 메서드
|
||||
*/
|
||||
@Transactional
|
||||
public CusApiResponse<HealthProfileHistoryResponse> generateRealisticMockData(
|
||||
String userName, LocalDate birthDate, Integer genderCode, String occupation,
|
||||
Long memberSerialNumber, Integer yearCount) {
|
||||
|
||||
// User 객체 생성하여 메인 메서드 호출
|
||||
User tempUser = new User();
|
||||
tempUser.setName(userName);
|
||||
tempUser.setBirthDate(birthDate);
|
||||
tempUser.setOccupation(occupation);
|
||||
|
||||
return generateRealisticMockData(tempUser, genderCode, memberSerialNumber, yearCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧬 개인별 건강 베이스라인 생성 (일관된 개인 특성 유지)
|
||||
*/
|
||||
private HealthBaseline generatePersonalHealthBaseline(int age, Integer genderCode) {
|
||||
HealthBaseline baseline = new HealthBaseline();
|
||||
|
||||
// 🏃♂️ 개인 유형 결정 (건강형, 평균형, 주의형)
|
||||
double healthTypeRand = random.nextDouble();
|
||||
if (healthTypeRand < 0.3) {
|
||||
baseline.healthType = HealthType.HEALTHY; // 30% - 건강형
|
||||
} else if (healthTypeRand < 0.8) {
|
||||
baseline.healthType = HealthType.AVERAGE; // 50% - 평균형
|
||||
} else {
|
||||
baseline.healthType = HealthType.AT_RISK; // 20% - 주의형
|
||||
}
|
||||
|
||||
// 💪 성별별 신체 기본값
|
||||
if (genderCode == 1) { // 남성
|
||||
baseline.baseHeight = 165 + random.nextInt(20); // 165~185cm
|
||||
baseline.baseWeight = 60 + random.nextInt(35); // 60~95kg
|
||||
} else { // 여성
|
||||
baseline.baseHeight = 155 + random.nextInt(20); // 155~175cm
|
||||
baseline.baseWeight = 45 + random.nextInt(30); // 45~75kg
|
||||
}
|
||||
|
||||
// 🧘♀️ 개인별 생활습관 패턴
|
||||
baseline.smokingTrend = random.nextDouble() < 0.7 ? "never" :
|
||||
(random.nextDouble() < 0.5 ? "quit" : "current");
|
||||
baseline.drinkingTrend = random.nextDouble() < 0.3 ? "none" :
|
||||
(random.nextDouble() < 0.6 ? "moderate" : "frequent");
|
||||
|
||||
// 🎯 개인별 취약 부위 설정 (현실적인 건강 약점)
|
||||
List<String> vulnerabilities = Arrays.asList("blood_pressure", "cholesterol", "glucose", "liver", "vision");
|
||||
Collections.shuffle(vulnerabilities);
|
||||
baseline.primaryVulnerability = vulnerabilities.get(0);
|
||||
baseline.secondaryVulnerability = vulnerabilities.get(1);
|
||||
|
||||
logger.debug("개인 베이스라인 생성 - 유형: {}, 키: {}cm, 몸무게: {}kg, 주요약점: {}",
|
||||
baseline.healthType, baseline.baseHeight, baseline.baseWeight, baseline.primaryVulnerability);
|
||||
|
||||
return baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* 📊 연도별 현실적인 건강 데이터 생성
|
||||
*/
|
||||
private HealthCheckupRaw generateRealisticHealthDataForYear(
|
||||
String name, LocalDate birthDate, int ageAtTime, Integer genderCode,
|
||||
int targetYear, int yearsAgo, HealthBaseline baseline) {
|
||||
|
||||
HealthCheckupRaw raw = new HealthCheckupRaw();
|
||||
|
||||
// 🆔 기본 정보 (실제 사용자 정보 활용)
|
||||
raw.setRawId(System.currentTimeMillis() + random.nextInt(1000) + yearsAgo * 1000);
|
||||
raw.setReferenceYear(targetYear);
|
||||
raw.setBirthDate(birthDate); // ✅ 실제 사용자 생년월일 사용
|
||||
raw.setName(name); // ✅ 실제 사용자 이름 사용
|
||||
raw.setRegionCode(regionCodes.get(random.nextInt(regionCodes.size())));
|
||||
raw.setGenderCode(genderCode);
|
||||
raw.setAge(ageAtTime); // ✅ 실제 생년월일 기반 계산된 나이
|
||||
raw.setCreatedAt(LocalDateTime.of(targetYear, 6 + random.nextInt(6), 15, 10, 0));
|
||||
|
||||
logger.debug("기본 정보 설정 - 이름: {}, 생년월일: {}, {}년 당시 나이: {}세",
|
||||
name, birthDate, targetYear, ageAtTime);
|
||||
|
||||
// 📏 신체 계측 - 비선형적 변화
|
||||
raw.setHeight(baseline.baseHeight); // 키는 고정
|
||||
raw.setWeight(generateRealisticWeight(baseline, yearsAgo, ageAtTime));
|
||||
raw.setWaistCircumference(generateRealisticWaistCircumference(baseline, yearsAgo, ageAtTime));
|
||||
|
||||
// 👁️ 시력 - 점진적 변화 + 개인차
|
||||
generateRealisticVision(raw, baseline, yearsAgo, ageAtTime);
|
||||
|
||||
// 👂 청력 - 연령별 현실적 변화
|
||||
generateRealisticHearing(raw, baseline, yearsAgo, ageAtTime);
|
||||
|
||||
// 🩸 혈압 - 개인 패턴 + 연령 요소
|
||||
generateRealisticBloodPressure(raw, baseline, yearsAgo, ageAtTime);
|
||||
|
||||
// 🧪 혈액검사 - 생활습관 반영 + 비선형 변화
|
||||
generateRealisticBloodTests(raw, baseline, yearsAgo, ageAtTime, genderCode);
|
||||
|
||||
// 🥃 생활습관 - 시간에 따른 변화 패턴
|
||||
generateRealisticLifestyle(raw, baseline, yearsAgo);
|
||||
|
||||
logger.debug("건강 데이터 생성 완료 - {}년: 체중 {}kg, 혈압 {}/{}, 총콜레스테롤 {}",
|
||||
targetYear, raw.getWeight(), raw.getSystolicBp(), raw.getDiastolicBp(), raw.getTotalCholesterol());
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* ⚖️ 현실적인 체중 변화 생성
|
||||
*/
|
||||
private Integer generateRealisticWeight(HealthBaseline baseline, int yearsAgo, int age) {
|
||||
double baseWeight = baseline.baseWeight;
|
||||
|
||||
// 🎢 비선형적 체중 변화 (사인파 + 노이즈)
|
||||
double yearlyVariation = Math.sin(yearsAgo * 0.5) * 3; // 주기적 변동 ±3kg
|
||||
double randomNoise = (random.nextGaussian() * 1.5); // 랜덤 노이즈 ±1.5kg
|
||||
double ageEffect = (age > 40) ? (age - 40) * 0.2 : 0; // 40세 이후 연간 +0.2kg
|
||||
|
||||
// 💪 개인 유형별 체중 변화 패턴
|
||||
double typeModifier = switch (baseline.healthType) {
|
||||
case HEALTHY -> -1.0; // 건강형은 약간 가벼움
|
||||
case AVERAGE -> 0.0; // 평균형은 변화 없음
|
||||
case AT_RISK -> 2.0; // 주의형은 약간 무거움
|
||||
};
|
||||
|
||||
int finalWeight = (int)(baseWeight + yearlyVariation + randomNoise + ageEffect + typeModifier);
|
||||
return Math.max(40, Math.min(150, finalWeight)); // 40~150kg 범위 제한
|
||||
}
|
||||
|
||||
/**
|
||||
* 📐 현실적인 허리둘레 변화 생성
|
||||
*/
|
||||
private Integer generateRealisticWaistCircumference(HealthBaseline baseline, int yearsAgo, int age) {
|
||||
// BMI 기반 기본 허리둘레 추정
|
||||
double estimatedBMI = baseline.baseWeight / Math.pow(baseline.baseHeight / 100.0, 2);
|
||||
int baseWaist = (int)(estimatedBMI * 3 + 50); // 대략적인 계산
|
||||
|
||||
// 연령별 증가 경향
|
||||
double ageIncrease = (age > 35) ? (age - 35) * 0.3 : 0;
|
||||
double yearlyVariation = (random.nextGaussian() * 2); // ±2cm 변동
|
||||
|
||||
int finalWaist = (int)(baseWaist + ageIncrease + yearlyVariation);
|
||||
return Math.max(60, Math.min(120, finalWaist));
|
||||
}
|
||||
|
||||
/**
|
||||
* 👁️ 현실적인 시력 변화 생성
|
||||
*/
|
||||
private void generateRealisticVision(HealthCheckupRaw raw, HealthBaseline baseline, int yearsAgo, int age) {
|
||||
// 기본 시력 (20~30대는 좋음, 이후 점진적 하락)
|
||||
double baseVision = (age < 30) ? 0.9 + random.nextDouble() * 0.8 :
|
||||
(age < 50) ? 0.7 + random.nextDouble() * 0.6 :
|
||||
0.5 + random.nextDouble() * 0.4;
|
||||
|
||||
|
||||
|
||||
// 📱 현대인 시력 악화 요소 (비선형)
|
||||
double modernLifeEffect = (age>15)
|
||||
? Math.log(age - 15) * 0.05 // 로그 함수로 비선형 악화
|
||||
: Math.log(age) * 0.01;
|
||||
|
||||
// 👀 개인차 및 유전적 요소
|
||||
double geneticFactor = (random.nextGaussian() * 0.1);
|
||||
|
||||
// 🕰️ 연도별 변화 (과거가 더 좋았음)
|
||||
double timeRecovery = yearsAgo * 0.03; // 과거로 갈수록 시력 개선
|
||||
|
||||
double leftVision = Math.max(0.1, Math.min(2.0, baseVision - modernLifeEffect + geneticFactor + timeRecovery));
|
||||
double rightVision = Math.max(0.1, Math.min(2.0, leftVision + random.nextGaussian() * 0.1)); // 좌우 약간 차이
|
||||
|
||||
raw.setVisualAcuityLeft(BigDecimal.valueOf(leftVision).setScale(1, BigDecimal.ROUND_HALF_UP));
|
||||
raw.setVisualAcuityRight(BigDecimal.valueOf(rightVision).setScale(1, BigDecimal.ROUND_HALF_UP));
|
||||
}
|
||||
|
||||
/**
|
||||
* 👂 현실적인 청력 변화 생성
|
||||
*/
|
||||
private void generateRealisticHearing(HealthCheckupRaw raw, HealthBaseline baseline, int yearsAgo, int age) {
|
||||
// 연령별 청력 손실 확률 (비선형)
|
||||
double hearingLossProbability = Math.max(0, (age - 50) * 0.02); // 50세부터 증가
|
||||
|
||||
// 🎵 개인 유형별 청력 건강도
|
||||
double typeModifier = switch (baseline.healthType) {
|
||||
case HEALTHY -> 0.005; // 건강형은 청력 좋음
|
||||
case AVERAGE -> 0.01; // 평균형
|
||||
case AT_RISK -> 0.02; // 주의형은 청력 나쁨
|
||||
};
|
||||
|
||||
boolean leftHearingLoss = random.nextDouble() < (hearingLossProbability + typeModifier);
|
||||
boolean rightHearingLoss = random.nextDouble() < (hearingLossProbability + typeModifier);
|
||||
|
||||
raw.setHearingLeft(leftHearingLoss ? 2 : 1);
|
||||
raw.setHearingRight(rightHearingLoss ? 2 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🩸 현실적인 혈압 변화 생성
|
||||
*/
|
||||
private void generateRealisticBloodPressure(HealthCheckupRaw raw, HealthBaseline baseline, int yearsAgo, int age) {
|
||||
// 🎯 개인 유형별 기본 혈압
|
||||
int baseSystolic = switch (baseline.healthType) {
|
||||
case HEALTHY -> 110 + random.nextInt(15); // 110~125
|
||||
case AVERAGE -> 115 + random.nextInt(20); // 115~135
|
||||
case AT_RISK -> 125 + random.nextInt(25); // 125~150
|
||||
};
|
||||
|
||||
int baseDiastolic = (int)(baseSystolic * 0.6 + random.nextInt(10)); // 수축기의 60% + 변동
|
||||
|
||||
// 📈 연령별 혈압 상승 (비선형 - 제곱근 함수)
|
||||
double ageEffect = Math.sqrt(Math.max(0, age - 30)) * 2;
|
||||
|
||||
// 🌊 스트레스 요인 (주기적 변화)
|
||||
double stressEffect = Math.sin(yearsAgo * 0.3) * 5 + random.nextGaussian() * 3;
|
||||
|
||||
// 🏃♂️ 생활습관 영향
|
||||
double lifestyleEffect = baseline.smokingTrend.equals("current") ? 8 :
|
||||
baseline.smokingTrend.equals("quit") ? 3 : 0;
|
||||
lifestyleEffect += baseline.drinkingTrend.equals("frequent") ? 5 : 0;
|
||||
|
||||
// 💊 취약 부위 반영
|
||||
double vulnerabilityEffect = baseline.primaryVulnerability.equals("blood_pressure") ? 10 : 0;
|
||||
|
||||
int finalSystolic = (int)(baseSystolic + ageEffect + stressEffect + lifestyleEffect + vulnerabilityEffect);
|
||||
int finalDiastolic = (int)(baseDiastolic + ageEffect * 0.5 + stressEffect * 0.6);
|
||||
|
||||
// 🔒 현실적 범위 제한
|
||||
raw.setSystolicBp(Math.max(90, Math.min(200, finalSystolic)));
|
||||
raw.setDiastolicBp(Math.max(60, Math.min(120, finalDiastolic)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧪 현실적인 혈액검사 수치 생성
|
||||
*/
|
||||
private void generateRealisticBloodTests(HealthCheckupRaw raw, HealthBaseline baseline,
|
||||
int yearsAgo, int age, Integer genderCode) {
|
||||
|
||||
// 🍭 혈당 - 식습관과 연령 반영
|
||||
int baseGlucose = switch (baseline.healthType) {
|
||||
case HEALTHY -> 85 + random.nextInt(10); // 85~95
|
||||
case AVERAGE -> 90 + random.nextInt(15); // 90~105
|
||||
case AT_RISK -> 95 + random.nextInt(20); // 95~115
|
||||
};
|
||||
|
||||
double glucoseAgeEffect = (age > 40) ? (age - 40) * 0.8 : 0;
|
||||
double vulnerabilityEffect = baseline.primaryVulnerability.equals("glucose") ? 15 : 0;
|
||||
raw.setFastingGlucose((int)(baseGlucose + glucoseAgeEffect + vulnerabilityEffect + random.nextGaussian() * 5));
|
||||
|
||||
// 🥩 콜레스테롤 - 연령과 생활습관 강한 상관관계
|
||||
generateRealisticCholesterol(raw, baseline, yearsAgo, age);
|
||||
|
||||
// 🩸 헤모글로빈 - 성별차 + 개인차
|
||||
generateRealisticHemoglobin(raw, baseline, genderCode);
|
||||
|
||||
// 🫘 간기능 - 음주 패턴 강하게 반영
|
||||
generateRealisticLiverFunction(raw, baseline, yearsAgo, age);
|
||||
|
||||
// 💧 신장기능 - 안정적이지만 연령별 변화
|
||||
generateRealisticKidneyFunction(raw, baseline, age);
|
||||
}
|
||||
|
||||
/**
|
||||
* 🥩 현실적인 콜레스테롤 수치 생성
|
||||
*/
|
||||
private void generateRealisticCholesterol(HealthCheckupRaw raw, HealthBaseline baseline, int yearsAgo, int age) {
|
||||
// 총 콜레스테롤 - 연령별 증가 경향
|
||||
int baseTotalChol = 160 + age * 2 + random.nextInt(40);
|
||||
double vulnerabilityEffect = baseline.primaryVulnerability.equals("cholesterol") ? 30 : 0;
|
||||
raw.setTotalCholesterol((int)(baseTotalChol + vulnerabilityEffect));
|
||||
|
||||
// 중성지방 - 더 변동성 크고 생활습관 민감
|
||||
int baseTriglyceride = 80 + random.nextInt(60);
|
||||
double lifestyleEffect = baseline.drinkingTrend.equals("frequent") ? 40 : 0;
|
||||
double metabolicEffect = Math.sin(yearsAgo * 0.4) * 20; // 주기적 변동
|
||||
raw.setTriglyceride((int)(baseTriglyceride + lifestyleEffect + metabolicEffect + random.nextGaussian() * 15));
|
||||
|
||||
// HDL (좋은 콜레스테롤) - 운동 습관 반영
|
||||
int baseHDL = switch (baseline.healthType) {
|
||||
case HEALTHY -> 55 + random.nextInt(20); // 55~75
|
||||
case AVERAGE -> 45 + random.nextInt(20); // 45~65
|
||||
case AT_RISK -> 35 + random.nextInt(20); // 35~55
|
||||
};
|
||||
raw.setHdlCholesterol(baseHDL);
|
||||
|
||||
// LDL (나쁜 콜레스테롤) - 총 콜레스테롤에서 계산
|
||||
int calculatedLDL = raw.getTotalCholesterol() - raw.getHdlCholesterol() - (raw.getTriglyceride() / 5);
|
||||
raw.setLdlCholesterol(Math.max(50, calculatedLDL));
|
||||
}
|
||||
|
||||
/**
|
||||
* 🩸 현실적인 헤모글로빈 수치 생성
|
||||
*/
|
||||
private void generateRealisticHemoglobin(HealthCheckupRaw raw, HealthBaseline baseline, Integer genderCode) {
|
||||
double baseHemoglobin;
|
||||
if (genderCode == 1) { // 남성
|
||||
baseHemoglobin = 14.0 + random.nextGaussian() * 1.5; // 평균 14.0 ± 1.5
|
||||
} else { // 여성
|
||||
baseHemoglobin = 12.5 + random.nextGaussian() * 1.2; // 평균 12.5 ± 1.2
|
||||
}
|
||||
|
||||
// 🍎 영양 상태 반영
|
||||
double nutritionEffect = switch (baseline.healthType) {
|
||||
case HEALTHY -> 0.5;
|
||||
case AVERAGE -> 0.0;
|
||||
case AT_RISK -> -0.8;
|
||||
};
|
||||
|
||||
double finalHemoglobin = Math.max(8.0, Math.min(20.0, baseHemoglobin + nutritionEffect));
|
||||
raw.setHemoglobin(BigDecimal.valueOf(finalHemoglobin).setScale(1, BigDecimal.ROUND_HALF_UP));
|
||||
}
|
||||
|
||||
/**
|
||||
* 🫘 현실적인 간기능 수치 생성
|
||||
*/
|
||||
private void generateRealisticLiverFunction(HealthCheckupRaw raw, HealthBaseline baseline, int yearsAgo, int age) {
|
||||
// 기본 간기능 수치
|
||||
int baseAST = 20 + random.nextInt(15);
|
||||
int baseALT = 15 + random.nextInt(20);
|
||||
int baseGGT = 15 + random.nextInt(20);
|
||||
|
||||
// 🍺 음주 영향 (강한 상관관계)
|
||||
double drinkingEffect = switch (baseline.drinkingTrend) {
|
||||
case "none" -> 0;
|
||||
case "moderate" -> 5 + random.nextGaussian() * 3;
|
||||
case "frequent" -> 15 + random.nextGaussian() * 8;
|
||||
default -> 0;
|
||||
};
|
||||
|
||||
// 🎯 간이 취약 부위인 경우
|
||||
double vulnerabilityEffect = baseline.primaryVulnerability.equals("liver") ? 12 : 0;
|
||||
|
||||
double ageEffect = (age > 19)
|
||||
? Math.log(age - 19) * 2
|
||||
: Math.log(age);
|
||||
|
||||
|
||||
raw.setAst((int)(baseAST + drinkingEffect + vulnerabilityEffect + ageEffect));
|
||||
raw.setAlt((int)(baseALT + drinkingEffect * 1.2 + vulnerabilityEffect + ageEffect));
|
||||
raw.setGammaGtp((int)(baseGGT + drinkingEffect * 2 + vulnerabilityEffect * 1.5));
|
||||
}
|
||||
|
||||
/**
|
||||
* 💧 현실적인 신장기능 수치 생성
|
||||
*/
|
||||
private void generateRealisticKidneyFunction(HealthCheckupRaw raw, HealthBaseline baseline, int age) {
|
||||
// 소변 단백 - 대부분 음성, 연령별 약간 증가
|
||||
double proteinuriaProbability = Math.max(0.05, (age - 50) * 0.01);
|
||||
raw.setUrineProtein(random.nextDouble() < proteinuriaProbability ? 2 : 1);
|
||||
|
||||
// 혈청 크레아티닌 - 안정적이지만 연령별 약간 증가
|
||||
double baseCreatinine = 0.8 + random.nextGaussian() * 0.2;
|
||||
double ageEffect = (age > 60) ? (age - 60) * 0.01 : 0;
|
||||
double finalCreatinine = Math.max(0.5, Math.min(2.0, baseCreatinine + ageEffect));
|
||||
raw.setSerumCreatinine(BigDecimal.valueOf(finalCreatinine).setScale(1, BigDecimal.ROUND_HALF_UP));
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚬 현실적인 생활습관 변화 생성
|
||||
*/
|
||||
private void generateRealisticLifestyle(HealthCheckupRaw raw, HealthBaseline baseline, int yearsAgo) {
|
||||
// 🚭 흡연 상태 - 시간에 따른 변화 패턴
|
||||
int smokingStatus = switch (baseline.smokingTrend) {
|
||||
case "never" -> 1; // 비흡연
|
||||
case "quit" -> (yearsAgo > 2) ? 3 : 2; // 과거에는 흡연, 최근에 금연
|
||||
case "current" -> (random.nextDouble() < 0.1) ? 2 : 3; // 대부분 흡연, 10% 확률로 과거 흡연
|
||||
default -> 1;
|
||||
};
|
||||
|
||||
// 🍺 음주 상태 - 연령별 패턴 변화
|
||||
int drinkingStatus = switch (baseline.drinkingTrend) {
|
||||
case "none" -> 1; // 비음주
|
||||
case "moderate" -> (random.nextDouble() < 0.7) ? 2 : 3; // 70% 과거, 30% 현재
|
||||
case "frequent" -> (yearsAgo > 3 && random.nextDouble() < 0.3) ? 2 : 3; // 과거엔 더 많이 음주
|
||||
default -> 1;
|
||||
};
|
||||
|
||||
raw.setSmokingStatus(smokingStatus);
|
||||
raw.setDrinkingStatus(drinkingStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 💾 모든 Mock 데이터를 데이터베이스에 저장
|
||||
*/
|
||||
private List<HealthCheckupRaw> saveAllMockDataToDatabase(List<HealthCheckupRaw> mockDataList) {
|
||||
List<HealthCheckupRaw> savedDataList = new ArrayList<>();
|
||||
|
||||
for (HealthCheckupRaw mockData : mockDataList) {
|
||||
HealthCheckupRawEntity rawEntity = HealthCheckupRawEntity.fromDomain(mockData);
|
||||
HealthCheckupRawEntity savedEntity = healthCheckupRawRepository.save(rawEntity);
|
||||
savedDataList.add(savedEntity.toDomain());
|
||||
}
|
||||
|
||||
logger.info("Mock Raw 데이터 DB 저장 완료 - {} 건", savedDataList.size());
|
||||
return savedDataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔄 기존 로직으로 Mock 데이터 처리
|
||||
*/
|
||||
private HealthCheckupSyncResult processMockDataWithExistingLogic(HealthCheckupRaw latestData, Long memberSerialNumber) {
|
||||
List<HealthCheckupRaw> latestDataList = Arrays.asList(latestData);
|
||||
HealthCheckupSyncResult syncResult = healthProfileService.syncHealthCheckupData(latestDataList, memberSerialNumber);
|
||||
|
||||
logger.info("최신 Mock 데이터 처리 완료 - 연도: {}, 결과: 신규{}/갱신{}/건너뜀{}",
|
||||
latestData.getReferenceYear(), syncResult.getNewCount(), syncResult.getUpdatedCount(), syncResult.getSkippedCount());
|
||||
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 📋 모든 데이터를 포함한 응답 생성
|
||||
*/
|
||||
private HealthProfileHistoryResponse createResponseWithAllData(
|
||||
String userName, int currentAge, Integer genderCode, String occupation,
|
||||
HealthCheckupRaw latestData, List<HealthCheckupRaw> allData) {
|
||||
|
||||
HealthProfileHistoryResponse.UserInfo userInfo = new HealthProfileHistoryResponse.UserInfo(
|
||||
userName,
|
||||
currentAge,
|
||||
convertGenderCodeToString(genderCode),
|
||||
occupation != null ? occupation : "정보 없음"
|
||||
);
|
||||
|
||||
return new HealthProfileHistoryResponse(userInfo, latestData, allData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 성별 코드 변환
|
||||
*/
|
||||
private String convertGenderCodeToString(Integer genderCode) {
|
||||
return switch (genderCode) {
|
||||
case 1 -> "남성";
|
||||
case 2 -> "여성";
|
||||
default -> "정보 없음";
|
||||
};
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 내부 클래스들
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* 🧬 개인 건강 베이스라인 정보
|
||||
*/
|
||||
private static class HealthBaseline {
|
||||
public HealthType healthType;
|
||||
public int baseHeight;
|
||||
public int baseWeight;
|
||||
public String smokingTrend;
|
||||
public String drinkingTrend;
|
||||
public String primaryVulnerability;
|
||||
public String secondaryVulnerability;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🏃♂️ 건강 유형 열거형
|
||||
*/
|
||||
private enum HealthType {
|
||||
HEALTHY, // 건강형 - 전반적으로 좋은 수치
|
||||
AVERAGE, // 평균형 - 일반적인 수치
|
||||
AT_RISK // 주의형 - 일부 수치가 주의 범위
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.healthsync.health.service.Oauth;
|
||||
|
||||
import com.healthsync.health.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.health.service.Oauth;
|
||||
|
||||
import com.healthsync.health.domain.Oauth.RefreshToken;
|
||||
import com.healthsync.health.repository.entity.RefreshTokenEntity;
|
||||
import com.healthsync.health.repository.jpa.RefreshTokenRepository;
|
||||
import com.healthsync.health.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());
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.healthsync.health.service.UserProfile;
|
||||
|
||||
import com.healthsync.health.domain.Oauth.User;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserService {
|
||||
|
||||
// 기존 메서드들
|
||||
Optional<User> findById(Long memberSerialNumber);
|
||||
Optional<User> findByGoogleId(String googleId);
|
||||
User saveUser(User user);
|
||||
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);
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.healthsync.health.service.UserProfile;
|
||||
|
||||
import com.healthsync.health.domain.Oauth.User;
|
||||
import com.healthsync.health.repository.jpa.UserRepository;
|
||||
import com.healthsync.health.repository.jpa.OccupationTypeRepository;
|
||||
import com.healthsync.health.repository.entity.UserEntity;
|
||||
|
||||
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;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package com.healthsync.health.util;
|
||||
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckup;
|
||||
import com.healthsync.health.domain.HealthCheck.HealthCheckupRaw;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 건강검진 데이터 변환 유틸리티 클래스
|
||||
*/
|
||||
@Component
|
||||
public class HealthDataConverter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HealthDataConverter.class);
|
||||
|
||||
/**
|
||||
* 가공된 데이터를 Raw 형태로 변환 (응답 형태 유지를 위해)
|
||||
*/
|
||||
public HealthCheckupRaw convertToRawForResponse(HealthCheckup processed, List<HealthCheckupRaw> originalRawList) {
|
||||
if (processed == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 같은 raw_id의 원본 Raw 데이터 찾기
|
||||
Optional<HealthCheckupRaw> originalRaw = originalRawList.stream()
|
||||
.filter(raw -> raw.getRawId().equals(processed.getRawId()))
|
||||
.findFirst();
|
||||
|
||||
if (originalRaw.isPresent()) {
|
||||
logger.debug("원본 Raw 데이터 사용 - Raw ID: {}", processed.getRawId());
|
||||
return originalRaw.get();
|
||||
} else {
|
||||
logger.debug("원본 Raw 데이터 없음, 가공된 데이터로 생성 - Raw ID: {}", processed.getRawId());
|
||||
return createRawFromProcessed(processed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 가공된 데이터 리스트를 Raw 형태 리스트로 변환
|
||||
*/
|
||||
public List<HealthCheckupRaw> convertToRawListForResponse(List<HealthCheckup> processedList, List<HealthCheckupRaw> originalRawList) {
|
||||
if (processedList == null || processedList.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return processedList.stream()
|
||||
.map(processed -> convertToRawForResponse(processed, originalRawList))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 가공된 데이터로부터 Raw 형태 객체 생성 (fallback)
|
||||
*/
|
||||
private HealthCheckupRaw createRawFromProcessed(HealthCheckup processed) {
|
||||
HealthCheckupRaw raw = new HealthCheckupRaw();
|
||||
|
||||
// 기본 정보
|
||||
raw.setRawId(processed.getRawId());
|
||||
raw.setReferenceYear(processed.getReferenceYear());
|
||||
raw.setAge(processed.getAge());
|
||||
raw.setCreatedAt(processed.getCreatedAt());
|
||||
|
||||
// 신체 측정
|
||||
raw.setHeight(processed.getHeight());
|
||||
raw.setWeight(processed.getWeight());
|
||||
raw.setWaistCircumference(processed.getWaistCircumference());
|
||||
|
||||
// 시력/청력
|
||||
raw.setVisualAcuityLeft(processed.getVisualAcuityLeft());
|
||||
raw.setVisualAcuityRight(processed.getVisualAcuityRight());
|
||||
raw.setHearingLeft(processed.getHearingLeft());
|
||||
raw.setHearingRight(processed.getHearingRight());
|
||||
|
||||
// 혈압
|
||||
raw.setSystolicBp(processed.getSystolicBp());
|
||||
raw.setDiastolicBp(processed.getDiastolicBp());
|
||||
|
||||
// 혈액검사
|
||||
raw.setFastingGlucose(processed.getFastingGlucose());
|
||||
raw.setTotalCholesterol(processed.getTotalCholesterol());
|
||||
raw.setTriglyceride(processed.getTriglyceride());
|
||||
raw.setHdlCholesterol(processed.getHdlCholesterol());
|
||||
raw.setLdlCholesterol(processed.getLdlCholesterol());
|
||||
raw.setHemoglobin(processed.getHemoglobin());
|
||||
|
||||
// 소변/혈청
|
||||
raw.setUrineProtein(processed.getUrineProtein());
|
||||
raw.setSerumCreatinine(processed.getSerumCreatinine());
|
||||
|
||||
// 간기능
|
||||
raw.setAst(processed.getAst());
|
||||
raw.setAlt(processed.getAlt());
|
||||
raw.setGammaGtp(processed.getGammaGtp());
|
||||
|
||||
// 생활습관
|
||||
raw.setSmokingStatus(processed.getSmokingStatus());
|
||||
raw.setDrinkingStatus(processed.getDrinkingStatus());
|
||||
|
||||
// 가공된 데이터에는 없는 정보들은 기본값 설정
|
||||
raw.setName(""); // 빈 문자열로 설정
|
||||
raw.setBirthDate(LocalDate.of(1000, 1, 1)); // 기본값
|
||||
raw.setRegionCode(null);
|
||||
raw.setGenderCode(null); // 성별 정보는 별도로 처리
|
||||
|
||||
logger.debug("가공된 데이터로부터 Raw 객체 생성 완료 - Raw ID: {}, Reference Year: {}",
|
||||
raw.getRawId(), raw.getReferenceYear());
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 성별 코드를 문자열로 변환
|
||||
*/
|
||||
public String convertGenderCodeToString(Integer genderCode) {
|
||||
if (genderCode == null) {
|
||||
return "정보 없음";
|
||||
}
|
||||
|
||||
switch (genderCode) {
|
||||
case 1:
|
||||
return "남성";
|
||||
case 2:
|
||||
return "여성";
|
||||
default:
|
||||
return "정보 없음";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 성별 문자열을 코드로 변환
|
||||
*/
|
||||
public Integer convertGenderStringToCode(String gender) {
|
||||
if (gender == null || gender.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (gender.trim()) {
|
||||
case "남성":
|
||||
case "M":
|
||||
case "MALE":
|
||||
return 1;
|
||||
case "여성":
|
||||
case "F":
|
||||
case "FEMALE":
|
||||
return 2;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw 데이터 리스트에서 특정 연도의 데이터 찾기
|
||||
*/
|
||||
public Optional<HealthCheckupRaw> findRawByYear(List<HealthCheckupRaw> rawList, Integer year) {
|
||||
if (rawList == null || rawList.isEmpty() || year == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return rawList.stream()
|
||||
.filter(raw -> year.equals(raw.getReferenceYear()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw 데이터 리스트에서 특정 Raw ID의 데이터 찾기
|
||||
*/
|
||||
public Optional<HealthCheckupRaw> findRawById(List<HealthCheckupRaw> rawList, Long rawId) {
|
||||
if (rawList == null || rawList.isEmpty() || rawId == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return rawList.stream()
|
||||
.filter(raw -> rawId.equals(raw.getRawId()))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
spring:
|
||||
application:
|
||||
name: health-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
|
||||
|
||||
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
|
||||
server:
|
||||
port: ${SERVER_PORT:8082}
|
||||
|
||||
# 외부 서비스 URL
|
||||
services:
|
||||
user-service:
|
||||
url: ${USER_SERVICE_URL:http://user-service:8081}
|
||||
intelligence-service:
|
||||
url: ${INTELLIGENCE_SERVICE_URL:http://intelligence-service:8083}
|
||||
|
||||
# Azure Blob Storage 설정 (건강검진 파일 업로드용)
|
||||
azure:
|
||||
storage:
|
||||
account-name: ${AZURE_STORAGE_ACCOUNT:healthsyncstorage}
|
||||
account-key: ${AZURE_STORAGE_KEY:your-storage-key}
|
||||
container-name: ${AZURE_STORAGE_CONTAINER:health-documents}
|
||||
connection-string: ${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=https;AccountName=healthsyncstorage;AccountKey=ceMrDkY+cD4OPiS812JIRZcwF/Re5lJGJUO58gue1LBHxlzRhbD6OpDR85zDs5hZTnFooWlhZACZ+AStJO9dMQ==}
|
||||
|
||||
# 건강검진 데이터 처리 설정
|
||||
# Health Service Specific Configuration
|
||||
health:
|
||||
# Mock 데이터 생성 설정
|
||||
mock:
|
||||
enabled: ${HEALTH_MOCK_ENABLED:true} # 환경변수로 제어 가능
|
||||
auto-save-to-db: ${HEALTH_MOCK_AUTO_SAVE:true} # Mock 데이터 자동 DB 저장 여부
|
||||
|
||||
# Mock 데이터 생성 시 사용할 기본 설정
|
||||
defaults:
|
||||
gender-code: 1 # 기본 성별 (1: 남성, 2: 여성)
|
||||
age-range:
|
||||
min: 20
|
||||
max: 70
|
||||
region-codes: [11, 26, 27, 28, 29, 30, 31, 36, 41, 42, 43, 44, 45, 46, 47, 48, 50]
|
||||
|
||||
# Mock 데이터 품질 설정
|
||||
quality:
|
||||
realistic-ranges: true # 현실적인 수치 범위 사용
|
||||
age-based-risks: true # 연령대별 위험 요소 적용
|
||||
gender-based-norms: true # 성별별 정상 범위 적용
|
||||
seasonal-variation: false # 계절별 변동 (추후 구현)
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
resource server:
|
||||
jwt:
|
||||
issuer-uri: "http://team1tier.20.214.196.128.nip.io"
|
||||
|
||||
# JWT 설정
|
||||
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
|
||||
|
||||
|
||||
# 로깅 설정
|
||||
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 메시지)
|
||||
|
||||
# Management endpoints
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
Reference in New Issue
Block a user