Skip to content

Commit a178fe1

Browse files
authored
✨ [feat] 마이페이지 조회 기능 (#119)
* ✨ feat: 마이페이지 기능 구현 * chore: Java 스타일 수정 * ♻️ refactor: dto 이름 변경(MyPage -> MyPageInfo) * ♻️ refactor: userId 기반 단건 조회 쿼리 메서드로 성능 개선 * chore: Java 스타일 수정 * ♻️ refactor: dto 이름 변경(StatWithNicknameAndUserId -> StatWithUserSummary) --------- Co-authored-by: github-actions <>
1 parent e9131e1 commit a178fe1

File tree

9 files changed

+92
-13
lines changed

9 files changed

+92
-13
lines changed

backend/src/main/java/io/f1/backend/domain/stat/dao/StatJpaRepository.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.f1.backend.domain.stat.dao;
22

33
import io.f1.backend.domain.stat.dto.StatWithNickname;
4-
import io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId;
4+
import io.f1.backend.domain.stat.dto.StatWithUserSummary;
55
import io.f1.backend.domain.stat.entity.Stat;
66

77
import org.springframework.data.domain.Page;
@@ -33,12 +33,12 @@ public interface StatJpaRepository extends JpaRepository<Stat, Long> {
3333
@Query(
3434
"""
3535
SELECT
36-
new io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId
36+
new io.f1.backend.domain.stat.dto.StatWithUserSummary
3737
(u.id, u.nickname, s.totalGames, s.winningGames, s.score)
3838
FROM
3939
Stat s JOIN s.user u
4040
""")
41-
List<StatWithNicknameAndUserId> findAllStatWithNicknameAndUserId();
41+
List<StatWithUserSummary> findAllStatWithUserSummary();
4242

4343
@Modifying
4444
@Query(
@@ -63,4 +63,14 @@ public interface StatJpaRepository extends JpaRepository<Stat, Long> {
6363
s.user.id = :userId
6464
""")
6565
void updateStatByUserIdCaseLose(long deltaScore, long userId);
66+
67+
@Query(
68+
"""
69+
SELECT new io.f1.backend.domain.stat.dto.StatWithUserSummary(
70+
u.id, u.nickname, s.totalGames, s.winningGames, s.score
71+
)
72+
FROM Stat s JOIN s.user u
73+
WHERE u.id = :userId
74+
""")
75+
Optional<StatWithUserSummary> findStatWithUserSummary(long userId);
6676
}

backend/src/main/java/io/f1/backend/domain/stat/dao/StatRedisRepository.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
import io.f1.backend.domain.stat.dto.StatPageResponse;
66
import io.f1.backend.domain.stat.dto.StatResponse;
7-
import io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId;
7+
import io.f1.backend.domain.stat.dto.StatWithUserSummary;
8+
import io.f1.backend.domain.user.dto.MyPageInfo;
89

910
import lombok.RequiredArgsConstructor;
1011

@@ -27,6 +28,7 @@
2728
@Repository
2829
@RequiredArgsConstructor
2930
public class StatRedisRepository {
31+
3032
private static final String STAT_RANK = "stat:rank";
3133
private static final String STAT_USER = "stat:user:%d";
3234
private static final String STAT_NICKNAME = "stat:%s";
@@ -42,7 +44,7 @@ public void setup() {
4244
valueOps = redisTemplate.opsForValue();
4345
}
4446

45-
public void initialize(StatWithNicknameAndUserId stat) {
47+
public void initialize(StatWithUserSummary stat) {
4648
String statUserKey = getStatUserKey(stat.userId());
4749
String statNicknameKey = getStatNickname(stat.nickname());
4850

@@ -149,4 +151,23 @@ private static String getStatNickname(String nickname) {
149151
private long getUserIdFromNickname(String nickname) {
150152
return ((Number) requireNonNull(valueOps.get(getStatNickname(nickname)))).longValue();
151153
}
154+
155+
public MyPageInfo getStatByUserId(long userId) {
156+
String statUserKey = getStatUserKey(userId);
157+
158+
Long rank = zSetOps.reverseRank(STAT_RANK, userId);
159+
Double score = zSetOps.score(STAT_RANK, userId);
160+
Map<Object, Object> statMap = hashOps.entries(statUserKey);
161+
162+
if (rank == null || score == null || statMap.isEmpty()) {
163+
throw new IllegalStateException("User not found in Redis: " + userId);
164+
}
165+
166+
return new MyPageInfo(
167+
(String) statMap.get("nickname"),
168+
rank + 1,
169+
(long) statMap.get("totalGames"),
170+
(long) statMap.get("winningGames"),
171+
score.longValue());
172+
}
152173
}

backend/src/main/java/io/f1/backend/domain/stat/dao/StatRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.f1.backend.domain.stat.dao;
22

33
import io.f1.backend.domain.stat.dto.StatPageResponse;
4+
import io.f1.backend.domain.user.dto.MyPageInfo;
45

56
import org.springframework.data.domain.Pageable;
67

@@ -17,4 +18,6 @@ public interface StatRepository {
1718
void updateNickname(long userId, String nickname);
1819

1920
void removeUser(long userId);
21+
22+
MyPageInfo getMyPageByUserId(long userId);
2023
}

backend/src/main/java/io/f1/backend/domain/stat/dao/StatRepositoryAdapter.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import io.f1.backend.domain.stat.dto.StatPageResponse;
66
import io.f1.backend.domain.stat.dto.StatWithNickname;
7-
import io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId;
7+
import io.f1.backend.domain.stat.dto.StatWithUserSummary;
8+
import io.f1.backend.domain.user.dto.MyPageInfo;
89
import io.f1.backend.global.exception.CustomException;
910
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
11+
import io.f1.backend.global.exception.errorcode.UserErrorCode;
1012

1113
import jakarta.annotation.PostConstruct;
1214

@@ -54,7 +56,7 @@ public StatPageResponse getRanksByNickname(String nickname, int pageSize) {
5456

5557
@Override
5658
public void addUser(long userId, String nickname) {
57-
redisRepository.initialize(new StatWithNicknameAndUserId(userId, nickname, 0, 0, 0));
59+
redisRepository.initialize(new StatWithUserSummary(userId, nickname, 0, 0, 0));
5860
}
5961

6062
@Override
@@ -78,7 +80,7 @@ public void removeUser(long userId) {
7880
}
7981

8082
private void warmingRedis() {
81-
jpaRepository.findAllStatWithNicknameAndUserId().forEach(redisRepository::initialize);
83+
jpaRepository.findAllStatWithUserSummary().forEach(redisRepository::initialize);
8284
}
8385

8486
private Pageable getPageableFromNickname(String nickname, int pageSize) {
@@ -97,4 +99,25 @@ private Pageable getPageableFromNickname(String nickname, int pageSize) {
9799
int pageNumber = rowNum > 0 ? (int) (rowNum / pageSize) : 0;
98100
return PageRequest.of(pageNumber, pageSize, Sort.by(Direction.DESC, "score"));
99101
}
102+
103+
@Override
104+
public MyPageInfo getMyPageByUserId(long userId) {
105+
try {
106+
return redisRepository.getStatByUserId(userId);
107+
} catch (Exception e) {
108+
log.error("Redis miss, fallback to MySQL for userId={}", userId, e);
109+
}
110+
111+
StatWithUserSummary stat = findStatByUserId(userId);
112+
long rank = jpaRepository.countByScoreGreaterThan(stat.score()) + 1;
113+
114+
return new MyPageInfo(
115+
stat.nickname(), rank, stat.totalGames(), stat.winningGames(), stat.score());
116+
}
117+
118+
private StatWithUserSummary findStatByUserId(long userId) {
119+
return jpaRepository
120+
.findStatWithUserSummary(userId)
121+
.orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND));
122+
}
100123
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package io.f1.backend.domain.stat.dto;
22

3-
public record StatWithNicknameAndUserId(
3+
public record StatWithUserSummary(
44
long userId, String nickname, long totalGames, long winningGames, long score) {}

backend/src/main/java/io/f1/backend/domain/user/api/UserController.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static io.f1.backend.global.util.SecurityUtils.logout;
44

55
import io.f1.backend.domain.user.app.UserService;
6+
import io.f1.backend.domain.user.dto.MyPageInfo;
67
import io.f1.backend.domain.user.dto.SignupRequest;
78
import io.f1.backend.domain.user.dto.UserPrincipal;
89

@@ -13,6 +14,7 @@
1314
import org.springframework.http.ResponseEntity;
1415
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1516
import org.springframework.web.bind.annotation.DeleteMapping;
17+
import org.springframework.web.bind.annotation.GetMapping;
1618
import org.springframework.web.bind.annotation.PutMapping;
1719
import org.springframework.web.bind.annotation.RequestBody;
1820
import org.springframework.web.bind.annotation.RequestMapping;
@@ -42,4 +44,11 @@ public ResponseEntity<Void> updateNickname(
4244
userPrincipal.getUserId(), signupRequest.nickname(), httpSession);
4345
return ResponseEntity.noContent().build();
4446
}
47+
48+
@GetMapping
49+
public ResponseEntity<MyPageInfo> getMyPage(
50+
@AuthenticationPrincipal UserPrincipal userPrincipal) {
51+
MyPageInfo response = userService.getMyPage(userPrincipal);
52+
return ResponseEntity.ok(response);
53+
}
4554
}

backend/src/main/java/io/f1/backend/domain/user/app/UserService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import static io.f1.backend.global.util.RedisPublisher.USER_UPDATE;
88

99
import io.f1.backend.domain.auth.dto.CurrentUserAndAdminResponse;
10+
import io.f1.backend.domain.stat.dao.StatRepository;
1011
import io.f1.backend.domain.user.dao.UserRepository;
1112
import io.f1.backend.domain.user.dto.AuthenticationUser;
13+
import io.f1.backend.domain.user.dto.MyPageInfo;
1214
import io.f1.backend.domain.user.dto.SignupRequest;
1315
import io.f1.backend.domain.user.dto.UserPrincipal;
1416
import io.f1.backend.domain.user.dto.UserSummary;
@@ -32,6 +34,7 @@ public class UserService {
3234

3335
private final UserRepository userRepository;
3436
private final RedisPublisher redisPublisher;
37+
private final StatRepository statRepository;
3538

3639
@Transactional
3740
public CurrentUserAndAdminResponse signup(HttpSession session, SignupRequest signupRequest) {
@@ -122,4 +125,10 @@ public void checkNickname(String nickname) {
122125
validateNicknameFormat(nickname);
123126
validateNicknameDuplicate(nickname);
124127
}
128+
129+
@Transactional(readOnly = true)
130+
public MyPageInfo getMyPage(UserPrincipal userPrincipal) {
131+
Long userId = userPrincipal.getUserId();
132+
return statRepository.getMyPageByUserId(userId);
133+
}
125134
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package io.f1.backend.domain.user.dto;
2+
3+
public record MyPageInfo(
4+
String nickname, long rank, long score, long totalGames, long winningGames) {}

backend/src/test/java/io/f1/backend/domain/stat/RedisStatBrowserTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import io.f1.backend.domain.stat.dao.StatJpaRepository;
2727
import io.f1.backend.domain.stat.dao.StatRepositoryAdapter;
2828
import io.f1.backend.domain.stat.dto.StatWithNickname;
29-
import io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId;
29+
import io.f1.backend.domain.stat.dto.StatWithUserSummary;
3030
import io.f1.backend.domain.user.dao.UserRepository;
3131
import io.f1.backend.domain.user.dto.AuthenticationUser;
3232
import io.f1.backend.domain.user.dto.SignupRequest;
@@ -186,9 +186,9 @@ private MockHttpSession getMockSession(User user, boolean signup) {
186186
}
187187

188188
private void warmingRedisOneUser(User user) {
189-
StatWithNicknameAndUserId mockStat =
190-
new StatWithNicknameAndUserId(user.getId(), user.getNickname(), 10, 10, 100);
191-
given(statJpaRepository.findAllStatWithNicknameAndUserId()).willReturn(List.of(mockStat));
189+
StatWithUserSummary mockStat =
190+
new StatWithUserSummary(user.getId(), user.getNickname(), 10, 10, 100);
191+
given(statJpaRepository.findAllStatWithUserSummary()).willReturn(List.of(mockStat));
192192
statRepositoryAdapter.setup();
193193
}
194194
}

0 commit comments

Comments
 (0)