Skip to content

Commit 0eadfe2

Browse files
authored
AIM-70-마이페이지-조회 (#66)
* feat: 마이페이지 조회 응답 DTO 추가 * feat: 마이페이지 티어 진행률 및 다음 티어 조회 로직 구현 * feat: 마이페이지 조회 API 추가 * feat: AuthService에 회원가입 시 레벨을 1로 설정하도록 추가 * feat: UserService에 레벨업시 티어 결정 로직 추가 * fix: 다이아몬드 티어 진행률 계산 로직 수정
1 parent 9554d9c commit 0eadfe2

File tree

5 files changed

+164
-2
lines changed

5 files changed

+164
-2
lines changed

src/main/java/targeter/aim/domain/auth/service/AuthService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public UserDto.UserResponse signUp(AuthDto.SignUpRequest request) {
5757

5858
User toSave = request.toEntity(passwordEncoder);
5959

60+
toSave.setLevel(1);
61+
6062
Tier bronze = tierRepository.findByName("BRONZE")
6163
.orElseThrow(() -> new RestException(ErrorCode.TIER_NOT_FOUND));
6264
toSave.setTier(bronze);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package targeter.aim.domain.user.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import targeter.aim.domain.user.dto.MyPageDto;
11+
import targeter.aim.domain.user.service.MyPageService;
12+
import targeter.aim.system.security.model.UserDetails;
13+
14+
@Tag(name = "MyPage", description = "마이페이지 API")
15+
@RestController
16+
@RequiredArgsConstructor
17+
@RequestMapping("/api/mypage")
18+
public class MyPageController {
19+
20+
private final MyPageService myPageService;
21+
22+
@GetMapping
23+
@Operation(
24+
summary = "마이페이지 레벨/티어 조회",
25+
description = "로그인한 사용자의 레벨, 티어, 티어 진행률, 다음 티어 정보를 조회합니다."
26+
)
27+
public MyPageDto.MyPageResponse getMyPage(
28+
@AuthenticationPrincipal UserDetails userDetails
29+
) {
30+
return myPageService.getMyPage(userDetails);
31+
}
32+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package targeter.aim.domain.user.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
8+
public class MyPageDto {
9+
10+
@Getter
11+
@NoArgsConstructor
12+
@AllArgsConstructor
13+
public static class MyPageResponse {
14+
15+
@Schema(description = "현재 레벨", example = "37")
16+
private int level;
17+
18+
@Schema(description = "현재 티어", example = "SILVER")
19+
private TierDto.TierResponse tier;
20+
21+
@Schema(description = "티어 진행률 (%)", example = "45")
22+
private int tierProgressPercent;
23+
24+
@Schema(
25+
description = "다음 티어 (다이아몬드인 경우 null)",
26+
example = "GOLD",
27+
nullable = true
28+
)
29+
private TierDto.TierResponse nextTier;
30+
}
31+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package targeter.aim.domain.user.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.stereotype.Service;
5+
import org.springframework.transaction.annotation.Transactional;
6+
import targeter.aim.domain.user.dto.MyPageDto;
7+
import targeter.aim.domain.user.dto.TierDto;
8+
import targeter.aim.domain.user.entity.Tier;
9+
import targeter.aim.domain.user.entity.User;
10+
import targeter.aim.domain.user.repository.TierRepository;
11+
import targeter.aim.domain.user.repository.UserRepository;
12+
import targeter.aim.system.exception.model.ErrorCode;
13+
import targeter.aim.system.exception.model.RestException;
14+
import targeter.aim.system.security.model.UserDetails;
15+
16+
@Service
17+
@RequiredArgsConstructor
18+
@Transactional(readOnly = true)
19+
public class MyPageService {
20+
21+
private final UserRepository userRepository;
22+
private final TierRepository tierRepository;
23+
24+
public MyPageDto.MyPageResponse getMyPage(UserDetails userDetails) {
25+
User user = userRepository.findById(userDetails.getUser().getId())
26+
.orElseThrow(() -> new RestException(ErrorCode.USER_NOT_FOUND));
27+
28+
int level = user.getLevel();
29+
Tier currentTier = user.getTier();
30+
31+
int tierProgressPercent = calculateTierProgressPercent(level, currentTier);
32+
Tier nextTierEntity = findNextTier(currentTier);
33+
34+
return new MyPageDto.MyPageResponse(
35+
level,
36+
TierDto.TierResponse.from(currentTier),
37+
tierProgressPercent,
38+
nextTierEntity == null
39+
? null
40+
: TierDto.TierResponse.from(nextTierEntity)
41+
);
42+
}
43+
44+
// 티어 진행률 계산
45+
private int calculateTierProgressPercent(int level, Tier tier) {
46+
int start;
47+
int end;
48+
49+
switch (tier.getName()) {
50+
case "BRONZE" -> {
51+
start = 1;
52+
end = 30;
53+
}
54+
case "SILVER" -> {
55+
start = 31;
56+
end = 60;
57+
}
58+
case "GOLD" -> {
59+
start = 61;
60+
end = 80;
61+
}
62+
case "DIAMOND" -> {
63+
start = 81;
64+
end = 100;
65+
}
66+
default -> throw new RestException(ErrorCode.TIER_NOT_FOUND);
67+
}
68+
69+
double progress = (double) (level - start) / (end - start);
70+
return Math.min(100, (int) Math.round(progress * 100));
71+
}
72+
73+
// 다음 티어 계산
74+
private Tier findNextTier(Tier currentTier) {
75+
return switch (currentTier.getName()) {
76+
case "BRONZE" -> tierRepository.findByName("SILVER").orElse(null);
77+
case "SILVER" -> tierRepository.findByName("GOLD").orElse(null);
78+
case "GOLD" -> tierRepository.findByName("DIAMOND").orElse(null);
79+
case "DIAMOND" -> null;
80+
default -> null;
81+
};
82+
}
83+
}

src/main/java/targeter/aim/domain/user/service/UserService.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import targeter.aim.domain.challenge.entity.ChallengeResult;
99
import targeter.aim.domain.challenge.repository.ChallengeMemberQueryRepository;
1010
import targeter.aim.domain.user.dto.UserDto;
11+
import targeter.aim.domain.user.entity.Tier;
1112
import targeter.aim.domain.user.entity.User;
13+
import targeter.aim.domain.user.repository.TierRepository;
1214
import targeter.aim.domain.user.repository.UserRepository;
1315
import targeter.aim.system.exception.model.ErrorCode;
1416
import targeter.aim.system.exception.model.RestException;
@@ -22,6 +24,7 @@
2224
public class UserService {
2325

2426
private final UserRepository userRepository;
27+
private final TierRepository tierRepository;
2528
private final ChallengeMemberQueryRepository challengeMemberQueryRepository;
2629

2730
@Transactional(readOnly = true)
@@ -44,8 +47,12 @@ public void checkAndApplyLevelUp(User user) {
4447
double score = calculateScore(successCount, totalCount, currentLevel);
4548

4649
if (score >= 1.0) {
47-
user.setLevel(currentLevel + 1);
48-
log.info("User {} Level Up! {} -> {}", user.getId(), currentLevel, currentLevel + 1);
50+
int newLevel = currentLevel + 1;
51+
user.setLevel(newLevel);
52+
53+
Tier newTier = determineTierByLevel(newLevel);
54+
user.setTier(newTier);
55+
log.info("User {} Level Up! {} -> {}, Tier -> {}", user.getId(), currentLevel, newLevel, newTier.getName());
4956
}
5057
}
5158

@@ -55,6 +62,13 @@ private double calculateScore(double success, double total, double level) {
5562
return term1 + term2;
5663
}
5764

65+
private Tier determineTierByLevel(int level) {
66+
if (level <= 30) return tierRepository.findByName("BRONZE").orElseThrow();
67+
if (level <= 60) return tierRepository.findByName("SILVER").orElseThrow();
68+
if (level <= 80) return tierRepository.findByName("GOLD").orElseThrow();
69+
return tierRepository.findByName("DIAMOND").orElseThrow();
70+
}
71+
5872
@Transactional(readOnly = true)
5973
public List<UserDto.RankTop10Response> getTop10UserRank() {
6074
List<User> users = userRepository

0 commit comments

Comments
 (0)