Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,14 @@ public interface StatJpaRepository extends JpaRepository<Stat, Long> {
s.user.id = :userId
""")
void updateStatByUserIdCaseLose(long deltaScore, long userId);

@Query(
"""
SELECT new io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId(
u.id, u.nickname, s.totalGames, s.winningGames, s.score
)
FROM Stat s JOIN s.user u
WHERE u.id = :userId
""")
Optional<StatWithNicknameAndUserId> findByUserId(long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.f1.backend.domain.stat.dto.StatPageResponse;
import io.f1.backend.domain.stat.dto.StatResponse;
import io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId;
import io.f1.backend.domain.user.dto.MyPageInfo;

import lombok.RequiredArgsConstructor;

Expand All @@ -27,6 +28,7 @@
@Repository
@RequiredArgsConstructor
public class StatRedisRepository {

private static final String STAT_RANK = "stat:rank";
private static final String STAT_USER = "stat:user:%d";
private static final String STAT_NICKNAME = "stat:%s";
Expand Down Expand Up @@ -149,4 +151,23 @@ private static String getStatNickname(String nickname) {
private long getUserIdFromNickname(String nickname) {
return ((Number) requireNonNull(valueOps.get(getStatNickname(nickname)))).longValue();
}

public MyPageInfo getStatByUserId(long userId) {
String statUserKey = getStatUserKey(userId);

Long rank = zSetOps.reverseRank(STAT_RANK, userId);
Double score = zSetOps.score(STAT_RANK, userId);
Map<Object, Object> statMap = hashOps.entries(statUserKey);

if (rank == null || score == null || statMap.isEmpty()) {
throw new IllegalStateException("User not found in Redis: " + userId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

왜 여기는 customException을 쓰지 않으신건지 궁금합니다!

Copy link
Collaborator Author

@jiwon1217 jiwon1217 Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구조가 Redis 조회 -> 없으면 -> MySQL 조회로 넘어가게 되는데, 위 코드에서 던진 예외를 아래의 코드로 처리를 합니다. 그래서 해당 예외는 프론트로 내려가지 않기 때문에 CustomException 처리를 하지 않았습니다 !

try {
            return redisRepository.getStatByUserId(userId);
        } catch (Exception e) {
            log.error("Redis miss, fallback to MySQL for userId={}", userId, e);
        }

}

return new MyPageInfo(
(String) statMap.get("nickname"),
rank + 1,
(long) statMap.get("totalGames"),
(long) statMap.get("winningGames"),
score.longValue());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.f1.backend.domain.stat.dao;

import io.f1.backend.domain.stat.dto.StatPageResponse;
import io.f1.backend.domain.user.dto.MyPageInfo;

import org.springframework.data.domain.Pageable;

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

void removeUser(long userId);

MyPageInfo getMyPageByUserId(long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import io.f1.backend.domain.stat.dto.StatPageResponse;
import io.f1.backend.domain.stat.dto.StatWithNickname;
import io.f1.backend.domain.stat.dto.StatWithNicknameAndUserId;
import io.f1.backend.domain.user.dto.MyPageInfo;
import io.f1.backend.global.exception.CustomException;
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
import io.f1.backend.global.exception.errorcode.UserErrorCode;

import jakarta.annotation.PostConstruct;

Expand Down Expand Up @@ -97,4 +99,25 @@ private Pageable getPageableFromNickname(String nickname, int pageSize) {
int pageNumber = rowNum > 0 ? (int) (rowNum / pageSize) : 0;
return PageRequest.of(pageNumber, pageSize, Sort.by(Direction.DESC, "score"));
}

@Override
public MyPageInfo getMyPageByUserId(long userId) {
try {
return redisRepository.getStatByUserId(userId);
} catch (Exception e) {
log.error("Redis miss, fallback to MySQL for userId={}", userId, e);
}

StatWithNicknameAndUserId stat = findStatByUserId(userId);
long rank = jpaRepository.countByScoreGreaterThan(stat.score()) + 1;

return new MyPageInfo(
stat.nickname(), rank, stat.totalGames(), stat.winningGames(), stat.score());
}

private StatWithNicknameAndUserId findStatByUserId(long userId) {
return jpaRepository
.findByUserId(userId)
.orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.f1.backend.global.util.SecurityUtils.logout;

import io.f1.backend.domain.user.app.UserService;
import io.f1.backend.domain.user.dto.MyPageInfo;
import io.f1.backend.domain.user.dto.SignupRequest;
import io.f1.backend.domain.user.dto.UserPrincipal;

Expand All @@ -13,6 +14,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -42,4 +44,11 @@ public ResponseEntity<Void> updateNickname(
userPrincipal.getUserId(), signupRequest.nickname(), httpSession);
return ResponseEntity.noContent().build();
}

@GetMapping
public ResponseEntity<MyPageInfo> getMyPage(
@AuthenticationPrincipal UserPrincipal userPrincipal) {
MyPageInfo response = userService.getMyPage(userPrincipal);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import static io.f1.backend.global.util.RedisPublisher.USER_UPDATE;

import io.f1.backend.domain.auth.dto.CurrentUserAndAdminResponse;
import io.f1.backend.domain.stat.dao.StatRepository;
import io.f1.backend.domain.user.dao.UserRepository;
import io.f1.backend.domain.user.dto.AuthenticationUser;
import io.f1.backend.domain.user.dto.MyPageInfo;
import io.f1.backend.domain.user.dto.SignupRequest;
import io.f1.backend.domain.user.dto.UserPrincipal;
import io.f1.backend.domain.user.dto.UserSummary;
Expand All @@ -32,6 +34,7 @@ public class UserService {

private final UserRepository userRepository;
private final RedisPublisher redisPublisher;
private final StatRepository statRepository;

@Transactional
public CurrentUserAndAdminResponse signup(HttpSession session, SignupRequest signupRequest) {
Expand Down Expand Up @@ -122,4 +125,10 @@ public void checkNickname(String nickname) {
validateNicknameFormat(nickname);
validateNicknameDuplicate(nickname);
}

@Transactional(readOnly = true)
public MyPageInfo getMyPage(UserPrincipal userPrincipal) {
Long userId = userPrincipal.getUserId();
return statRepository.getMyPageByUserId(userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.f1.backend.domain.user.dto;

public record MyPageInfo(
String nickname, long rank, long score, long totalGames, long winningGames) {}