diff --git a/user-service/build.gradle.kts b/user-service/build.gradle.kts index cecff71..60e0093 100644 --- a/user-service/build.gradle.kts +++ b/user-service/build.gradle.kts @@ -31,6 +31,8 @@ dependencies { testImplementation("io.projectreactor:reactor-test") testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + implementation("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") } tasks.withType { diff --git a/user-service/src/main/java/kr/mafoo/user/annotation/RequestMemberId.java b/user-service/src/main/java/kr/mafoo/user/annotation/RequestMemberId.java new file mode 100644 index 0000000..a7c9843 --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/annotation/RequestMemberId.java @@ -0,0 +1,11 @@ +package kr.mafoo.user.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.TYPE_PARAMETER}) +public @interface RequestMemberId { +} diff --git a/user-service/src/main/java/kr/mafoo/user/api/MeApi.java b/user-service/src/main/java/kr/mafoo/user/api/MeApi.java index 64f451a..92ade94 100644 --- a/user-service/src/main/java/kr/mafoo/user/api/MeApi.java +++ b/user-service/src/main/java/kr/mafoo/user/api/MeApi.java @@ -1,10 +1,12 @@ package kr.mafoo.user.api; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import kr.mafoo.user.annotation.RequestMemberId; import kr.mafoo.user.controller.dto.response.MemberResponse; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import reactor.core.publisher.Mono; @@ -13,9 +15,13 @@ public interface MeApi { @Operation(summary = "내 정보 조회", description = "현재 토큰 주인의 정보를 조회합니다.") @GetMapping - Mono getMemberWhoRequested(); + Mono getMemberWhoRequested( + @RequestMemberId @Parameter(hidden = true) String memberId + ); @Operation(summary = "탈퇴", description = "현재 토큰 주인이 탈퇴합니다.") - @PostMapping("/quit") - Mono deleteMemberWhoRequested(); + @DeleteMapping + Mono deleteMemberWhoRequested( + @RequestMemberId @Parameter(hidden = true) String memberId + ); } diff --git a/user-service/src/main/java/kr/mafoo/user/config/MemberIdParameterResolver.java b/user-service/src/main/java/kr/mafoo/user/config/MemberIdParameterResolver.java new file mode 100644 index 0000000..856e51d --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/config/MemberIdParameterResolver.java @@ -0,0 +1,22 @@ +package kr.mafoo.user.config; + + +import kr.mafoo.user.annotation.RequestMemberId; +import org.springframework.core.MethodParameter; +import org.springframework.web.reactive.BindingContext; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class MemberIdParameterResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterAnnotation(RequestMemberId.class) != null; + } + + @Override + public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { + String memberId = exchange.getRequest().getHeaders().getFirst("X-MEMBER-ID"); + return Mono.justOrEmpty(memberId); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/config/WebExceptionHandler.java b/user-service/src/main/java/kr/mafoo/user/config/WebExceptionHandler.java new file mode 100644 index 0000000..b5fddc1 --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/config/WebExceptionHandler.java @@ -0,0 +1,17 @@ +package kr.mafoo.user.config; + +import kr.mafoo.user.controller.dto.response.ErrorResponse; +import kr.mafoo.user.exception.DomainException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class WebExceptionHandler { + @ExceptionHandler(DomainException.class) + public ResponseEntity handleDomainException(DomainException exception) { + return ResponseEntity + .badRequest() + .body(ErrorResponse.fromErrorCode(exception.getErrorCode())); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/config/WebFluxConfig.java b/user-service/src/main/java/kr/mafoo/user/config/WebFluxConfig.java new file mode 100644 index 0000000..449f4ca --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/config/WebFluxConfig.java @@ -0,0 +1,15 @@ +package kr.mafoo.user.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; + +@EnableWebFlux +@Configuration +public class WebFluxConfig implements WebFluxConfigurer { + @Override + public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { + configurer.addCustomResolver(new MemberIdParameterResolver()); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/controller/MeController.java b/user-service/src/main/java/kr/mafoo/user/controller/MeController.java index fbd375d..2cfb6dd 100644 --- a/user-service/src/main/java/kr/mafoo/user/controller/MeController.java +++ b/user-service/src/main/java/kr/mafoo/user/controller/MeController.java @@ -2,18 +2,26 @@ import kr.mafoo.user.api.MeApi; import kr.mafoo.user.controller.dto.response.MemberResponse; +import kr.mafoo.user.service.MemberService; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; +@RequiredArgsConstructor @RestController public class MeController implements MeApi { + private final MemberService memberService; + @Override - public Mono getMemberWhoRequested() { - return Mono.just(new MemberResponse("test", "송영민")); + public Mono getMemberWhoRequested(String memberId) { + return memberService + .getMemberByMemberId(memberId) + .map(MemberResponse::fromEntity); } @Override - public Mono deleteMemberWhoRequested() { - return Mono.empty(); + public Mono deleteMemberWhoRequested(String memberId) { + return memberService + .quitMemberByMemberId(memberId); } } diff --git a/user-service/src/main/java/kr/mafoo/user/controller/dto/response/ErrorResponse.java b/user-service/src/main/java/kr/mafoo/user/controller/dto/response/ErrorResponse.java new file mode 100644 index 0000000..d8b6d4c --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/controller/dto/response/ErrorResponse.java @@ -0,0 +1,20 @@ +package kr.mafoo.user.controller.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.user.exception.ErrorCode; + +@Schema(description = "에러 응답") +public record ErrorResponse( + @Schema(description = "에러 코드", example = "ME0001") + String code, + + @Schema(description = "에러 메시지", example = "사용자를 찾을 수 없습니다") + String message +) { + public static ErrorResponse fromErrorCode(ErrorCode errorCode) { + return new ErrorResponse( + errorCode.getCode(), + errorCode.getMessage() + ); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java b/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java index f75d5c8..a7a239d 100644 --- a/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java +++ b/user-service/src/main/java/kr/mafoo/user/controller/dto/response/MemberResponse.java @@ -1,6 +1,7 @@ package kr.mafoo.user.controller.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import kr.mafoo.user.domain.MemberEntity; @Schema(description = "사용자 정보 응답") public record MemberResponse( @@ -10,4 +11,10 @@ public record MemberResponse( @Schema(description = "사용자 이름", example = "송영민") String name ) { + public static MemberResponse fromEntity(MemberEntity memberEntity) { + return new MemberResponse( + memberEntity.getId(), + memberEntity.getName() + ); + } } diff --git a/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java b/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java new file mode 100644 index 0000000..048b10e --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/domain/MemberEntity.java @@ -0,0 +1,43 @@ +package kr.mafoo.user.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@Table("member") +public class MemberEntity { + @Id + @Column("member_id") + private String id; + + @Column("name") + private String name; + + @CreatedDate + @Column("created_at") + private LocalDateTime createdAt; + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + MemberEntity that = (MemberEntity) obj; + + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/exception/DomainException.java b/user-service/src/main/java/kr/mafoo/user/exception/DomainException.java new file mode 100644 index 0000000..604bde7 --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/exception/DomainException.java @@ -0,0 +1,13 @@ +package kr.mafoo.user.exception; + +import lombok.Getter; + +@Getter +public class DomainException extends RuntimeException { + private final ErrorCode errorCode; + + public DomainException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/exception/ErrorCode.java b/user-service/src/main/java/kr/mafoo/user/exception/ErrorCode.java new file mode 100644 index 0000000..59f0902 --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/exception/ErrorCode.java @@ -0,0 +1,12 @@ +package kr.mafoo.user.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ErrorCode { + MEMBER_NOT_FOUND("ME0001", "사용자를 찾을 수 없습니다"); + private final String code; + private final String message; +} diff --git a/user-service/src/main/java/kr/mafoo/user/exception/MemberNotFoundException.java b/user-service/src/main/java/kr/mafoo/user/exception/MemberNotFoundException.java new file mode 100644 index 0000000..09dbd35 --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/exception/MemberNotFoundException.java @@ -0,0 +1,7 @@ +package kr.mafoo.user.exception; + +public class MemberNotFoundException extends DomainException { + public MemberNotFoundException() { + super(ErrorCode.MEMBER_NOT_FOUND); + } +} diff --git a/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java b/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java new file mode 100644 index 0000000..e8da018 --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/repository/MemberRepository.java @@ -0,0 +1,9 @@ +package kr.mafoo.user.repository; + +import kr.mafoo.user.domain.MemberEntity; +import org.springframework.data.r2dbc.repository.R2dbcRepository; +import reactor.core.publisher.Mono; + +public interface MemberRepository extends R2dbcRepository { + Mono deleteMemberById(String memberId); +} diff --git a/user-service/src/main/java/kr/mafoo/user/service/MemberService.java b/user-service/src/main/java/kr/mafoo/user/service/MemberService.java new file mode 100644 index 0000000..94deb7b --- /dev/null +++ b/user-service/src/main/java/kr/mafoo/user/service/MemberService.java @@ -0,0 +1,25 @@ +package kr.mafoo.user.service; + +import kr.mafoo.user.domain.MemberEntity; +import kr.mafoo.user.exception.MemberNotFoundException; +import kr.mafoo.user.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +@Service +public class MemberService { + private final MemberRepository memberRepository; + + public Mono quitMemberByMemberId(String memberId) { + return memberRepository.deleteMemberById(memberId); + } + + public Mono getMemberByMemberId(String memberId) { + return memberRepository + .findById(memberId) + .switchIfEmpty(Mono.error(new MemberNotFoundException())); + } + +} diff --git a/user-service/src/main/resources/application.yaml b/user-service/src/main/resources/application.yaml index 1a7f479..273e526 100644 --- a/user-service/src/main/resources/application.yaml +++ b/user-service/src/main/resources/application.yaml @@ -5,3 +5,6 @@ spring: url: ${MYSQL_URL} username: ${MYSQL_USERNAME} password: ${MYSQL_PASSWORD} +logging: + level: + org.springframework.data.r2dbc: DEBUG