From bf41fcb67c98f4a0feebf90e4245168ec2448c94 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 11 Mar 2026 21:45:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EC=8B=9C=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=20=EB=94=94=ED=8F=B4=ED=8A=B8=20=EA=B0=92=20=EB=84=A3?= =?UTF-8?q?=EC=96=B4=EC=A4=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/flipnote/user/user/domain/User.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/flipnote/user/user/domain/User.java b/src/main/java/flipnote/user/user/domain/User.java index ee76858..411549a 100644 --- a/src/main/java/flipnote/user/user/domain/User.java +++ b/src/main/java/flipnote/user/user/domain/User.java @@ -15,6 +15,9 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User extends BaseEntity { + private static final String DEFAULT_PROFILE_IMAGE_URL = + "https://flipnote-bucket.s3.ap-northeast-2.amazonaws.com/image/default/user.png"; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -54,6 +57,7 @@ public User(String email, String password, String name, String nickname, String this.password = password; this.name = name; this.nickname = nickname; + this.profileImageUrl = DEFAULT_PROFILE_IMAGE_URL; this.phone = phone; this.smsAgree = smsAgree; this.role = role != null ? role : Role.USER; From 18378b27558f36d087730d911b08ee148432ba32 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 11 Mar 2026 21:56:16 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/global/config/GrpcClientConfig.java | 16 +++ .../user/user/application/UserService.java | 100 +++++++++++------- .../dto/response/UserUpdateResponse.java | 14 +-- src/main/proto/image.proto | 57 ++++++++++ src/main/resources/application.yml | 6 +- 5 files changed, 149 insertions(+), 44 deletions(-) create mode 100644 src/main/java/flipnote/user/global/config/GrpcClientConfig.java create mode 100644 src/main/proto/image.proto diff --git a/src/main/java/flipnote/user/global/config/GrpcClientConfig.java b/src/main/java/flipnote/user/global/config/GrpcClientConfig.java new file mode 100644 index 0000000..2b5eed3 --- /dev/null +++ b/src/main/java/flipnote/user/global/config/GrpcClientConfig.java @@ -0,0 +1,16 @@ +package flipnote.user.global.config; + +import flipnote.image.grpc.v1.ImageCommandServiceGrpc; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.grpc.client.GrpcChannelFactory; + +@Configuration +public class GrpcClientConfig { + + @Bean + ImageCommandServiceGrpc.ImageCommandServiceBlockingStub imageCommandServiceBlockingStub( + GrpcChannelFactory channelFactory) { + return ImageCommandServiceGrpc.newBlockingStub(channelFactory.createChannel("image-service")); + } +} diff --git a/src/main/java/flipnote/user/user/application/UserService.java b/src/main/java/flipnote/user/user/application/UserService.java index 0461861..320ed1f 100644 --- a/src/main/java/flipnote/user/user/application/UserService.java +++ b/src/main/java/flipnote/user/user/application/UserService.java @@ -1,5 +1,14 @@ package flipnote.user.user.application; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import flipnote.image.grpc.v1.ChangeImageRequest; +import flipnote.image.grpc.v1.ChangeImageResponse; +import flipnote.image.grpc.v1.GetUrlByReferenceRequest; +import flipnote.image.grpc.v1.GetUrlByReferenceResponse; +import flipnote.image.grpc.v1.ImageCommandServiceGrpc; +import flipnote.image.grpc.v1.Type; import flipnote.user.auth.infrastructure.jwt.JwtProvider; import flipnote.user.auth.infrastructure.redis.SessionInvalidationRepository; import flipnote.user.global.exception.UserException; @@ -11,46 +20,65 @@ import flipnote.user.user.presentation.dto.response.UserInfoResponse; import flipnote.user.user.presentation.dto.response.UserUpdateResponse; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class UserService { - private final UserRepository userRepository; - private final SessionInvalidationRepository sessionInvalidationRepository; - private final JwtProvider jwtProvider; - - public MyInfoResponse getMyInfo(Long userId) { - User user = findActiveUser(userId); - return MyInfoResponse.from(user); - } - - public UserInfoResponse getUserInfo(Long userId) { - User user = userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); - return UserInfoResponse.from(user); - } - - @Transactional - public UserUpdateResponse updateProfile(Long userId, UpdateProfileRequest request) { - User user = findActiveUser(userId); - user.updateProfile(request.getNickname(), request.getPhone(), - Boolean.TRUE.equals(request.getSmsAgree()), null); - return UserUpdateResponse.from(user); - } - - @Transactional - public void withdraw(Long userId) { - User user = findActiveUser(userId); - user.withdraw(); - sessionInvalidationRepository.invalidate(userId, jwtProvider.getRefreshTokenExpiration()); - } - - private User findActiveUser(Long userId) { - return userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); - } + private final UserRepository userRepository; + private final SessionInvalidationRepository sessionInvalidationRepository; + private final JwtProvider jwtProvider; + private final ImageCommandServiceGrpc.ImageCommandServiceBlockingStub imageCommandServiceStub; + + public MyInfoResponse getMyInfo(Long userId) { + User user = findActiveUser(userId); + return MyInfoResponse.from(user); + } + + public UserInfoResponse getUserInfo(Long userId) { + User user = userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) + .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + return UserInfoResponse.from(user); + } + + @Transactional + public UserUpdateResponse updateProfile(Long userId, UpdateProfileRequest request) { + User user = findActiveUser(userId); + + String profileImageUrl = null; + Long imageRefId = null; + if (request.getImageRefId() != null) { + ChangeImageResponse changeImageResponse = imageCommandServiceStub.changeImage( + ChangeImageRequest.newBuilder() + .setReferenceType(Type.USER) + .setReferenceId(userId) + .setImageRefId(request.getImageRefId()) + .build()); + + GetUrlByReferenceResponse urlResponse = imageCommandServiceStub.getUrlByReference( + GetUrlByReferenceRequest.newBuilder() + .setReferenceType(Type.USER) + .setReferenceId(userId) + .build()); + + profileImageUrl = urlResponse.getImageUrl(); + imageRefId = changeImageResponse.getImageRefId(); + } + + user.updateProfile(request.getNickname(), request.getPhone(), request.getSmsAgree(), profileImageUrl); + return UserUpdateResponse.from(user, imageRefId); + } + + @Transactional + public void withdraw(Long userId) { + User user = findActiveUser(userId); + user.withdraw(); + sessionInvalidationRepository.invalidate(userId, jwtProvider.getRefreshTokenExpiration()); + } + + private User findActiveUser(Long userId) { + return userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) + .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + } } diff --git a/src/main/java/flipnote/user/user/presentation/dto/response/UserUpdateResponse.java b/src/main/java/flipnote/user/user/presentation/dto/response/UserUpdateResponse.java index 05d24b8..1880087 100644 --- a/src/main/java/flipnote/user/user/presentation/dto/response/UserUpdateResponse.java +++ b/src/main/java/flipnote/user/user/presentation/dto/response/UserUpdateResponse.java @@ -15,14 +15,14 @@ public class UserUpdateResponse { private String profileImageUrl; private Long imageRefId; - public static UserUpdateResponse from(User user) { + public static UserUpdateResponse from(User user, Long imageRefId) { return new UserUpdateResponse( - user.getId(), - user.getNickname(), - user.getPhone(), - user.isSmsAgree(), - user.getProfileImageUrl(), - null + user.getId(), + user.getNickname(), + user.getPhone(), + user.isSmsAgree(), + user.getProfileImageUrl(), + imageRefId ); } } diff --git a/src/main/proto/image.proto b/src/main/proto/image.proto new file mode 100644 index 0000000..81d2300 --- /dev/null +++ b/src/main/proto/image.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +// 논리적 패키지 API 관점 +package image.v1; + +// Java 옵션 설정 +option java_multiple_files = true; +option java_package = "flipnote.image.grpc.v1"; // Java 패키지 경로 설정 +option java_outer_classname = "ImageServiceProto"; // 외부 클래스 이름 설정 + +// gRPC 서비스 정의 +service ImageCommandService { + // 이미지 url 조회 + rpc GetUrlByReference(GetUrlByReferenceRequest) returns (GetUrlByReferenceResponse); + // 이미지 활성화 + rpc ActivateImage(ActivateImageRequest) returns (ActivateImageResponse); + // 이미지 변경 + rpc ChangeImage(ChangeImageRequest) returns (ChangeImageResponse); +} + +// 요청/응답 메시지 +enum Type { + REFERENCE_TYPE_UNSPECIFIED = 0; + USER = 1; + GROUP = 2; + CARD_SET = 3; +} + +message GetUrlByReferenceRequest { + Type reference_type = 1; + int64 reference_id = 2; +} + +message GetUrlByReferenceResponse { + string image_url = 1; +} + +message ActivateImageRequest { + int64 image_ref_id = 1; + Type reference_type = 2; + int64 reference_id = 3; +} + +message ActivateImageResponse { +// int64 image_ref_id = 1; +} + +message ChangeImageRequest { + Type reference_type = 1; + int64 reference_id = 2; + int64 image_ref_id = 3; +} + +message ChangeImageResponse { + int64 image_ref_id = 1; + string url = 2; +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4a48b13..d7455e0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,7 +25,11 @@ spring: grpc: server: - port: ${GRPC_PORT:9091} + port: ${GRPC_PORT:9092} + client: + channels: + image-service: + address: static://image-service:9092 server: port: 8081 From d703857d1fe35ba1497295df42060706aaa8b728 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 11 Mar 2026 22:53:07 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EB=AC=B8=EC=A0=9C=EC=8B=9C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/auth/application/AuthService.java | 40 +++++++++---------- .../user/auth/application/OAuthService.java | 14 +++---- .../auth/presentation/OAuthController.java | 4 +- .../global/error/GlobalExceptionHandler.java | 12 ++++-- .../user/global/error/ImageErrorCode.java | 21 ++++++++++ .../user/global/exception/BizException.java | 12 ++++++ .../user/global/exception/UserException.java | 15 ------- .../user/user/application/UserService.java | 37 +++++++++-------- 8 files changed, 91 insertions(+), 64 deletions(-) create mode 100644 src/main/java/flipnote/user/global/error/ImageErrorCode.java create mode 100644 src/main/java/flipnote/user/global/exception/BizException.java delete mode 100644 src/main/java/flipnote/user/global/exception/UserException.java diff --git a/src/main/java/flipnote/user/auth/application/AuthService.java b/src/main/java/flipnote/user/auth/application/AuthService.java index 12a5181..0b81523 100644 --- a/src/main/java/flipnote/user/auth/application/AuthService.java +++ b/src/main/java/flipnote/user/auth/application/AuthService.java @@ -19,7 +19,7 @@ import flipnote.user.auth.presentation.dto.response.TokenValidateResponse; import flipnote.user.auth.presentation.dto.response.UserResponse; import flipnote.user.global.config.ClientProperties; -import flipnote.user.global.exception.UserException; +import flipnote.user.global.exception.BizException; import flipnote.user.user.domain.OAuthLink; import flipnote.user.user.domain.OAuthLinkRepository; import flipnote.user.user.domain.User; @@ -54,11 +54,11 @@ public class AuthService { @Transactional public UserResponse register(SignupRequest request) { if (!emailVerificationRepository.isVerified(request.getEmail())) { - throw new UserException(AuthErrorCode.UNVERIFIED_EMAIL); + throw new BizException(AuthErrorCode.UNVERIFIED_EMAIL); } if (userRepository.existsByEmail(request.getEmail())) { - throw new UserException(AuthErrorCode.EMAIL_ALREADY_EXISTS); + throw new BizException(AuthErrorCode.EMAIL_ALREADY_EXISTS); } User user = User.builder() @@ -76,10 +76,10 @@ public UserResponse register(SignupRequest request) { public TokenPair login(LoginRequest request) { User user = userRepository.findByEmailAndStatus(request.getEmail(), User.Status.ACTIVE) - .orElseThrow(() -> new UserException(AuthErrorCode.INVALID_CREDENTIALS)); + .orElseThrow(() -> new BizException(AuthErrorCode.INVALID_CREDENTIALS)); if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new UserException(AuthErrorCode.INVALID_CREDENTIALS); + throw new BizException(AuthErrorCode.INVALID_CREDENTIALS); } return jwtProvider.generateTokenPair(user); @@ -96,18 +96,18 @@ public void logout(String refreshToken) { public TokenPair refreshToken(String refreshToken) { if (refreshToken == null || !jwtProvider.isTokenValid(refreshToken)) { - throw new UserException(AuthErrorCode.INVALID_TOKEN); + throw new BizException(AuthErrorCode.INVALID_TOKEN); } if (tokenBlacklistRepository.isBlacklisted(refreshToken)) { - throw new UserException(AuthErrorCode.BLACKLISTED_TOKEN); + throw new BizException(AuthErrorCode.BLACKLISTED_TOKEN); } TokenClaims claims = jwtProvider.extractClaims(refreshToken); sessionInvalidationRepository.getInvalidatedAtMillis(claims.userId()).ifPresent(invalidatedAtMillis -> { if (jwtProvider.getIssuedAt(refreshToken).getTime() < invalidatedAtMillis) { - throw new UserException(AuthErrorCode.INVALIDATED_SESSION); + throw new BizException(AuthErrorCode.INVALIDATED_SESSION); } }); @@ -126,7 +126,7 @@ public void changePassword(Long userId, ChangePasswordRequest request) { User user = findActiveUser(userId); if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { - throw new UserException(AuthErrorCode.PASSWORD_MISMATCH); + throw new BizException(AuthErrorCode.PASSWORD_MISMATCH); } user.changePassword(passwordEncoder.encode(request.getNewPassword())); @@ -135,18 +135,18 @@ public void changePassword(Long userId, ChangePasswordRequest request) { public TokenValidateResponse validateToken(String token) { if (!jwtProvider.isTokenValid(token)) { - throw new UserException(AuthErrorCode.INVALID_TOKEN); + throw new BizException(AuthErrorCode.INVALID_TOKEN); } if (tokenBlacklistRepository.isBlacklisted(token)) { - throw new UserException(AuthErrorCode.BLACKLISTED_TOKEN); + throw new BizException(AuthErrorCode.BLACKLISTED_TOKEN); } TokenClaims claims = jwtProvider.extractClaims(token); sessionInvalidationRepository.getInvalidatedAtMillis(claims.userId()).ifPresent(invalidatedAtMillis -> { if (jwtProvider.getIssuedAt(token).getTime() < invalidatedAtMillis) { - throw new UserException(AuthErrorCode.INVALIDATED_SESSION); + throw new BizException(AuthErrorCode.INVALIDATED_SESSION); } }); @@ -157,7 +157,7 @@ public TokenValidateResponse validateToken(String token) { public void sendEmailVerificationCode(String email) { if (emailVerificationRepository.hasCode(email)) { - throw new UserException(AuthErrorCode.ALREADY_ISSUED_VERIFICATION_CODE); + throw new BizException(AuthErrorCode.ALREADY_ISSUED_VERIFICATION_CODE); } String code = verificationCodeGenerator.generate(); @@ -167,12 +167,12 @@ public void sendEmailVerificationCode(String email) { public void verifyEmail(String email, String code) { if (!emailVerificationRepository.hasCode(email)) { - throw new UserException(AuthErrorCode.NOT_ISSUED_VERIFICATION_CODE); + throw new BizException(AuthErrorCode.NOT_ISSUED_VERIFICATION_CODE); } String savedCode = emailVerificationRepository.getCode(email); if (!code.equals(savedCode)) { - throw new UserException(AuthErrorCode.INVALID_VERIFICATION_CODE); + throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE); } emailVerificationRepository.deleteCode(email); @@ -186,7 +186,7 @@ public void requestPasswordReset(String email) { } if (passwordResetRepository.hasToken(email)) { - throw new UserException(AuthErrorCode.ALREADY_SENT_PASSWORD_RESET_LINK); + throw new BizException(AuthErrorCode.ALREADY_SENT_PASSWORD_RESET_LINK); } String token = passwordResetTokenGenerator.generate(); @@ -201,11 +201,11 @@ public void requestPasswordReset(String email) { public void resetPassword(String token, String newPassword) { String email = passwordResetRepository.findEmailByToken(token); if (email == null) { - throw new UserException(AuthErrorCode.INVALID_PASSWORD_RESET_TOKEN); + throw new BizException(AuthErrorCode.INVALID_PASSWORD_RESET_TOKEN); } User user = userRepository.findByEmailAndStatus(email, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); user.changePassword(passwordEncoder.encode(newPassword)); sessionInvalidationRepository.invalidate(user.getId(), jwtProvider.getRefreshTokenExpiration()); @@ -220,13 +220,13 @@ public SocialLinksResponse getSocialLinks(Long userId) { @Transactional public void deleteSocialLink(Long userId, Long socialLinkId) { if (!oAuthLinkRepository.existsByIdAndUser_Id(socialLinkId, userId)) { - throw new UserException(AuthErrorCode.NOT_REGISTERED_SOCIAL_ACCOUNT); + throw new BizException(AuthErrorCode.NOT_REGISTERED_SOCIAL_ACCOUNT); } oAuthLinkRepository.deleteById(socialLinkId); } private User findActiveUser(Long userId) { return userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); } } diff --git a/src/main/java/flipnote/user/auth/application/OAuthService.java b/src/main/java/flipnote/user/auth/application/OAuthService.java index 3a6660b..d888a97 100644 --- a/src/main/java/flipnote/user/auth/application/OAuthService.java +++ b/src/main/java/flipnote/user/auth/application/OAuthService.java @@ -9,7 +9,7 @@ import flipnote.user.auth.infrastructure.redis.SocialLinkTokenRepository; import flipnote.user.global.config.OAuthProperties; import flipnote.user.global.constants.HttpConstants; -import flipnote.user.global.exception.UserException; +import flipnote.user.global.exception.BizException; import flipnote.user.user.domain.OAuthLink; import flipnote.user.user.domain.OAuthLinkRepository; import flipnote.user.user.domain.User; @@ -75,7 +75,7 @@ public TokenPair socialLogin(String providerName, String code, String codeVerifi OAuthLink oAuthLink = oAuthLinkRepository .findByProviderAndProviderIdWithUser(userInfo.getProvider(), userInfo.getProviderId()) - .orElseThrow(() -> new UserException(AuthErrorCode.NOT_REGISTERED_SOCIAL_ACCOUNT)); + .orElseThrow(() -> new BizException(AuthErrorCode.NOT_REGISTERED_SOCIAL_ACCOUNT)); return jwtProvider.generateTokenPair(oAuthLink.getUser()); } @@ -84,7 +84,7 @@ public TokenPair socialLogin(String providerName, String code, String codeVerifi public void linkSocialAccount(String providerName, String code, String state, String codeVerifier, HttpServletRequest request) { Long userId = socialLinkTokenRepository.findUserIdByState(state) - .orElseThrow(() -> new UserException(AuthErrorCode.INVALID_SOCIAL_LINK_TOKEN)); + .orElseThrow(() -> new BizException(AuthErrorCode.INVALID_SOCIAL_LINK_TOKEN)); socialLinkTokenRepository.delete(state); @@ -92,11 +92,11 @@ public void linkSocialAccount(String providerName, String code, String state, if (oAuthLinkRepository.existsByUser_IdAndProviderAndProviderId( userId, userInfo.getProvider(), userInfo.getProviderId())) { - throw new UserException(AuthErrorCode.ALREADY_LINKED_SOCIAL_ACCOUNT); + throw new BizException(AuthErrorCode.ALREADY_LINKED_SOCIAL_ACCOUNT); } User user = userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); OAuthLink link = OAuthLink.builder() .provider(userInfo.getProvider()) @@ -117,12 +117,12 @@ private OAuth2UserInfo getOAuth2UserInfo(String providerName, String code, private OAuthProperties.Provider resolveProvider(String providerName) { Map providers = oAuthProperties.getProviders(); if (providers == null) { - throw new UserException(AuthErrorCode.INVALID_OAUTH_PROVIDER); + throw new BizException(AuthErrorCode.INVALID_OAUTH_PROVIDER); } OAuthProperties.Provider provider = providers.get(providerName.toLowerCase()); if (provider == null) { log.warn("지원하지 않는 OAuth Provider: {}", providerName); - throw new UserException(AuthErrorCode.INVALID_OAUTH_PROVIDER); + throw new BizException(AuthErrorCode.INVALID_OAUTH_PROVIDER); } return provider; } diff --git a/src/main/java/flipnote/user/auth/presentation/OAuthController.java b/src/main/java/flipnote/user/auth/presentation/OAuthController.java index 6f787ed..34ec294 100644 --- a/src/main/java/flipnote/user/auth/presentation/OAuthController.java +++ b/src/main/java/flipnote/user/auth/presentation/OAuthController.java @@ -2,7 +2,7 @@ import flipnote.user.auth.application.OAuthService; import flipnote.user.auth.domain.AuthErrorCode; -import flipnote.user.global.exception.UserException; +import flipnote.user.global.exception.BizException; import flipnote.user.auth.domain.TokenPair; import flipnote.user.global.config.ClientProperties; import flipnote.user.global.constants.HttpConstants; @@ -86,7 +86,7 @@ private ResponseEntity handleSocialLink(String provider, String code, Stri return ResponseEntity.status(HttpStatus.FOUND) .location(URI.create(clientProperties.getUrl() + clientProperties.getPaths().getSocialLinkSuccess())) .build(); - } catch (UserException e) { + } catch (BizException e) { log.warn("소셜 계정 연동 처리 실패. provider: {}", provider, e); if (e.getErrorCode() == AuthErrorCode.ALREADY_LINKED_SOCIAL_ACCOUNT) { return ResponseEntity.status(HttpStatus.FOUND) diff --git a/src/main/java/flipnote/user/global/error/GlobalExceptionHandler.java b/src/main/java/flipnote/user/global/error/GlobalExceptionHandler.java index 7150a0b..5d4d142 100644 --- a/src/main/java/flipnote/user/global/error/GlobalExceptionHandler.java +++ b/src/main/java/flipnote/user/global/error/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ package flipnote.user.global.error; -import flipnote.user.global.exception.UserException; +import flipnote.user.global.exception.BizException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -15,9 +15,13 @@ @RestControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(UserException.class) - public ResponseEntity> handleUserException(UserException e) { - log.warn("UserException: {}", e.getMessage()); + @ExceptionHandler(BizException.class) + public ResponseEntity> handleBizException(BizException e) { + log.warn("BizException: code={}, status={}, message={}", + e.getErrorCode().getCode(), + e.getErrorCode().getStatus(), + e.getErrorCode().getMessage() + ); return ResponseEntity.status(e.getErrorCode().getStatus()).body(ApiResponse.error(e.getErrorCode())); } diff --git a/src/main/java/flipnote/user/global/error/ImageErrorCode.java b/src/main/java/flipnote/user/global/error/ImageErrorCode.java new file mode 100644 index 0000000..16fc7b1 --- /dev/null +++ b/src/main/java/flipnote/user/global/error/ImageErrorCode.java @@ -0,0 +1,21 @@ +package flipnote.user.global.error; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ImageErrorCode implements ErrorCode { + + IMAGE_SERVICE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "IMAGE_001", "이미지 서비스 처리 중 오류가 발생했습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public int getStatus() { + return httpStatus.value(); + } +} diff --git a/src/main/java/flipnote/user/global/exception/BizException.java b/src/main/java/flipnote/user/global/exception/BizException.java new file mode 100644 index 0000000..f31adef --- /dev/null +++ b/src/main/java/flipnote/user/global/exception/BizException.java @@ -0,0 +1,12 @@ +package flipnote.user.global.exception; + +import flipnote.user.global.error.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class BizException extends RuntimeException { + + private ErrorCode errorCode; +} diff --git a/src/main/java/flipnote/user/global/exception/UserException.java b/src/main/java/flipnote/user/global/exception/UserException.java deleted file mode 100644 index b5a1bd8..0000000 --- a/src/main/java/flipnote/user/global/exception/UserException.java +++ /dev/null @@ -1,15 +0,0 @@ -package flipnote.user.global.exception; - -import flipnote.user.global.error.ErrorCode; -import lombok.Getter; - -@Getter -public class UserException extends RuntimeException { - - private final ErrorCode errorCode; - - public UserException(ErrorCode errorCode) { - super(errorCode.getMessage()); - this.errorCode = errorCode; - } -} diff --git a/src/main/java/flipnote/user/user/application/UserService.java b/src/main/java/flipnote/user/user/application/UserService.java index 320ed1f..aad1107 100644 --- a/src/main/java/flipnote/user/user/application/UserService.java +++ b/src/main/java/flipnote/user/user/application/UserService.java @@ -11,7 +11,8 @@ import flipnote.image.grpc.v1.Type; import flipnote.user.auth.infrastructure.jwt.JwtProvider; import flipnote.user.auth.infrastructure.redis.SessionInvalidationRepository; -import flipnote.user.global.exception.UserException; +import flipnote.user.global.error.ImageErrorCode; +import flipnote.user.global.exception.BizException; import flipnote.user.user.domain.User; import flipnote.user.user.domain.UserErrorCode; import flipnote.user.user.domain.UserRepository; @@ -38,7 +39,7 @@ public MyInfoResponse getMyInfo(Long userId) { public UserInfoResponse getUserInfo(Long userId) { User user = userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); return UserInfoResponse.from(user); } @@ -49,21 +50,25 @@ public UserUpdateResponse updateProfile(Long userId, UpdateProfileRequest reques String profileImageUrl = null; Long imageRefId = null; if (request.getImageRefId() != null) { - ChangeImageResponse changeImageResponse = imageCommandServiceStub.changeImage( - ChangeImageRequest.newBuilder() - .setReferenceType(Type.USER) - .setReferenceId(userId) - .setImageRefId(request.getImageRefId()) - .build()); + try { + ChangeImageResponse changeImageResponse = imageCommandServiceStub.changeImage( + ChangeImageRequest.newBuilder() + .setReferenceType(Type.USER) + .setReferenceId(userId) + .setImageRefId(request.getImageRefId()) + .build()); - GetUrlByReferenceResponse urlResponse = imageCommandServiceStub.getUrlByReference( - GetUrlByReferenceRequest.newBuilder() - .setReferenceType(Type.USER) - .setReferenceId(userId) - .build()); + GetUrlByReferenceResponse urlResponse = imageCommandServiceStub.getUrlByReference( + GetUrlByReferenceRequest.newBuilder() + .setReferenceType(Type.USER) + .setReferenceId(userId) + .build()); - profileImageUrl = urlResponse.getImageUrl(); - imageRefId = changeImageResponse.getImageRefId(); + profileImageUrl = urlResponse.getImageUrl(); + imageRefId = changeImageResponse.getImageRefId(); + } catch (Exception ex) { + throw new BizException(ImageErrorCode.IMAGE_SERVICE_ERROR); + } } user.updateProfile(request.getNickname(), request.getPhone(), request.getSmsAgree(), profileImageUrl); @@ -79,6 +84,6 @@ public void withdraw(Long userId) { private User findActiveUser(Long userId) { return userRepository.findByIdAndStatus(userId, User.Status.ACTIVE) - .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); + .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); } }