Skip to content

[feat] 관리자 페이지에 당일 가입자 수, 방문자 수 조회 로직 추가#106

Merged
KiSeungMin merged 9 commits into
developfrom
feature/seungmin
Aug 6, 2025
Merged

[feat] 관리자 페이지에 당일 가입자 수, 방문자 수 조회 로직 추가#106
KiSeungMin merged 9 commits into
developfrom
feature/seungmin

Conversation

@KiSeungMin

@KiSeungMin KiSeungMin commented Aug 6, 2025

Copy link
Copy Markdown
Member

✔️ 연관 이슈

📝 작업 내용

  • 관리자 페이지에 당일 방문자 수, 가입자 수 항목 추가
  • 관리자 페이지에 유저 목록이 조회되지 않던 버그 수정

스크린샷 (선택)

image

Summary by CodeRabbit

  • 신규 기능

    • 관리자가 일일 통계(오늘 가입자 수, 오늘 방문자 수 등)를 대시보드에서 확인할 수 있는 기능과 API가 추가되었습니다.
    • 오늘 가입자, 오늘 방문자 등 통계 카드가 대시보드에 새롭게 표시됩니다.
    • 사용자 가입 및 토큰 갱신 시 행동 로그가 비동기로 저장되어 통계에 활용됩니다.
  • 버그 수정

    • 관리자의 사용자 상세 및 목록 화면에서 사용자 유형, 로그인 유형, 성별 정보가 없을 때 "미설정"으로 안전하게 표시되며, 오류가 발생하지 않도록 개선되었습니다.
  • 스타일

    • 대시보드 통계 카드의 레이아웃이 개선되고, 아이콘 배경색 및 카드 구성이 일부 변경되었습니다.
  • 기타

    • EC2 배포 성공 시 Discord 알림 메시지에서 브랜치명이 제거되어 메시지가 간결해졌습니다.

@KiSeungMin KiSeungMin self-assigned this Aug 6, 2025
@KiSeungMin KiSeungMin added the 💡 feature 기능 구현 및 개발 label Aug 6, 2025
@coderabbitai

coderabbitai Bot commented Aug 6, 2025

Copy link
Copy Markdown

Walkthrough

관리자 페이지에 당일 가입자 수와 방문자 수를 집계하고 표시하는 기능이 추가되었습니다. 이를 위해 사용자 액션 로그를 Elasticsearch에 저장하는 로직, 관련 엔티티 및 저장소, 서비스 계층, 관리자 API 및 대시보드 템플릿이 도입·수정되었습니다. 일부 워크플로우 메시지와 null 체크도 개선되었습니다.

Changes

Cohort / File(s) Change Summary
Elasticsearch 사용자 액션 로그 도입
src/main/java/com/wayble/server/logging/entity/UserActionLog.java, src/main/java/com/wayble/server/logging/repository/UserActionLogRepository.java, src/main/java/com/wayble/server/logging/service/UserActionLogService.java, src/main/java/com/wayble/server/logging/config/LoggingAsyncConfig.java, src/main/java/com/wayble/server/ServerApplication.java
사용자 가입/토큰 갱신 로그를 Elasticsearch에 저장하기 위한 엔티티, 저장소, 서비스, 비동기 처리 설정 및 리포지토리 스캔 패키지 확장 추가
가입/로그인/토큰 관련 서비스 연동
src/main/java/com/wayble/server/auth/service/AuthService.java, src/main/java/com/wayble/server/auth/service/KakaoLoginService.java, src/main/java/com/wayble/server/user/service/UserService.java
회원가입, 로그인, 토큰 갱신 시 액션 로그 서비스 호출 추가(비동기), 신규 가입·토큰 갱신 로그 기록
관리자 통계 API 및 대시보드
src/main/java/com/wayble/server/admin/controller/AdminController.java, src/main/java/com/wayble/server/admin/service/AdminSystemService.java, src/main/java/com/wayble/server/admin/dto/DailyStatsDto.java, src/main/resources/templates/admin/dashboard.html
당일 가입자/방문자/누적 사용자/Wayble존 수 통계 API 및 DTO, 대시보드 표시 카드 추가, 통계 데이터 조회 및 반환
Null 체크 및 템플릿 안전성 개선
src/main/java/com/wayble/server/admin/service/AdminUserService.java, src/main/resources/templates/admin/user/user-detail.html, src/main/resources/templates/admin/user/users.html
Enum 변환 및 템플릿 출력 시 null 체크 추가, 미설정 값에 대한 안전 처리
CI/CD 워크플로우 메시지 수정
.github/workflows/cd-develop.yml
Discord Webhook 메시지에서 브랜치명(main) 제거, 메시지 단순화

