Skip to content

feat: 관리자 대시보드 문제 등록 조회 API추가 및 월별 접속자 조회 리팩토링#91

Merged
2jiyong merged 11 commits into
devfrom
feat/dashboard
May 20, 2026
Merged

feat: 관리자 대시보드 문제 등록 조회 API추가 및 월별 접속자 조회 리팩토링#91
2jiyong merged 11 commits into
devfrom
feat/dashboard

Conversation

@2jiyong

@2jiyong 2jiyong commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Ⅰ. PR 내용 설명
관리자 대시보드에 문제 등록 현황 조회 API를 추가하고, 기존 월별 접속자 조회를 보강했습니다.

  1. 문제 등록 현황 조회 API (GET /api/v1/admin/dashboard/problems)
  • 페이지네이션 기반 문제 목록 조회 (기본 size 20, 최대 100)
  • 응답 필드: 문제 ID, 단원명, 대단원명, 문제 유형, AI 풀이 호출 수, 조회 수, 등록 일시, 오답 완료 여부, 등록 사용자
    권한
  1. 문제 조회/풀이 카운터 집계
  • Problem 엔티티에 viewCount, aiSolutionCount 컬럼 추가
  • 문제 상세 조회 API 호출 시 viewCount 증가
  • AI 풀이 요청 API 호출 시 aiSolutionCount 증가 (캐시 적중 포함)
  • 동시성 안전성을 위해 원자적 UPDATE(SET x = x + 1) 방식으로 구현
  1. 월별 일별 접속자 조회 보강
  • DAU 집계에서 ADMIN 권한 사용자 제외
  • 일별 신규 가입자 수(newUsers) 응답 필드 추가
  • DashboardDailyAccessItem.count → dailyVisitors로 필드명 변경

Ⅱ. 관련 이슈

Ⅲ. 검증 방법

Ⅳ. 리뷰 시 참고 사항

  1. Breaking change — 어드민 프론트 동기화 필요
    DashboardDailyAccessItem의 JSON 필드명이 count → dailyVisitors로 변경됐습니다. 어드민 프론트가 기존 필드를 참조
    중이라면 동시 배포가 필요합니다.

  2. 카운터 증가 구현 방식
    조회수/AI 호출수 증가는 엔티티 load-modify-save 대신 JPQL @Modifying UPDATE로 처리했습니다.

  • DB 왕복 1회 (SELECT + UPDATE → UPDATE 1회)
  • InnoDB row-level 락으로 lost update 방지
  • @LastModifiedDate 라이프사이클을 거치지 않아 단순 조회가 updated_at을 갱신하지 않음

@jihun4452 jihun4452 changed the title Feat/dashboard feat: 관리자 대시보드 문제 등록 조회 API추가 및 월별 접속자 조회 리팩토링 May 16, 2026
.select(access.accessDate, access.userId.countDistinct())
.from(access)
.where(access.accessDate.between(start, end))
.join(user).on(user.id.eq(access.userId))

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.

admin 제외의 의도는 맞는데, 이 코드는 제가 봤을땐, user 테이블이랑 JOIN을 태우게 돼서, 데이터가 쌓일수록 부담이 커질 수 있을 거 같아요, ADMIN계정은 소수이기 때문에, user테이블에서 admin id목록을 먼저 조회 한 후,
userId NOT IN (adminIds) 조건으로 처리하는 게 JOIN 오버헤드 없이 더 깔끔할 것 같습니다.

public DashboardProblemsResponse getProblems(Pageable pageable) {
List<DashboardProblemItem> content = dashboardProblemQueryPort.findProblems(pageable);
long totalElements = dashboardProblemQueryPort.countProblems();
int totalPages = totalElements == 0 ? 0 : (int) ((totalElements + pageable.getPageSize() - 1) / pageable.getPageSize());

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.

findProblems()와 countProblems()가 각각 독립적으로 작성되어 있어서, 나중에 findProblems()에
필터 조건이 추가될 때 countProblems()에 같은 조건을 빠뜨리면 페이지네이션 totalElements가
틀려지는 버그가 생길 수 있을거같아여. where 조건을 공유하는 private 메서드로 묶어두면 어떨까요?

.orElseThrow(() -> new ProblemException(ErrorCode.PROBLEM_NOT_FOUND));

problemRepositoryPort.findById(row.problemId())
.ifPresent(Problem::incrementViewCount);

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.

Query 서비스인데 내부에서 쓰기 작업(viewCount 증가)을 하고 있어서, CQS 관점에서 좀 어색하다고 생각이 들기도하는거같아요
클래스 레벨이 readOnly = true인데 이 메서드만 @transactional로 오버라이드하고 있어서, 나중에
다른 개발자가 이 서비스를 읽기 전용 컨텍스트에서 호출하거나 캐싱을 붙이게 되면 카운트 증가가 누락될 수 있지않을까요?. viewCount 증가는 커맨드 서비스 쪽으로 옮기거나, 컨트롤러에서 조회
후 별도로 호출하는 구조가 더 자연스러울 것 같은데 어떠신가요?

Problem problem = problemRepositoryPort.findByIdAndUserId(problemId, userId)
.orElseThrow(() -> new ProblemException(ErrorCode.PROBLEM_NOT_FOUND));

problemRepositoryPort.incrementAiSolutionCount(problemId);

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.

이 시점에 카운트를 올리면 요청 성공/실패 여부와 무관하게 항상 올라가는데 이 부분은 어떻게 생각하시나요??
AiSolutuonCount 필드명보다는 aiSolutinReqCount 같은건 어떨까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

일단은 수정 안하고 프론트랑 나중에 말 맞춰서 수정해야할 것 같습니당

size = DEFAULT_SIZE;
if (size > MAX_SIZE)
size = MAX_SIZE;
}

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.

if문에 중괄호가 없어서 다른 코드들과 코드 스타일이 조금 다른거 같은데 어떻게 생각하시나요?
이 방법을 사용하셔야한다면 혹시 이유가 있을까요?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이건 그냥 원래 있던 코드 스타일 따라 했습니당

@jihun4452

Copy link
Copy Markdown
Member

이번 대시보드 기능 구현 고생 많으셨습니다! 전반적인 헥사고날 아키텍처 구조를 잘 따르고 있고,
원자적 카운트 증가로 직접 리팩토링하신 부분도 좋았습니다. 몇 가지 피드백 남겼는데 참고해서 같이
개선해봐요 :)

@2jiyong 2jiyong merged commit aa07fc0 into dev May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants