Skip to content

Commit ca546d5

Browse files
authored
[FEATURE/#47] 로그인 상태 및 기본 정보 조회 API 구현 (#51)
## 🔗 Related Issue <!-- 이슈 번호를 작성하여 종료시켜주세요 --> - Closes #47 ## 📝 Summary <!-- 작업한 기능을 설명해주세요 --> 현재 접속 중인 사용자의 로그인 여부를 판단하고, 화면에 표시할 기본적인 회원 정보(이름)를 제공하는 API를 구현했습니다. - 로그인 상태 조회의 경우 요청 헤더의 엑세스 토큰이 유효하면 로그인 상태로, 유효하지 않거나 토큰이 없으면 비로그인 상태로 간주 - 기본 정보 조회는 로그인한 사용자의 `memberId`를 이용해 `Member` 객체 조회 후 DTO로 변환하여 응답 ## 🔄 Changes <!-- 구체적으로 어떤 파일/로직이 변경되었는지 체크해주세요 --> - [x] API 변경 (추가/수정) - [ ] 데이터 및 도메인 변경 (DB, 비즈니스 로직) - [ ] 설정 또는 인프라 관련 변경 - [ ] 리팩토링 ## 💬 Questions & Review Points <!-- 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요 --> ## 📸 API Test Results (Swagger) <!-- API 테스트 스크린샷 첨부 --> <img width="1423" height="812" alt="image" src="https://github.com/user-attachments/assets/294d27ff-90e0-4ec0-ad9a-4b62df96d37e" /> <img width="1426" height="812" alt="image" src="https://github.com/user-attachments/assets/9b2b4dd2-66d0-478e-bb5b-fdd83e99bfe9" /> <img width="1424" height="809" alt="image" src="https://github.com/user-attachments/assets/c15a9130-f560-4b52-af76-5a548006cc5a" /> <img width="1427" height="756" alt="image" src="https://github.com/user-attachments/assets/396a9895-22be-4057-9bea-48dfad1b1741" /> ## ✅ Checklist - [x] API 테스트 완료 - [x] 테스트 결과 사진 첨부 - [x] 빌드 성공 확인 (./gradlew build)
1 parent 0722a07 commit ca546d5

13 files changed

Lines changed: 256 additions & 23 deletions

File tree

src/main/java/org/umc/valuedi/domain/auth/controller/AuthController.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,12 @@ public ApiResponse<Void> logout(
158158

159159
return ApiResponse.onSuccess(AuthSuccessCode.LOGOUT_OK, null);
160160
}
161+
162+
@Override
163+
@GetMapping("/status")
164+
public ApiResponse<AuthResDTO.AuthStatusDTO> getAuthStatus(
165+
@CurrentMember Long memberId
166+
) {
167+
return ApiResponse.onSuccess(AuthSuccessCode.AUTH_STATUS_GET_SUCCESS, authQueryService.getAuthStatus(memberId));
168+
}
161169
}

src/main/java/org/umc/valuedi/domain/auth/controller/AuthControllerDocs.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.umc.valuedi.domain.auth.dto.req.AuthReqDTO;
1616
import org.umc.valuedi.domain.auth.dto.res.AuthResDTO;
1717
import org.umc.valuedi.global.apiPayload.ApiResponse;
18+
import org.umc.valuedi.global.security.annotation.CurrentMember;
1819
import org.umc.valuedi.global.security.principal.CustomUserDetails;
1920