Sequence Diagram(s)

sequenceDiagram
    participant Admin as 관리자
    participant AdminController
    participant AdminSystemService
    participant UserActionLogService
    participant AdminUserService
    participant AdminWaybleZoneService
    participant UserActionLogRepository

    Admin->>AdminController: /admin/api/daily-stats 요청
    AdminController->>AdminSystemService: getDailyStats()
    AdminSystemService->>UserActionLogService: getTodayUserRegistrationCount()
    AdminSystemService->>UserActionLogService: getTodayActiveUserCount()
    AdminSystemService->>AdminUserService: getTotalUserCount()
    AdminSystemService->>AdminWaybleZoneService: getTotalWaybleZoneCount()
    AdminSystemService->>AdminController: DailyStatsDto 반환
    AdminController->>Admin: 통계 JSON 반환
Loading
sequenceDiagram
    participant User as 사용자
    participant UserService
    participant UserActionLogService
    participant UserActionLogRepository

    User->>UserService: 회원가입 요청
    UserService->>UserActionLogService: logUserRegister()
    UserActionLogService->>UserActionLogRepository: save(UserActionLog)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
가입자 수, 방문자 수 로그 document 저장 로직 추가 (#105)
조회 결과를 관리자 페이지에 반영 (#105)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Discord Webhook 메시지 단순화 (.github/workflows/cd-develop.yml) 워크플로우 메시지 수정은 관리자 통계 기능과 직접적인 관련이 없습니다.

Possibly related PRs

  • [CI] 61 workflows 웹훅 추가 #62: 동일한 .github/workflows/cd-develop.yml에서 Discord Webhook 메시지 관련 직접적인 변경 내역이 있어 강한 연관성이 있습니다.
  • [CI] workflows 수정 #21: 동일 워크플로우 파일에서 잡 구조 변경 등 관련성이 있으나, 메시지 내용에는 직접적 변경 없음.

Suggested labels

🛠️ fix

Suggested reviewers

  • hyoinYang
  • seung-in-Yoo

Poem

🐰
오늘도 통계가 쏙쏙,
가입·방문자 수가 똑똑!
대시보드에 반짝이는 숫자,
로그는 ES에 착착차~
관리자는 미소짓고,
토끼도 춤을 추네!
🎉

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/seungmin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
.github/workflows/cd-develop.yml (1)

190-193: 배포 알림 메시지에서 브랜치·커밋 정보를 포함하는 방안을 고려해 주세요
메시지를 간결하게 변경한 것은 이해됩니다만, 어떤 브랜치(또는 커밋)가 배포됐는지 알 수 없으면 후속 모니터링‧Rollback 시 맥락 파악이 어렵습니다.
${{ github.ref_name }} 또는 ${{ github.sha }} 등을 함께 포함해 배포 대상 식별성을 높이는 것을 권장합니다.

- -d "{\"content\": \"✅ EC2 배포 성공!\"}" \
+ -d "{\"content\": \"✅ EC2 배포 성공! (branch: ${{ github.ref_name }})\"}" \
src/main/java/com/wayble/server/user/service/UserService.java (1)

31-38: 비동기 로깅 로직 검증 필요

회원가입 후 로깅 로직이 추가되었지만, 몇 가지 개선사항이 있습니다:

  1. Line 37에서 null을 직접 전달하고 있습니다
  2. 비동기 로깅 실패 시 예외 처리가 명시되어 있지 않습니다

다음과 같이 개선할 수 있습니다:

// 회원가입 로그 저장 (비동기)
userActionLogService.logUserRegister(
        savedUser.getId(), 
        req.loginType().name(), 
-       null
+       savedUser.getUserType() != null ? savedUser.getUserType().name() : null
);
src/main/java/com/wayble/server/logging/config/LoggingAsyncConfig.java (1)

10-26: 비동기 로깅 설정 검토

ThreadPool 설정이 전반적으로 적절하지만, 몇 가지 개선사항이 있습니다:

  1. 로깅용 스레드 풀 크기(코어 2, 최대 4)가 적절합니다
  2. 큐 용량 100도 합리적입니다
  3. shutdown 대기 시간 30초도 적절합니다

로깅에 대한 추가 설명을 주석으로 추가하는 것을 고려해보세요:

@Bean("loggingTaskExecutor")
public Executor loggingTaskExecutor() {
+   // 사용자 액션 로깅을 위한 비동기 실행자
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
src/main/java/com/wayble/server/logging/entity/UserActionLog.java (1)

42-45: ActionType enum 위치 고려

enum이 클래스 내부에 정의되어 있습니다. 다른 클래스에서도 사용할 가능성이 있다면 별도 파일로 분리하는 것을 고려해보세요.

만약 다른 서비스나 컨트롤러에서도 ActionType을 참조해야 한다면:

// 별도의 ActionType.java 파일로 분리
public enum ActionType {
    USER_REGISTER,
    USER_TOKEN_REFRESH
}
src/main/java/com/wayble/server/admin/service/AdminSystemService.java (1)

56-92: 일일 통계 메서드가 잘 구현되었지만 개선 여지가 있습니다.

전반적인 구현은 적절하나 몇 가지 개선점이 있습니다:

  1. LocalDate.now() 중복 호출 (line 58, 85)
  2. 개별 서비스 호출 실패 시 전체가 실패하는 구조

다음과 같이 개선할 수 있습니다:

 public DailyStatsDto getDailyStats() {
+    LocalDate today = LocalDate.now();
     try {
-        LocalDate today = LocalDate.now();
         
         // 일일 가입자 수
         long dailyRegistrationCount = userActionLogService.getTodayUserRegistrationCount();
         
         // ... 기존 로직 ...
         
     } catch (Exception e) {
         log.error("Failed to get daily stats", e);
         
         // 에러 발생시 기본값 반환
         return DailyStatsDto.builder()
-                .date(LocalDate.now())
+                .date(today)
                 // ... 나머지 동일 ...

추가로 각 서비스 호출을 try-catch로 개별 처리하여 일부 실패 시에도 다른 데이터는 표시할 수 있도록 개선하는 것을 고려해보세요.

src/main/java/com/wayble/server/logging/service/UserActionLogService.java (1)

54-85: 토큰 갱신 로깅의 중복 체크 로직이 우수합니다.

하루 1회 제한과 중복 체크 로직이 매우 좋습니다. 다만 개선 여지가 있습니다.

시간 일관성을 위해 LocalDateTime.now()를 한 번만 호출하는 것을 고려해보세요:

 public CompletableFuture<Void> logTokenRefresh(Long userId, String userType) {
     try {
+        LocalDateTime now = LocalDateTime.now();
         HttpServletRequest request = getCurrentRequest();
         
         // 하루에 한 번만 로그 저장하도록 체크
-        LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay();
+        LocalDateTime startOfDay = now.toLocalDate().atStartOfDay();
         LocalDateTime endOfDay = startOfDay.plusDays(1);
         
         // ... 중간 로직 ...
         
         if (existingCount == 0) {
             UserActionLog userActionLog = UserActionLog.builder()
                     .userId(userId)
                     .action(UserActionLog.ActionType.USER_TOKEN_REFRESH.name())
                     .userAgent(request != null ? request.getHeader("User-Agent") : null)
-                    .timestamp(LocalDateTime.now())
+                    .timestamp(now)
                     // ... 나머지 동일 ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19801dd and 04af9de.

📒 Files selected for processing (16)
  • .github/workflows/cd-develop.yml (1 hunks)
  • src/main/java/com/wayble/server/ServerApplication.java (1 hunks)
  • src/main/java/com/wayble/server/admin/controller/AdminController.java (4 hunks)
  • src/main/java/com/wayble/server/admin/dto/DailyStatsDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/service/AdminSystemService.java (3 hunks)
  • src/main/java/com/wayble/server/admin/service/AdminUserService.java (3 hunks)
  • src/main/java/com/wayble/server/auth/service/AuthService.java (4 hunks)
  • src/main/java/com/wayble/server/auth/service/KakaoLoginService.java (3 hunks)
  • src/main/java/com/wayble/server/logging/config/LoggingAsyncConfig.java (1 hunks)
  • src/main/java/com/wayble/server/logging/entity/UserActionLog.java (1 hunks)
  • src/main/java/com/wayble/server/logging/repository/UserActionLogRepository.java (1 hunks)
  • src/main/java/com/wayble/server/logging/service/UserActionLogService.java (1 hunks)
  • src/main/java/com/wayble/server/user/service/UserService.java (3 hunks)
  • src/main/resources/templates/admin/dashboard.html (3 hunks)
  • src/main/resources/templates/admin/user/user-detail.html (2 hunks)
  • src/main/resources/templates/admin/user/users.html (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 im...
Learnt from: seung-in-Yoo
PR: Wayble-Project/wayble-spring#37
File: src/main/java/com/wayble/server/user/entity/User.java:61-62
Timestamp: 2025-07-13T15:08:59.318Z
Learning: Java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 import 없이 사용 가능하다.

Applied to files:

  • src/main/java/com/wayble/server/logging/entity/UserActionLog.java
🧬 Code Graph Analysis (1)
src/main/java/com/wayble/server/admin/dto/DailyStatsDto.java (1)
src/main/java/com/wayble/server/common/response/CommonResponse.java (1)
  • Builder (8-42)
🔇 Additional comments (32)
src/main/java/com/wayble/server/ServerApplication.java (1)

18-18: LGTM! Elasticsearch 저장소 패키지 추가가 올바르게 설정되었습니다.

새로운 로깅 기능을 위한 com.wayble.server.logging.repository 패키지가 @EnableElasticsearchRepositories에 올바르게 추가되었습니다. 이는 UserActionLogRepository와 같은 새로운 Elasticsearch 저장소들이 Spring에서 정상적으로 스캔되고 등록될 수 있도록 합니다.

src/main/resources/templates/admin/user/users.html (3)

102-103: LGTM! 사용자 타입에 대한 안전한 null 체크가 추가되었습니다.

user.userType이 null일 경우를 처리하여 NullPointerException을 방지하고 "미설정"이라는 적절한 대체 텍스트를 표시합니다. CSS 클래스 할당도 안전하게 처리됩니다.


106-107: LGTM! 로그인 타입에 대한 안전한 null 체크가 추가되었습니다.

user.loginType이 null일 경우를 처리하여 안전한 템플릿 렌더링을 보장합니다. 적절한 대체 텍스트와 CSS 클래스가 제공됩니다.


131-131: LGTM! 성별에 대한 안전한 null 체크가 추가되었습니다.

user.gender가 null일 경우를 처리하여 템플릿 렌더링 오류를 방지합니다. 백엔드의 null-safe 처리와 일관성을 유지합니다.

src/main/resources/templates/admin/user/user-detail.html (3)

70-71: LGTM! 헤더 배지에서 사용자 타입에 대한 안전한 null 체크가 추가되었습니다.

페이지 헤더의 사용자 타입 배지에서 null 값을 안전하게 처리하고 적절한 CSS 스타일링과 대체 텍스트를 제공합니다.


74-75: LGTM! 헤더 배지에서 로그인 타입에 대한 안전한 null 체크가 추가되었습니다.

로그인 타입 배지에서 null 값을 안전하게 처리하여 템플릿 렌더링 오류를 방지합니다.


116-116: LGTM! 상세 정보 섹션에서 일관된 null 체크가 추가되었습니다.

로그인 타입과 사용자 타입 필드에서 null 값을 안전하게 처리하여 users.html 템플릿과 일관된 방식으로 "미설정" 대체 텍스트를 표시합니다.

Also applies to: 120-120

src/main/java/com/wayble/server/admin/dto/DailyStatsDto.java (1)

7-15: LGTM! 일일 통계 DTO가 잘 설계되었습니다.

  • Record 클래스 사용으로 불변성과 간결성을 보장합니다
  • @Builder 어노테이션으로 인스턴스 생성이 편리합니다
  • 필드 타입들이 데이터 특성에 적합합니다 (날짜는 LocalDate, 개수는 long)
  • 네이밍이 명확하고 일관성이 있습니다

관리자 대시보드의 일일 통계 표시 기능에 적합한 구조입니다.

src/main/java/com/wayble/server/admin/service/AdminUserService.java (3)

168-169: LGTM! 열거형 변환에 안전한 null 체크가 추가되었습니다.

데이터베이스에서 LoginTypeUserType 값이 null일 경우를 처리하여 IllegalArgumentException을 방지합니다. 이는 프론트엔드 템플릿의 null 체크와 일관성을 유지합니다.


184-185: LGTM! convertToDetailDto 메서드에서 일관된 null 체크가 적용되었습니다.

다른 변환 메서드들과 동일한 방식으로 열거형 변환 전 null 체크를 수행하여 코드의 일관성을 유지합니다.


206-207: LGTM! convertToDetailDtoWithStats 메서드에서 일관된 null 체크가 적용되었습니다.

세 개의 변환 메서드 모두에서 동일한 null-safe 열거형 변환 패턴을 사용하여 코드의 일관성과 안정성을 보장합니다.

src/main/java/com/wayble/server/user/service/UserService.java (2)

4-4: 의존성 추가 확인됨

UserActionLogService 의존성이 올바르게 import되었습니다.


19-19: 의존성 주입 확인됨

final 키워드와 함께 올바르게 의존성이 주입되었습니다.

src/main/java/com/wayble/server/auth/service/KakaoLoginService.java (3)

7-7: 의존성 추가 확인됨

UserActionLogService import가 올바르게 추가되었습니다.


30-30: 의존성 주입 확인됨

UserActionLogService가 올바르게 주입되었습니다.


72-85: 로깅 로직이 잘 구현되었습니다

신규 사용자와 기존 사용자를 구분하여 로깅하는 로직이 올바르게 구현되었습니다. null 체크도 일관되게 적용되었습니다.

src/main/resources/templates/admin/dashboard.html (4)

61-61: 반응형 그리드 레이아웃 개선

4개의 통계 카드를 수용하기 위한 그리드 레이아웃 변경이 적절합니다.


109-109: 아이콘 색상 변경 확인

방문자 카드의 아이콘 색상이 purple로 변경되어 새로 추가되는 가입자 카드(orange)와 구분됩니다.


118-118: 동적 데이터 바인딩 확인

Thymeleaf 표현식을 사용하여 실시간 방문자 수를 표시하도록 개선되었습니다.


125-144: 새로운 가입자 통계 카드 추가

오늘 가입자 수를 표시하는 새로운 카드가 기존 디자인과 일관되게 추가되었습니다. 적절한 아이콘과 색상(orange)을 사용했습니다.

src/main/java/com/wayble/server/logging/entity/UserActionLog.java (2)

14-18: Lombok과 Elasticsearch 어노테이션 확인

필요한 Lombok 어노테이션들이 올바르게 사용되었고, Elasticsearch Document 설정도 적절합니다.


21-40: 필드 매핑 검토

Elasticsearch 필드 타입 매핑이 적절하게 설정되었습니다:

  • ID는 자동 생성
  • userId는 Long 타입으로 적절
  • action, loginType, userType은 Keyword로 정확한 매칭 가능
  • userAgent는 Text로 전체 텍스트 검색 가능
  • timestamp는 Date 타입으로 시간 기반 쿼리 지원
src/main/java/com/wayble/server/auth/service/AuthService.java (3)

26-26: 의존성 주입 적절합니다.

UserActionLogService 의존성이 올바르게 추가되었습니다.


47-51: 로그인 시 활성 유저 로깅이 적절합니다.

비동기 처리와 하루 1회 제한으로 성능에 미치는 영향을 최소화했으며, 유저 타입 null 처리도 적절합니다.


85-89: 토큰 갱신 시 활성 유저 로깅이 적절합니다.

토큰 재발급 시에도 활성 유저 로그를 저장하여 일일 방문자 통계에 반영하는 로직이 적절합니다.

src/main/java/com/wayble/server/admin/service/AdminSystemService.java (1)

22-24: 새로운 의존성 추가가 적절합니다.

일일 통계 기능을 위한 필요한 서비스들이 올바르게 주입되었습니다.

src/main/java/com/wayble/server/admin/controller/AdminController.java (3)

3-3: 새로운 import 추가가 적절합니다.

일일 통계 기능을 위한 필요한 클래스들이 올바르게 import되었습니다.

Also applies to: 8-8, 19-19


80-81: 대시보드에 일일 통계 데이터 추가가 적절합니다.

기존 로직에 영향을 주지 않고 일일 통계 데이터가 올바르게 추가되었습니다.

Also applies to: 90-90


104-114: 일일 통계 API 엔드포인트가 잘 구현되었습니다.

인증 체크, 에러 처리, 응답 형태 모두 적절하며 일관된 패턴을 따르고 있습니다.

src/main/java/com/wayble/server/logging/repository/UserActionLogRepository.java (1)

10-22: countDistinctUserIdByActionAndTimestampBetween 지원 여부 재확인 필요

Spring Data Elasticsearch에서 파생된(Derived) countDistinct…By 방식이 자동으로 지원되는지 공식 문서나 통합 테스트로 반드시 확인하세요.
현재 인터페이스와 서비스 레이어에 선언·호출되어 있지만, Elasticsearch 쿼리로 올바르게 변환되지 않을 가능성이 있습니다.

– 점검 대상
• src/main/java/com/wayble/server/logging/repository/UserActionLogRepository.java 라인 13
• src/main/java/com/wayble/server/logging/service/UserActionLogService.java – 호출부

src/main/java/com/wayble/server/logging/service/UserActionLogService.java (2)

23-43: 사용자 등록 로깅 메서드가 잘 구현되었습니다.

비동기 처리, 예외 처리, 로깅 모두 적절하며 성능에 미치는 영향을 최소화했습니다.


46-52: 일일 통계 조회 메서드들이 적절합니다.

시간 범위 계산과 리포지토리 호출이 올바르게 구현되었습니다.

Also applies to: 87-93

Comment on lines +95 to +103
private HttpServletRequest getCurrentRequest() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes != null ? attributes.getRequest() : null;
} catch (Exception e) {
log.debug("Failed to get current request", e);
return null;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

HTTP 요청 컨텍스트 획득이 적절하지만 주의가 필요합니다.

비동기 컨텍스트에서 RequestContextHolder가 항상 작동하지 않을 수 있으므로 현재의 null 처리 방식이 적절합니다.

비동기 실행 시 RequestContextHolder 동작을 확인해보세요:


🌐 Web query:

Spring RequestContextHolder와 @Async 어노테이션 함께 사용할 때의 주의사항

💡 Result:

Spring에서 RequestContextHolder@Async 어노테이션을 함께 사용할 때는 ThreadLocal 기반 컨텍스트 관리의 한계를 반드시 주의해야 합니다. 비동기 메서드 실행 시 기존의 요청 스코프(RequestAttributes, 즉 request/session 등)가 새 스레드로 전달되지 않아, RequestContextHolder를 통한 값 조회가 실패하거나 null을 반환할 수 있습니다[1][5].

핵심 주의사항은 다음과 같습니다:

  1. RequestContextHolder의 동작 방식
    RequestContextHolder는 HttpServletRequest, Session 등의 요청/세션 기반 속성을 ThreadLocal에 저장하여 관리합니다[1][5]. 그러나 @Async가 붙은 메서드는 별도의 스레드에서 실행되므로, 해당 ThreadLocal 컨텍스트가 새 스레드에는 기본적으로 전달되지 않습니다.
    이로 인해, 비동기 메서드 내에서 RequestContextHolder.getRequestAttributes() 등을 호출하면 null이나 예상과 다른 값을 얻을 수 있습니다[5].

  2. 비동기 환경에서 Request/Session 정보 필요 시
    비동기 메서드가 요청 스코프의 데이터를 필요로 한다면,

    • 필요한 값을 미리 추출해 파라미터로 넘겨야 합니다.
    • 아니면, 컨텍스트 정보를 복사해 새 스레드에 전달하는 별도의 처리가 필요합니다[1][5].
  3. 컨텍스트 정보 복사/전달 패턴
    실제로, 컨텍스트 복사를 위해 다음과 같은 패턴이 사용됩니다:

    • TaskExecutor 등 스레드 풀 구현체(CustomExecutor)를 오버라이딩해서,
      호출 시점의 RequestAttributes를 별도로 캡처 및 전달(set/reset)하도록 래핑합니다[5]. 이런 식의 ContextAwareExecutor 구현이 널리 활용됩니다.
    • 또는 이미 필요한 데이터를 DTO/VO 형태로 변경 후, 메서드 파라미터로 직접 전달하는 것이 가장 안전합니다[1].
  4. RequestContextHolder를 반드시 비동기에서 사용해야 한다면

    • Spring DispatcherServlet의 setThreadContextInheritable(true)를 활용해 Context Inheritable ThreadLocal 기능을 켤 수 있지만, 이 방식은 여러 문제(메모리 누수, 컨텍스트 오염 등)로 인해 운영 환경에서 권장되지 않습니다[5].
    • 일반적으로는 ThreadLocal 자체를 통한 요청 데이터 공유를 피하고, Service 계층에서는 반드시 필요한 데이터만 메서드 파라미터로 명시적으로 전달하는 것이 유지보수, 안정성 면에서 안전합니다[1][5].
  5. @async 관련 일반 주의사항

    • 반드시 public 메서드에만 지정, 내부 메서드(self-invocation) 또는 private 메서드는 적용되지 않음[2][3][6][7].
    • 비동기 메서드의 예외 처리는 호출자에게 전파되지 않으니 별도의 AsyncUncaughtExceptionHandler 구현 필요[4].
    • 별도의 ThreadPool 설정 없이 사용할 경우, 비효율적이거나 시스템 리소스 고갈 우려 있음. ThreadPoolTaskExecutor를 사용하여 풀 관리 권장[6][8][9][10].

종합적으로 볼 때,
RequestContextHolder를 그대로 비동기 메서드에서 사용하고 싶다면 직접 컨텍스트를 옮겨주는 코드(Executor 래핑 등)가 필요하며, 가장 권장되는 것은 "비동기에 필요한 데이터를 명시적으로 파라미터로 넘긴다" 입니다[1][5].


  • "Spring RequestContextHolder를 이용해서 HttpServletRequest에 접근하는 메소드를 비동기로 처리하게 되면 thread가 달라져서 Spring RequestContextHolder를 이용하는데 실패하게 됩니다." [1]
  • "RequestContextHolder는 ThreadLocal을 사용하여 attributes 들을 저장하고 있다. ... 비동기적으로 작업을 처리해야 할때가 있는데 이때 RequestContextHolder에 접근하게 되면 Thread가 달라 attributes가 원하는 값이 나오지 않게 된다." [5]
  • "HttpServletRequest에서 필요한 값들을 모아서 VO(Value Object)로 만들고 파라미터로 service에게 넘긴다면 문제가 되지 않습니다." [1]
  • "ThreadLocal 사용 시 내용 복사" [3]
  • "ContextAwarePoolExecutor" 등 별도 구현을 통해 RequestAttributes 복사 후 세팅 [5]

Citations:


비동기(@async) 환경에서 RequestContextHolder 사용 주의

getCurrentRequest()로 HTTP 요청 컨텍스트를 획득하고 null 처리를 하는 것은 안전하지만, @async 메서드(별도 스레드)에서는 ThreadLocal 기반의 RequestAttributes가 전파되지 않아 항상 null이 반환될 수 있습니다.
필요 시 아래 중 하나를 적용해 주세요:

  • 비동기 메서드에 필요한 요청 정보를 미리 추출하여 DTO/파라미터로 전달
  • ContextAware Executor(또는 TaskExecutor 래퍼)를 구현해 호출 시점의 RequestAttributes를 캡처‧전달
  • Spring의 inheritable ThreadLocal(setThreadContextInheritable(true))은 메모리 누수 위험이 있으므로 권장되지 않음

추가 검토가 필요하다면, 비동기 로직에서 실제로 RequestContextHolder.getRequestAttributes()가 null인지 확인하고 적절한 전달 방식을 선택하세요.

🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/logging/service/UserActionLogService.java
around lines 95 to 103, the getCurrentRequest() method uses RequestContextHolder
to obtain the HTTP request, which returns null in @Async methods due to
ThreadLocal context not being propagated. To fix this, avoid calling
getCurrentRequest() directly in asynchronous methods; instead, extract and pass
the necessary request information as method parameters or DTOs before the async
call, or implement a ContextAware Executor that captures and transfers the
RequestAttributes to the async thread. Do not rely on inheritable ThreadLocal
due to memory leak risks.

@seung-in-Yoo seung-in-Yoo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

관리자 페이지 퀄리티가 ㅎㄷㄷ 하네요 ^.^ 수고하셨습니다~!!

@hyoinYang hyoinYang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다 👍

@KiSeungMin KiSeungMin merged commit 59d2c31 into develop Aug 6, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 feature 기능 구현 및 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants