Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -3,8 +3,9 @@
import com.example.nexus.app.datacenter.controller.dto.response.datacenter.*;
import com.example.nexus.app.datacenter.controller.dto.response.datacenter.InsightsResponse.FeedbackItemDto;
import com.example.nexus.app.datacenter.controller.dto.response.datacenter.QualityFeedbackResponse.ProblemLocationDto;
import com.example.nexus.app.datacenter.domain.ParticipantFeedback;
import com.example.nexus.app.datacenter.repository.ParticipantFeedbackRepository;
import com.example.nexus.app.feedback.domain.Feedback;
import com.example.nexus.app.feedback.domain.BugType;
import com.example.nexus.app.feedback.repository.FeedbackRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -24,7 +25,7 @@
@Slf4j
public class DataCenterService {

private final ParticipantFeedbackRepository feedbackRepository;
private final FeedbackRepository feedbackRepository;
private final KeywordAnalyzer keywordAnalyzer;

/**
Expand All @@ -39,12 +40,12 @@ public DataCenterResponse getDataCenterData(Long postId, int days) {
LocalDateTime startDate = endDate.minusDays(days);

// 기간 내 피드백 조회
List<ParticipantFeedback> feedbacks = feedbackRepository.findByPostIdAndDateRange(
List<Feedback> feedbacks = feedbackRepository.findByPostIdAndDateRange(
postId, startDate, endDate);

// 전주 데이터 (비교용)
LocalDateTime lastWeekStart = startDate.minusDays(7);
List<ParticipantFeedback> lastWeekFeedbacks = feedbackRepository.findByPostIdAndDateRange(
List<Feedback> lastWeekFeedbacks = feedbackRepository.findByPostIdAndDateRange(
postId, lastWeekStart, startDate);

return DataCenterResponse.builder()
Expand All @@ -60,8 +61,8 @@ public DataCenterResponse getDataCenterData(Long postId, int days) {
* 요약 카드 데이터 생성
*/
private DataCenterSummaryResponse buildSummary(Long postId,
List<ParticipantFeedback> feedbacks,
List<ParticipantFeedback> lastWeekFeedbacks,
List<Feedback> feedbacks,
List<Feedback> lastWeekFeedbacks,
int days) {
long totalParticipants = feedbacks.size();
long lastWeekParticipants = lastWeekFeedbacks.size();
Expand All @@ -73,13 +74,13 @@ private DataCenterSummaryResponse buildSummary(Long postId,
// 평균 만족도
double avgSatisfaction = feedbacks.stream()
.filter(f -> f.getOverallSatisfaction() != null)
.mapToInt(ParticipantFeedback::getOverallSatisfaction)
.mapToInt(Feedback::getOverallSatisfaction)
.average()
.orElse(0.0);

double lastWeekAvgSatisfaction = lastWeekFeedbacks.stream()
.filter(f -> f.getOverallSatisfaction() != null)
.mapToInt(ParticipantFeedback::getOverallSatisfaction)
.mapToInt(Feedback::getOverallSatisfaction)
.average()
.orElse(0.0);

Expand Down Expand Up @@ -131,19 +132,19 @@ private DataCenterSummaryResponse buildSummary(Long postId,
/**
* 전반 평가 데이터 생성
*/
private OverallEvaluationResponse buildOverallEvaluation(List<ParticipantFeedback> feedbacks) {
private OverallEvaluationResponse buildOverallEvaluation(List<Feedback> feedbacks) {
// 평균 점수 계산
double avgSatisfaction = calculateAverage(feedbacks, ParticipantFeedback::getOverallSatisfaction);
double avgRecommendation = calculateAverage(feedbacks, ParticipantFeedback::getRecommendationIntent);
double avgReuse = calculateAverage(feedbacks, ParticipantFeedback::getReuseIntent);
double avgSatisfaction = calculateAverage(feedbacks, Feedback::getOverallSatisfaction);
double avgRecommendation = calculateAverage(feedbacks, Feedback::getRecommendationIntent);
double avgReuse = calculateAverage(feedbacks, Feedback::getReuseIntent);

// 분포 계산
Map<Integer, Long> satisfactionDist = calculateDistribution(feedbacks,
ParticipantFeedback::getOverallSatisfaction);
Feedback::getOverallSatisfaction);
Map<Integer, Long> recommendationDist = calculateDistribution(feedbacks,
ParticipantFeedback::getRecommendationIntent);
Feedback::getRecommendationIntent);
Map<Integer, Long> reuseDist = calculateDistribution(feedbacks,
ParticipantFeedback::getReuseIntent);
Feedback::getReuseIntent);

return OverallEvaluationResponse.builder()
.averageSatisfaction(Math.round(avgSatisfaction * 10) / 10.0)
Expand All @@ -158,10 +159,11 @@ private OverallEvaluationResponse buildOverallEvaluation(List<ParticipantFeedbac
/**
* 품질 피드백 데이터 생성
*/
private QualityFeedbackResponse buildQualityFeedback(List<ParticipantFeedback> feedbacks) {
// 불편 요소 Top 3
private QualityFeedbackResponse buildQualityFeedback(List<Feedback> feedbacks) {
// 불편 요소 Top 3 (Feedback.mostInconvenient를 문자열로 변환)
Map<String, Long> inconvenientElements = feedbacks.stream()
.flatMap(f -> f.getInconvenientElements().stream())
.filter(f -> f.getMostInconvenient() != null)
.map(f -> f.getMostInconvenient().getDescription())
.collect(Collectors.groupingBy(e -> e, Collectors.counting()))
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
Expand All @@ -182,11 +184,12 @@ private QualityFeedbackResponse buildQualityFeedback(List<ParticipantFeedback> f

// 만족도 점수 분포 (비율)
Map<Integer, Double> satisfactionScoreDist = calculatePercentageDistribution(feedbacks,
ParticipantFeedback::getOverallSatisfaction);
Feedback::getOverallSatisfaction);

// 문제 유형 비중
// 문제 유형 비중 (Feedback.bugTypes를 문자열로 변환)
Map<String, Double> problemTypeProportions = feedbacks.stream()
.flatMap(f -> f.getProblemTypes().stream())
.flatMap(f -> f.getBugTypes().stream())
.map(BugType::getDescription)
.collect(Collectors.groupingBy(t -> t, Collectors.counting()))
.entrySet().stream()
.collect(Collectors.toMap(
Expand All @@ -198,9 +201,9 @@ private QualityFeedbackResponse buildQualityFeedback(List<ParticipantFeedback> f

// 주요 문제 발생 위치 (Top 5)
List<ProblemLocationDto> topProblemLocations = feedbacks.stream()
.filter(f -> f.getProblemLocation() != null && !f.getProblemLocation().isEmpty())
.filter(f -> f.getBugLocation() != null && !f.getBugLocation().isEmpty())
.collect(Collectors.groupingBy(
ParticipantFeedback::getProblemLocation,
Feedback::getBugLocation,
Collectors.counting()
))
.entrySet().stream()
Expand All @@ -209,8 +212,9 @@ private QualityFeedbackResponse buildQualityFeedback(List<ParticipantFeedback> f
.map(e -> {
// 해당 위치의 주요 문제 유형 찾기
String mainProblemType = feedbacks.stream()
.filter(f -> e.getKey().equals(f.getProblemLocation()))
.flatMap(f -> f.getProblemTypes().stream())
.filter(f -> e.getKey().equals(f.getBugLocation()))
.flatMap(f -> f.getBugTypes().stream())
.map(BugType::getDescription)
.findFirst()
.orElse("오류");

Expand Down Expand Up @@ -243,12 +247,13 @@ private QualityFeedbackResponse buildQualityFeedback(List<ParticipantFeedback> f
/**
* 기능별 사용성 평가 데이터 생성
*/
private UsabilityEvaluationResponse buildUsabilityEvaluation(List<ParticipantFeedback> feedbacks) {
double functionalityScore = calculateAverage(feedbacks, ParticipantFeedback::getFunctionalityScore);
double comprehensibilityScore = calculateAverage(feedbacks, ParticipantFeedback::getComprehensibilityScore);
double loadingSpeedScore = calculateAverage(feedbacks, ParticipantFeedback::getLoadingSpeedScore);
double responseTimingScore = calculateAverage(feedbacks, ParticipantFeedback::getResponseTimingScore);
double stabilityScore = calculateAverage(feedbacks, ParticipantFeedback::getStabilityScore);
private UsabilityEvaluationResponse buildUsabilityEvaluation(List<Feedback> feedbacks) {
double functionalityScore = calculateAverage(feedbacks, Feedback::getFunctionalityScore);
double comprehensibilityScore = calculateAverage(feedbacks, Feedback::getComprehensibilityScore);
double loadingSpeedScore = calculateAverage(feedbacks, Feedback::getSpeedScore);
double responseTimingScore = calculateAverage(feedbacks, Feedback::getResponseTimingScore);
// Feedback에는 stabilityScore가 없으므로 0.0으로 설정
double stabilityScore = 0.0;

return UsabilityEvaluationResponse.builder()
.functionalityScore(Math.round(functionalityScore * 10) / 10.0)
Expand All @@ -262,37 +267,37 @@ private UsabilityEvaluationResponse buildUsabilityEvaluation(List<ParticipantFee
/**
* 인사이트 데이터 생성 (키워드 분석 포함)
*/
private InsightsResponse buildInsights(List<ParticipantFeedback> feedbacks) {
// 좋았던 점 피드백
private InsightsResponse buildInsights(List<Feedback> feedbacks) {
// 좋았던 점 피드백 (Feedback.goodPoints)
List<FeedbackItemDto> positiveFeedbacks = feedbacks.stream()
.filter(f -> f.getPositiveFeedback() != null && !f.getPositiveFeedback().isEmpty())
.filter(f -> f.getGoodPoints() != null && !f.getGoodPoints().isEmpty())
.map(f -> FeedbackItemDto.builder()
.feedbackId(f.getId())
.summary(keywordAnalyzer.summarize(f.getPositiveFeedback()))
.fullContent(f.getPositiveFeedback())
.emoji(keywordAnalyzer.selectEmoji(f.getPositiveFeedback(), true))
.summary(keywordAnalyzer.summarize(f.getGoodPoints()))
.fullContent(f.getGoodPoints())
.emoji(keywordAnalyzer.selectEmoji(f.getGoodPoints(), true))
.build())
.collect(Collectors.toList());

// 개선 제안 피드백
// 개선 제안 피드백 (Feedback.improvementSuggestions)
List<FeedbackItemDto> improvementSuggestions = feedbacks.stream()
.filter(f -> f.getImprovementSuggestion() != null && !f.getImprovementSuggestion().isEmpty())
.filter(f -> f.getImprovementSuggestions() != null && !f.getImprovementSuggestions().isEmpty())
.map(f -> FeedbackItemDto.builder()
.feedbackId(f.getId())
.summary(keywordAnalyzer.summarize(f.getImprovementSuggestion()))
.fullContent(f.getImprovementSuggestion())
.emoji(keywordAnalyzer.selectEmoji(f.getImprovementSuggestion(), false))
.summary(keywordAnalyzer.summarize(f.getImprovementSuggestions()))
.fullContent(f.getImprovementSuggestions())
.emoji(keywordAnalyzer.selectEmoji(f.getImprovementSuggestions(), false))
.build())
.collect(Collectors.toList());

// 키워드 분석 (긍정 피드백 + 개선 제안 모두 분석)
List<String> allTexts = new ArrayList<>();
feedbacks.forEach(f -> {
if (f.getPositiveFeedback() != null) {
allTexts.add(f.getPositiveFeedback());
if (f.getGoodPoints() != null && !f.getGoodPoints().isEmpty()) {
allTexts.add(f.getGoodPoints());
}
if (f.getImprovementSuggestion() != null) {
allTexts.add(f.getImprovementSuggestion());
if (f.getImprovementSuggestions() != null && !f.getImprovementSuggestions().isEmpty()) {
allTexts.add(f.getImprovementSuggestions());
}
});

Expand Down Expand Up @@ -320,8 +325,8 @@ private double calculateChangeRate(double current, double previous) {
/**
* 평균 계산
*/
private double calculateAverage(List<ParticipantFeedback> feedbacks,
java.util.function.Function<ParticipantFeedback, Integer> getter) {
private double calculateAverage(List<Feedback> feedbacks,
java.util.function.Function<Feedback, Integer> getter) {
return feedbacks.stream()
.map(getter)
.filter(Objects::nonNull)
Expand All @@ -333,8 +338,8 @@ private double calculateAverage(List<ParticipantFeedback> feedbacks,
/**
* 분포 계산 (1~5점별 개수)
*/
private Map<Integer, Long> calculateDistribution(List<ParticipantFeedback> feedbacks,
java.util.function.Function<ParticipantFeedback, Integer> getter) {
private Map<Integer, Long> calculateDistribution(List<Feedback> feedbacks,
java.util.function.Function<Feedback, Integer> getter) {
Map<Integer, Long> distribution = feedbacks.stream()
.map(getter)
.filter(Objects::nonNull)
Expand All @@ -351,8 +356,8 @@ private Map<Integer, Long> calculateDistribution(List<ParticipantFeedback> feedb
/**
* 비율 분포 계산 (%)
*/
private Map<Integer, Double> calculatePercentageDistribution(List<ParticipantFeedback> feedbacks,
java.util.function.Function<ParticipantFeedback, Integer> getter) {
private Map<Integer, Double> calculatePercentageDistribution(List<Feedback> feedbacks,
java.util.function.Function<Feedback, Integer> getter) {
Map<Integer, Long> countDist = calculateDistribution(feedbacks, getter);
long total = feedbacks.size();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.nexus.app.feedback.repository;

import com.example.nexus.app.feedback.domain.Feedback;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -23,5 +24,12 @@ public interface FeedbackRepository extends JpaRepository<Feedback, Long> {

@Query("SELECT COUNT(f) FROM Feedback f WHERE f.participation.post.id = :postId")
Long countByPostId(@Param("postId") Long postId);

@Query("SELECT f FROM Feedback f JOIN FETCH f.participation p WHERE p.post.id = :postId " +
"AND f.createdAt >= :startDate AND f.createdAt < :endDate ORDER BY f.createdAt DESC")
List<Feedback> findByPostIdAndDateRange(
@Param("postId") Long postId,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate);
}

Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ public FeedbackResponse submitFeedback(FeedbackSubmitRequest request, Long userI
feedbackDraftRepository.findByParticipationId(participation.getId())
.ifPresent(feedbackDraftRepository::delete);

if (participation.isApproved()) {
participation.complete();
participationRepository.save(participation);
log.info("피드백 제출로 인한 참여 완료 처리: participationId={}, status={}",
participation.getId(), participation.getStatus());
}

log.info("피드백 제출 완료: feedbackId={}, participationId={}, userId={}",
savedFeedback.getId(), participation.getId(), userId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.example.nexus.app.global.config;

import com.example.nexus.app.global.code.status.ErrorStatus;
import com.example.nexus.app.global.exception.GeneralException;
import com.example.nexus.app.global.oauth.domain.CustomUserDetails;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.AuditorAware;
import org.springframework.lang.NonNull;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
Expand All @@ -16,14 +15,15 @@
public class AuditorAwareImpl implements AuditorAware<Long> {

@Override
@NonNull
public Optional<Long> getCurrentAuditor() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();

if (authentication == null || !authentication.isAuthenticated()
|| "anonymousUser".equals(authentication.getPrincipal())) {
log.warn("인증되지 않은 사용자 - Optional.empty() 반환");
// throw new GeneralException(ErrorStatus.UNAUTHORIZED);
return Optional.empty();
}

// CustomUserDetails에서 직접 userId 가져오기
Expand Down