2021
@Tag(name = "Auth", description = "Auth 관련 API (로그인, 회원가입 등)")
@@ -441,7 +442,7 @@ public ApiResponse<AuthResDTO.LoginResultDTO> localLogin(
441442

442443
@Operation(
443444
summary = "토큰 재발급 API",
444-
description = "쿠키에 저장된 리프레시 토큰으로 새로운 엑세스 토큰과 리프레시 토큰을 발급합니다. \n요청 헤더에 만료되지 않은 엑세스 토큰이 있다면 이를 무효화하며, 새로 발급된 리프레시 토큰은 쿠키에 저장됩니다.")
445+
description = "쿠키에 저장된 리프레시 토큰으로 새로운 액세스 토큰과 리프레시 토큰을 발급합니다. \n요청 헤더에 만료되지 않은 액세스 토큰이 있다면 이를 무효화하며, 새로 발급된 리프레시 토큰은 쿠키에 저장됩니다.")
445446
@ApiResponses({
446447
@io.swagger.v3.oas.annotations.responses.ApiResponse(
447448
responseCode = "200",
@@ -475,7 +476,7 @@ public ApiResponse<AuthResDTO.LoginResultDTO> tokenReissue(
475476

476477
@Operation(
477478
summary = "로그아웃 API",
478-
description = "카카오/로컬로 로그인한 계정을 로그아웃 시킵니다. \n요청 시 헤더에 포함된 엑세스 토큰을 이용해 엑세스/리프레시 토큰을 무효화합니다.")
479+
description = "카카오/로컬로 로그인한 계정을 로그아웃 시킵니다. \n요청 시 헤더에 포함된 액세스 토큰을 이용해 액세스/리프레시 토큰을 무효화합니다.")
479480
@ApiResponses({
480481
@io.swagger.v3.oas.annotations.responses.ApiResponse(
481482
responseCode = "200",
@@ -502,4 +503,50 @@ public ApiResponse<Void> logout(
502503
String accessToken,
503504
HttpServletResponse response
504505
);
506+
507+
@Operation(
508+
summary = "로그인 상태 조회 API",
509+
description = "현재 접속 중인 사용자의 로그인 상태를 조회합니다. \n요청 헤더의 액세스 토큰이 유효하지 않거나 없는 경우 비로그인 상태이며, 액세스 토큰이 유효할 경우 로그인 상태입니다.")
510+
@ApiResponses({
511+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
512+
responseCode = "200",
513+
description = "성공 - 로그인 상태 조회(로그인/비로그인)",
514+
content = @Content(
515+
schema = @Schema(implementation = ApiResponse.class),
516+
examples = {
517+
@ExampleObject(
518+
name = "로그인 상태 예시",
519+
value = """
520+
{
521+
"isSuccess": true,
522+
"code": "AUTH200_8",
523+
"message": "로그인 상태 조회에 성공했습니다.",
524+
"result": {
525+
"isLogin": true,
526+
"memberId": 9
527+
}
528+
}
529+
"""
530+
),
531+
@ExampleObject(
532+
name = "비로그인 상태 예시",
533+
value = """
534+
{
535+
"isSuccess": true,
536+
"code": "AUTH200_8",
537+
"message": "로그인 상태 조회에 성공했습니다.",
538+
"result": {
539+
"isLogin": false,
540+
"memberId": null
541+
}
542+
}
543+
"""
544+
)
545+
}
546+
)
547+
)
548+
})
549+
public ApiResponse<AuthResDTO.AuthStatusDTO> getAuthStatus(
550+
@Parameter(hidden = true) Long memberId
551+
);
505552
}

src/main/java/org/umc/valuedi/domain/auth/converter/AuthConverter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ public static AuthResDTO.LoginResultDTO toLoginResultDTO(Member member, String a
7070
.build();
7171
}
7272

73+
// 로그인 상태를 DTO로 변환
74+
public static AuthResDTO.AuthStatusDTO toAuthStatusDTO(Boolean authStatus, Long memberId) {
75+
return AuthResDTO.AuthStatusDTO.builder()
76+
.isLogin(authStatus)
77+
.memberId(memberId)
78+
.build();
79+
}
80+
7381
// 카카오에서 넘어온 정보에서 필수 필드가 모두 있는지 검증
7482
private static void validateRequiredFields(KakaoResDTO.UserInfoDTO.KakaoAccount account) {
7583
if (account.getName() == null || account.getGender() == null ||

src/main/java/org/umc/valuedi/domain/auth/dto/res/AuthResDTO.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@ public class AuthResDTO {
77

88
@Builder
99
public record LoginResultDTO (
10-
String accessToken,
10+
String accessToken,
1111

12-
// 리프레시 토큰은 쿠키로 전달하고 JSON body에는 저장하지 않음
13-
@JsonIgnore
14-
String refreshToken,
12+
// 리프레시 토큰은 쿠키로 전달하고 JSON body에는 저장하지 않음
13+
@JsonIgnore
14+
String refreshToken,
1515

16-
Long memberId
16+
Long memberId
1717
) {}
1818

1919
@Builder
2020
public record RegisterResDTO (
2121
Long memberId
2222
) {}
23+
24+
@Builder
25+
public record AuthStatusDTO (
26+
Boolean isLogin,
27+
Long memberId
28+
) {}
2329
}

src/main/java/org/umc/valuedi/domain/auth/exception/code/AuthSuccessCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public enum AuthSuccessCode implements BaseSuccessCode {
1616
EMAIL_VERIFY_SUCCESS(HttpStatus.OK, "AUTH200_5", "이메일 인증에 성공했습니다."),
1717
SIGNUP_SUCCESS(HttpStatus.CREATED, "AUTH201_1", "회원가입이 성공적으로 완료되었습니다."),
1818
TOKEN_REISSUE_SUCCESS(HttpStatus.OK, "AUTH200_6", "토큰 재발급에 성공했습니다."),
19-
LOGOUT_OK(HttpStatus.OK, "AUTH200_7", "로그아웃이 완료되었습니다.")
19+
LOGOUT_OK(HttpStatus.OK, "AUTH200_7", "로그아웃이 완료되었습니다."),
20+
AUTH_STATUS_GET_SUCCESS(HttpStatus.OK, "AUTH200_8", "로그인 상태 조회에 성공했습니다.")
2021
;
2122

2223
private final HttpStatus status;

src/main/java/org/umc/valuedi/domain/auth/service/query/AuthQueryService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import lombok.RequiredArgsConstructor;
44
import org.springframework.stereotype.Service;
55
import org.springframework.transaction.annotation.Transactional;
6+
import org.umc.valuedi.domain.auth.converter.AuthConverter;
7+
import org.umc.valuedi.domain.auth.dto.res.AuthResDTO;
68
import org.umc.valuedi.domain.auth.exception.AuthException;
79
import org.umc.valuedi.domain.auth.exception.code.AuthErrorCode;
810
import org.umc.valuedi.domain.member.repository.MemberRepository;
@@ -19,4 +21,13 @@ public void checkUsernameDuplicate(String username) {
1921
throw new AuthException(AuthErrorCode.DUPLICATE_USERNAME);
2022
}
2123
}
24+
25+
// 로그인 상태 조회
26+
public AuthResDTO.AuthStatusDTO getAuthStatus(Long memberId) {
27+
if(memberId == null) {
28+
return AuthConverter.toAuthStatusDTO(false, null);
29+
}
30+
31+
return AuthConverter.toAuthStatusDTO(true, memberId);
32+
}
2233
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.umc.valuedi.domain.member.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RestController;
7+
import org.umc.valuedi.domain.member.dto.res.MemberResDTO;
8+
import org.umc.valuedi.domain.member.exception.code.MemberSuccessCode;
9+
import org.umc.valuedi.domain.member.service.query.MemberQueryService;
10+
import org.umc.valuedi.global.apiPayload.ApiResponse;
11+
import org.umc.valuedi.global.security.annotation.CurrentMember;
12+
13+
@RestController
14+
@RequestMapping("/api/users")
15+
@RequiredArgsConstructor
16+
public class MemberController implements MemberControllerDocs {
17+
private final MemberQueryService memberQueryService;
18+
19+
@Override
20+
@GetMapping("/me")
21+
public ApiResponse<MemberResDTO.MemberInfoDTO> getMemberInfo(
22+
@CurrentMember Long memberId
23+
) {
24+
return ApiResponse.onSuccess(MemberSuccessCode.MEMBER_INFO_GET_SUCCESS, memberQueryService.getMemberInfo(memberId));
25+
}
26+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.umc.valuedi.domain.member.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.ExampleObject;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import org.umc.valuedi.domain.member.dto.res.MemberResDTO;
11+
import org.umc.valuedi.global.apiPayload.ApiResponse;
12+
13+
@Tag(name = "Member", description = "Member 관련 API")
14+
public interface MemberControllerDocs {
15+
16+
@Operation(
17+
summary = "기본 정보 조회(이름) API",
18+
description = "로그인한 사용자의 기본 정보를 조회합니다.")
19+
@ApiResponses({
20+
@io.swagger.v3.oas.annotations.responses.ApiResponse(
21+
responseCode = "200",
22+
description = "성공 - 기본 정보 조회",
23+
content = @Content(
24+
schema = @Schema(implementation = ApiResponse.class),
25+
examples = @ExampleObject(
26+
name = "기본 정보 조회 예시",
27+
value = """
28+
{
29+
"isSuccess": true,
30+
"code": "MEMBER200_1",
31+
"message": "회원 기본 정보 조회에 성공했습니다.",
32+
"result": {
33+
"memberId": 9,
34+
"name": "밸류디"
35+
}
36+
}
37+
"""
38+
)
39+
)
40+
)
41+
})
42+
public ApiResponse<MemberResDTO.MemberInfoDTO> getMemberInfo(
43+
@Parameter(hidden = true) Long memberId
44+
);
45+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.umc.valuedi.domain.member.converter;
2+
3+
import org.umc.valuedi.domain.member.dto.res.MemberResDTO;
4+
import org.umc.valuedi.domain.member.entity.Member;
5+
6+
public class MemberConverter {
7+
8+
// Member -> 기본 정보 조회 DTO 변환
9+
public static MemberResDTO.MemberInfoDTO toMemberInfoDTO(Member member) {
10+
return MemberResDTO.MemberInfoDTO.builder()
11+
.memberId(member.getId())
12+
.name(member.getRealName())
13+
.build();
14+
}
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.umc.valuedi.domain.member.dto.res;
2+
3+
import lombok.Builder;
4+
5+
public class MemberResDTO {
6+
7+
@Builder
8+
public record MemberInfoDTO(
9+
Long memberId,
10+
String name
11+
) {}
12+
}

0 commit comments

Comments
 (0)