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,6 +3,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -12,6 +13,7 @@
import com.olive.pribee.global.common.ResponseDto;
import com.olive.pribee.global.enums.DetectKeyword;
import com.olive.pribee.module.auth.domain.entity.Member;
import com.olive.pribee.module.feed.dto.res.FbPostDetailRes;
import com.olive.pribee.module.feed.dto.res.FbPostTotalRes;
import com.olive.pribee.module.feed.service.FbPostService;

Expand All @@ -25,17 +27,24 @@ public class FbPostController implements FbPostControllerDocs {
private final FbPostService fbPostService;

// 개별 게시물 조회
@GetMapping("/{id}")
public ResponseEntity<ResponseDto> getDetailPost(@AuthenticationPrincipal Member member,
@PathVariable(name = "id") Long postId) {
FbPostDetailRes res = fbPostService.getDetailPost(member.getId(), postId);
return ResponseEntity.status(200).body(DataResponseDto.of(res, 200));

}

// 전체 게시물 조회
@GetMapping
public ResponseEntity<ResponseDto> getExhibitions(
public ResponseEntity<ResponseDto> getTotalPost(
@AuthenticationPrincipal Member member,
@Schema(description = "필터를 의미합니다.") @RequestParam(name = "detectType") @Nullable DetectKeyword detectType,
@Schema(description = "검색어를 의미합니다.") @RequestParam(name = "keyword") @Nullable String keyword,
@Schema(description = "0번부터 시작합니다. 조회할 페이지 번호를 의미합니다.") @RequestParam(name = "page") int page,
@Schema(description = "조회할 페이지 크기를 의미합니다.") @RequestParam(name = "size") int size) {
FbPostTotalRes resPage = fbPostService.getTotalPost(member.getId(), detectType, keyword, page, size);
return ResponseEntity.status(200).body(DataResponseDto.of(resPage, 200));
FbPostTotalRes resDto = fbPostService.getTotalPost(member.getId(), detectType, keyword, page, size);
return ResponseEntity.status(200).body(DataResponseDto.of(resDto, 200));
}

// 게시물 첨부 조회
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,23 @@ public class DetectKeywordInPhotoBoundingBox extends BaseTime {
private DetectKeywordInPhoto detectKeywordInPhoto;

@NotNull
private int y_position;
private int y;

@NotNull
private int x_position;
private int x;

@NotNull
private int width;

@NotNull
private int height;

public static DetectKeywordInPhotoBoundingBox of(@NotNull DetectKeywordInPhoto detectKeywordInPhoto, int y_position,
int x_position, int width, int height) {
public static DetectKeywordInPhotoBoundingBox of(@NotNull DetectKeywordInPhoto detectKeywordInPhoto, int y,
int x, int width, int height) {
return DetectKeywordInPhotoBoundingBox.builder()
.detectKeywordInPhoto(detectKeywordInPhoto)
.y_position(y_position)
.x_position(x_position)
.y(y)
.x(x)
.width(width)
.height(height)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.stereotype.Repository;

import com.olive.pribee.global.enums.DetectKeyword;
import com.olive.pribee.module.feed.dto.res.FbPostDetailRes;
import com.olive.pribee.module.feed.dto.res.FbPostRes;
import com.olive.pribee.module.feed.dto.res.FbPostTotalRes;

Expand All @@ -15,4 +16,6 @@ Page<FbPostRes> getFbPostByKeywordAndPaging(Long userId, DetectKeyword detectTyp

FbPostTotalRes getFbPostTotal(Long memberId, DetectKeyword detectType, String keyword,
Pageable pageable);

FbPostDetailRes getFbPostDetail(Long memberId, Long fbPostId);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.olive.pribee.module.feed.domain.repository.custom;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -11,16 +13,26 @@
import org.springframework.util.StringUtils;

import com.olive.pribee.global.enums.DetectKeyword;
import com.olive.pribee.global.enums.DetectLikelihood;
import com.olive.pribee.global.error.exception.AppException;
import com.olive.pribee.module.feed.domain.entity.DetectKeywordInMessage;
import com.olive.pribee.module.feed.domain.entity.FbPost;
import com.olive.pribee.module.feed.domain.entity.FbPostPictureUrl;
import com.olive.pribee.module.feed.domain.entity.QDetectKeywordInMessage;
import com.olive.pribee.module.feed.domain.entity.QDetectKeywordInPhoto;
import com.olive.pribee.module.feed.domain.entity.QDetectKeywordInPhotoBoundingBox;
import com.olive.pribee.module.feed.domain.entity.QFbPost;
import com.olive.pribee.module.feed.domain.entity.QFbPostPictureUrl;
import com.olive.pribee.module.feed.dto.res.BoundingBoxRes;
import com.olive.pribee.module.feed.dto.res.FbPostDetailRes;
import com.olive.pribee.module.feed.dto.res.FbPostRes;
import com.olive.pribee.module.feed.dto.res.FbPostTotalRes;
import com.olive.pribee.module.feed.dto.res.MessageDetectRes;
import com.olive.pribee.module.feed.dto.res.PhotoDetectRes;
import com.olive.pribee.module.feed.dto.res.PhotoUrlRes;
import com.olive.pribee.module.feed.error.FbPostErrorCode;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;
Expand All @@ -31,82 +43,82 @@ public class FbPostRepositoryImpl implements FbPostRepositoryCustom {

private final JPAQueryFactory jpaQueryFactory;

@Override
public Page<FbPostRes> getFbPostByKeywordAndPaging(Long memberId, DetectKeyword detectType, String keyword,
Pageable pageable) {

QFbPost fbPost = QFbPost.fbPost;
QDetectKeywordInMessage detectKeywordInMessage = QDetectKeywordInMessage.detectKeywordInMessage;
QDetectKeywordInPhoto detectKeywordInPhoto = QDetectKeywordInPhoto.detectKeywordInPhoto;

BooleanBuilder builder = new BooleanBuilder();
builder.and(fbPost.member.id.eq(memberId));

if (detectType != null) {
builder.and(detectKeywordInMessage.keyword.eq(detectType)
.or(detectKeywordInPhoto.keyword.eq(detectType)));
}

if (StringUtils.hasText(keyword)) {
builder.and(fbPost.message.containsIgnoreCase(keyword));
}

// 1. 페이징 적용하여 FbPost ID 조회
List<Long> postIds = jpaQueryFactory.select(fbPost.id)
.from(fbPost)
.where(builder)
.orderBy(fbPost.createdTime.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

if (postIds.isEmpty()) {
return new PageImpl<>(List.of(), pageable, 0);
}

// 2. FbPost 엔티티 조회 (Batch Fetch 적용)
List<FbPost> posts = jpaQueryFactory.selectFrom(fbPost)
.where(fbPost.id.in(postIds))
.fetch();

// 3. detectKeywordInMessages 조회 (Batch Fetch)
Map<Long, List<DetectKeywordInMessage>> detectKeywordMap = jpaQueryFactory
.selectFrom(detectKeywordInMessage)
.where(detectKeywordInMessage.fbPost.id.in(postIds))
.fetch()
.stream()
.collect(Collectors.groupingBy(d -> d.getFbPost().getId()));

// 4. fbPostPictureUrls 조회 (Batch Fetch)
Map<Long, List<FbPostPictureUrl>> pictureUrlMap = jpaQueryFactory
.selectFrom(QFbPostPictureUrl.fbPostPictureUrl)
.where(QFbPostPictureUrl.fbPostPictureUrl.fbPost.id.in(postIds))
.fetch()
.stream()
.collect(Collectors.groupingBy(p -> p.getFbPost().getId()));

// 5. DTO 변환
List<FbPostRes> resDtos = posts.stream().map(post -> FbPostRes.of(
post.getId(),
post.getCreatedTime(),
pictureUrlMap.getOrDefault(post.getId(), List.of()).stream()
.findFirst().map(FbPostPictureUrl::getPhotoUrl).orElse(null),
detectKeywordMap.getOrDefault(post.getId(), List.of()).stream()
.map(DetectKeywordInMessage::getKeyword)
.distinct()
.collect(Collectors.toList())
)).collect(Collectors.toList());

long total = jpaQueryFactory.select(fbPost.count())
.from(fbPost)
.where(builder)
.fetchOne();

return new PageImpl<>(resDtos, pageable, total);
}


@Override
@Override
public Page<FbPostRes> getFbPostByKeywordAndPaging(Long memberId, DetectKeyword detectType, String keyword,
Pageable pageable) {

QFbPost fbPost = QFbPost.fbPost;
QDetectKeywordInMessage detectKeywordInMessage = QDetectKeywordInMessage.detectKeywordInMessage;
QDetectKeywordInPhoto detectKeywordInPhoto = QDetectKeywordInPhoto.detectKeywordInPhoto;

BooleanBuilder builder = new BooleanBuilder();
builder.and(fbPost.member.id.eq(memberId));

if (detectType != null) {
builder.and(detectKeywordInMessage.keyword.eq(detectType)
.or(detectKeywordInPhoto.keyword.eq(detectType)));
}

if (StringUtils.hasText(keyword)) {
builder.and(fbPost.message.containsIgnoreCase(keyword));
}

// 1. 페이징 적용하여 FbPost ID 조회
List<Long> postIds = jpaQueryFactory.select(fbPost.id)
.from(fbPost)
.where(builder)
.orderBy(fbPost.createdTime.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

if (postIds.isEmpty()) {
return new PageImpl<>(List.of(), pageable, 0);
}

// 2. FbPost 엔티티 조회 (Batch Fetch 적용)
List<FbPost> posts = jpaQueryFactory.selectFrom(fbPost)
.where(fbPost.id.in(postIds))
.fetch();

// 3. detectKeywordInMessages 조회 (Batch Fetch)
Map<Long, List<DetectKeywordInMessage>> detectKeywordMap = jpaQueryFactory
.selectFrom(detectKeywordInMessage)
.where(detectKeywordInMessage.fbPost.id.in(postIds))
.fetch()
.stream()
.collect(Collectors.groupingBy(d -> d.getFbPost().getId()));

// 4. fbPostPictureUrls 조회 (Batch Fetch) - 오래된 순으로 정렬
Map<Long, List<FbPostPictureUrl>> pictureUrlMap = jpaQueryFactory
.selectFrom(QFbPostPictureUrl.fbPostPictureUrl)
.where(QFbPostPictureUrl.fbPostPictureUrl.fbPost.id.in(postIds))
.orderBy(QFbPostPictureUrl.fbPostPictureUrl.createdAt.asc())
.fetch()
.stream()
.collect(Collectors.groupingBy(p -> p.getFbPost().getId()));

// 5. DTO 변환
List<FbPostRes> resDtos = posts.stream().map(post -> FbPostRes.of(
post.getId(),
post.getCreatedTime(),
pictureUrlMap.getOrDefault(post.getId(), List.of()).stream()
.findFirst().map(FbPostPictureUrl::getPhotoUrl).orElse(null),
detectKeywordMap.getOrDefault(post.getId(), List.of()).stream()
.map(DetectKeywordInMessage::getKeyword)
.distinct()
.collect(Collectors.toList())
)).collect(Collectors.toList());

long total = jpaQueryFactory.select(fbPost.count())
.from(fbPost)
.where(builder)
.fetchOne();

return new PageImpl<>(resDtos, pageable, total);
}

@Override
public FbPostTotalRes getFbPostTotal(Long memberId, DetectKeyword detectType, String keyword,
Pageable pageable) {

Expand Down Expand Up @@ -140,4 +152,112 @@ public FbPostTotalRes getFbPostTotal(Long memberId, DetectKeyword detectType, St
fbPostResPage
);
}

@Override
public FbPostDetailRes getFbPostDetail(Long memberId, Long fbPostId) {
QFbPost fbPost = QFbPost.fbPost;
QDetectKeywordInMessage detectKeywordInMessage = QDetectKeywordInMessage.detectKeywordInMessage;
QDetectKeywordInPhoto detectKeywordInPhoto = QDetectKeywordInPhoto.detectKeywordInPhoto;
QFbPostPictureUrl fbPostPictureUrl = QFbPostPictureUrl.fbPostPictureUrl;
QDetectKeywordInPhotoBoundingBox boundingBox = QDetectKeywordInPhotoBoundingBox.detectKeywordInPhotoBoundingBox;

// 1. 게시물 기본 정보 조회
FbPost post = jpaQueryFactory
.selectFrom(fbPost)
.where(fbPost.id.eq(fbPostId).and(fbPost.member.id.eq(memberId)))
.fetchOne();

if (post == null) {
throw new AppException(FbPostErrorCode.INVALID_QUIZ_ID);
}

// 2. 메시지 내 감지된 개인정보 조회
List<MessageDetectRes> messageDetectResList = jpaQueryFactory
.selectFrom(detectKeywordInMessage)
.where(detectKeywordInMessage.fbPost.id.eq(fbPostId))
.fetch()
.stream()
.map(msg -> MessageDetectRes.of(
msg.getDetectWord(),
msg.getKeyword(),
msg.getLikelihood(),
msg.getStartAt(),
msg.getEndAt()
))
.collect(Collectors.toList());

// 3. 사진 URL 가져오기 (오래된 순 정렬)
List<String> allPhotoUrls = jpaQueryFactory
.select(fbPostPictureUrl.photoUrl)
.from(fbPostPictureUrl)
.where(fbPostPictureUrl.fbPost.id.eq(fbPostId))
.orderBy(fbPostPictureUrl.createdAt.asc())
.fetch();

// 4. 사진 내 감지된 개인정보 조회
List<Tuple> photoData = jpaQueryFactory
.select(
fbPostPictureUrl.photoUrl,
detectKeywordInPhoto.detectWord,
detectKeywordInPhoto.keyword,
detectKeywordInPhoto.likelihood,
boundingBox.x,
boundingBox.y,
boundingBox.width,
boundingBox.height
)
.from(fbPostPictureUrl)
.leftJoin(detectKeywordInPhoto).on(detectKeywordInPhoto.fbPostPictureUrl.id.eq(fbPostPictureUrl.id))
.leftJoin(boundingBox).on(detectKeywordInPhoto.id.eq(boundingBox.detectKeywordInPhoto.id))
.where(fbPostPictureUrl.fbPost.id.eq(fbPostId))
.orderBy(fbPostPictureUrl.createdAt.asc())
.fetch();

// 5. 데이터를 `PhotoUrlRes`로 변환 (LinkedHashMap 사용)
Map<String, List<PhotoDetectRes>> photoDetectMap = new LinkedHashMap<>();

// 5-1. 모든 사진 URL을 우선 빈 리스트로 저장
for (String pictureUrl : allPhotoUrls) {
photoDetectMap.put(pictureUrl, new ArrayList<>());
}

// 5-2. 감지된 개인정보 정보 추가
for (Tuple tuple : photoData) {
String pictureUrl = tuple.get(fbPostPictureUrl.photoUrl);
String detectWord = tuple.get(detectKeywordInPhoto.detectWord);
DetectKeyword keyword = tuple.get(detectKeywordInPhoto.keyword);
DetectLikelihood likelihood = tuple.get(detectKeywordInPhoto.likelihood);

Integer x = tuple.get(boundingBox.x);
Integer y = tuple.get(boundingBox.y);
Integer width = tuple.get(boundingBox.width);
Integer height = tuple.get(boundingBox.height);

List<BoundingBoxRes> boundingBoxes = new ArrayList<>();
if (x != null && y != null && width != null && height != null) {
boundingBoxes.add(BoundingBoxRes.of(x, y, width, height));
}

if (detectWord != null || keyword != null || likelihood != null) {
PhotoDetectRes photoDetectRes = PhotoDetectRes.of(detectWord, keyword, likelihood, boundingBoxes);
photoDetectMap.get(pictureUrl).add(photoDetectRes); // 기존 리스트에 추가
}
}

// 5-3. `PhotoUrlRes` 리스트 생성
List<PhotoUrlRes> photoUrlResList = photoDetectMap.entrySet().stream()
.map(entry -> PhotoUrlRes.of(entry.getKey(), entry.getValue())) // 빈 리스트 유지
.collect(Collectors.toList());

// 6. DTO 변환 후 반환
return FbPostDetailRes.of(
post.getCreatedTime(),
post.getMessage(),
post.getPermalinkUrl(),
post.getSafeMessage(),
messageDetectResList,
photoUrlResList
);
}

}
Loading