Skip to content

Commit

Permalink
Merge pull request #497 from Travel-in-nanaland/feat/#496-post-view-c…
Browse files Browse the repository at this point in the history
…ount

Feat/#496 post view count
  • Loading branch information
Te-H0 authored Nov 8, 2024
2 parents c9741bd + 13de9ce commit 33df2bd
Show file tree
Hide file tree
Showing 25 changed files with 372 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jeju.nanaland.domain.common.entity;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand Down Expand Up @@ -28,6 +29,9 @@ public abstract class Post extends BaseEntity {
@NotNull
private Long priority;

@Column(name = "view_count", nullable = false, columnDefinition = "int default 0")
private int viewCount;

protected Post(ImageFile firstImageFile, Long priority) {
this.firstImageFile = firstImageFile;
this.priority = priority;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.jeju.nanaland.domain.common.repository;

import com.jeju.nanaland.domain.common.entity.Post;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;


public interface PostRepository extends JpaRepository<Post, Long> {

@Modifying
@Query("UPDATE Post p SET p.viewCount = p.viewCount + 1 WHERE p.id = :postId")
void increaseViewCount(@Param("postId") Long postId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.jeju.nanaland.domain.common.service;

import com.jeju.nanaland.domain.common.repository.PostRepository;
import com.jeju.nanaland.global.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class PostViewCountService {

private final PostRepository postRepository;
private final RedisUtil redisUtil;

@Transactional
public void increaseViewCount(Long postId, Long memberId) {

String redisKey = "post_viewed_" + memberId + "_" + postId;

// 30분 -> 1800초
long cacheDurationSeconds = 1800L;

// 30분 이내에 조회한 기록이 없으면
if (redisUtil.getValue(redisKey) == null) {
// 조회수 증가
postRepository.increaseViewCount(postId);
// 레디스에 등록
redisUtil.setExpiringValue(redisKey, "viewed", cacheDurationSeconds);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public interface ExperienceRepositoryCustom {

ExperienceCompositeDto findCompositeDtoById(Long id, Language language);

ExperienceCompositeDto findCompositeDtoByIdWithPessimisticLock(Long id, Language language);

Page<ExperienceCompositeDto> searchCompositeDtoByKeyword(String keyword, Language language,
Pageable pageable);

Expand All @@ -26,6 +28,8 @@ Page<ExperienceThumbnail> findExperienceThumbnails(Language language,

Set<ExperienceTypeKeyword> getExperienceTypeKeywordSet(Long postId);

Set<ExperienceTypeKeyword> getExperienceTypeKeywordSetWithWithPessimisticLock(Long postId);

List<SearchPostForReviewDto> findAllSearchPostForReviewDtoByLanguage(Language language);

List<Long> findAllIds();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.LockModeType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -66,6 +67,35 @@ public ExperienceCompositeDto findCompositeDtoById(Long id, Language language) {
.fetchOne();
}

@Override
public ExperienceCompositeDto findCompositeDtoByIdWithPessimisticLock(Long id,
Language language) {
return queryFactory
.select(new QExperienceCompositeDto(
experience.id,
imageFile.originUrl,
imageFile.thumbnailUrl,
experience.contact,
experience.homepage,
experienceTrans.language,
experienceTrans.title,
experienceTrans.content,
experienceTrans.address,
experienceTrans.addressTag,
experienceTrans.intro,
experienceTrans.details,
experienceTrans.time,
experienceTrans.amenity,
experienceTrans.fee
))
.from(experience)
.leftJoin(experience.firstImageFile, imageFile)
.leftJoin(experience.experienceTrans, experienceTrans)
.where(experience.id.eq(id).and(experienceTrans.language.eq(language)))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
}

@Override
public Page<ExperienceCompositeDto> searchCompositeDtoByKeyword(String keyword, Language language,
Pageable pageable) {
Expand Down Expand Up @@ -171,6 +201,19 @@ public Set<ExperienceTypeKeyword> getExperienceTypeKeywordSet(Long postId) {
return map.getOrDefault(postId, Collections.emptySet());
}

@Override
public Set<ExperienceTypeKeyword> getExperienceTypeKeywordSetWithWithPessimisticLock(
Long postId) {
Map<Long, Set<ExperienceTypeKeyword>> map = queryFactory
.selectFrom(experienceKeyword)
.where(experienceKeyword.experience.id.eq(postId))
.setLockMode(LockModeType.PESSIMISTIC_WRITE) // 비관적 락 추가
.transform(GroupBy.groupBy(experienceKeyword.experience.id)
.as(GroupBy.set(experienceKeyword.experienceTypeKeyword)));

return map.getOrDefault(postId, Collections.emptySet());
}

@Override
public List<SearchPostForReviewDto> findAllSearchPostForReviewDtoByLanguage(Language language) {
return queryFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.jeju.nanaland.domain.common.entity.Post;
import com.jeju.nanaland.domain.common.repository.ImageFileRepository;
import com.jeju.nanaland.domain.common.service.PostService;
import com.jeju.nanaland.domain.common.service.PostViewCountService;
import com.jeju.nanaland.domain.experience.dto.ExperienceCompositeDto;
import com.jeju.nanaland.domain.experience.dto.ExperienceResponse.ExperienceDetailDto;
import com.jeju.nanaland.domain.experience.dto.ExperienceResponse.ExperienceThumbnail;
Expand All @@ -35,6 +36,7 @@
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
Expand All @@ -46,6 +48,7 @@ public class ExperienceService implements PostService {
private final ImageFileRepository imageFileRepository;
private final SearchService searchService;
private final ReviewRepository reviewRepository;
private final PostViewCountService postViewCountService;

/**
* Experience 객체 조회
Expand Down Expand Up @@ -110,12 +113,14 @@ public ExperienceThumbnailDto getExperienceList(MemberInfoDto memberInfoDto,
.build();
}


// 이색체험 상세 정보 조회
@Transactional
public ExperienceDetailDto getExperienceDetail(MemberInfoDto memberInfoDto, Long postId,
boolean isSearch) {

Language language = memberInfoDto.getLanguage();
ExperienceCompositeDto experienceCompositeDto = experienceRepository.findCompositeDtoById(
ExperienceCompositeDto experienceCompositeDto = experienceRepository.findCompositeDtoByIdWithPessimisticLock(
postId, language);

// 해당 id의 포스트가 없는 경우 404 에러
Expand All @@ -138,13 +143,16 @@ public ExperienceDetailDto getExperienceDetail(MemberInfoDto memberInfoDto, Long
images.addAll(imageFileRepository.findPostImageFiles(postId));

// 키워드
Set<ExperienceTypeKeyword> keywordSet = experienceRepository.getExperienceTypeKeywordSet(
Set<ExperienceTypeKeyword> keywordSet = experienceRepository.getExperienceTypeKeywordSetWithWithPessimisticLock(
postId);
List<String> keywords = keywordSet.stream()
.map(experienceTypeKeyword ->
experienceTypeKeyword.getValueByLocale(language)
).toList();

// 조회 수 증가
postViewCountService.increaseViewCount(postId, member.getId());

return ExperienceDetailDto.builder()
.id(experienceCompositeDto.getId())
.title(experienceCompositeDto.getTitle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface FestivalRepositoryCustom {

FestivalCompositeDto findCompositeDtoById(Long id, Language locale);

FestivalCompositeDto findCompositeDtoByIdWithPessimisticLock(Long id, Language locale);

Page<FestivalCompositeDto> searchCompositeDtoByKeyword(String keyword, Language locale,
Pageable pageable);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.LockModeType;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -59,6 +60,37 @@ public FestivalCompositeDto findCompositeDtoById(Long id, Language language) {
.fetchOne();
}

@Override
public FestivalCompositeDto findCompositeDtoByIdWithPessimisticLock(Long id, Language language) {
return queryFactory
.select(new QFestivalCompositeDto(
festival.id,
imageFile.originUrl,
imageFile.thumbnailUrl,
festival.contact,
festival.homepage,
festivalTrans.language,
festivalTrans.title,
festivalTrans.content,
festivalTrans.address,
festivalTrans.addressTag,
festivalTrans.time,
festivalTrans.intro,
festivalTrans.fee,
festival.startDate,
festival.endDate,
festival.season
))
.from(festival)
.leftJoin(festival.firstImageFile, imageFile)
.leftJoin(festival.festivalTrans, festivalTrans)
.where(festival.id.eq(id).and(festivalTrans.language.eq(language))
.and(festival.status.eq(Status.ACTIVE))
)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
}

@Override
public Page<FestivalCompositeDto> searchCompositeDtoByKeyword(String keyword, Language language,
Pageable pageable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.jeju.nanaland.domain.common.entity.Post;
import com.jeju.nanaland.domain.common.service.ImageFileService;
import com.jeju.nanaland.domain.common.service.PostService;
import com.jeju.nanaland.domain.common.service.PostViewCountService;
import com.jeju.nanaland.domain.favorite.service.MemberFavoriteService;
import com.jeju.nanaland.domain.festival.dto.FestivalCompositeDto;
import com.jeju.nanaland.domain.festival.dto.FestivalResponse.FestivalDetailDto;
Expand All @@ -25,7 +26,6 @@
import com.jeju.nanaland.global.exception.BadRequestException;
import com.jeju.nanaland.global.exception.ErrorCode;
import com.jeju.nanaland.global.exception.NotFoundException;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
Expand All @@ -38,6 +38,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
Expand All @@ -48,6 +49,7 @@ public class FestivalService implements PostService {
private final MemberFavoriteService memberFavoriteService;
private final SearchService searchService;
private final ImageFileService imageFileService;
private final PostViewCountService postViewCountService;

/**
* Festival 객체 조회
Expand Down Expand Up @@ -144,9 +146,11 @@ public FestivalThumbnailDto getSeasonFestivalList(MemberInfoDto memberInfoDto, i
}

// 축제 상세 정보 조회
@Transactional
public FestivalDetailDto getFestivalDetail(MemberInfoDto memberInfoDto, Long id,
boolean isSearch) {
FestivalCompositeDto compositeDtoById = festivalRepository.findCompositeDtoById(id,
FestivalCompositeDto compositeDtoById = festivalRepository.findCompositeDtoByIdWithPessimisticLock(
id,
memberInfoDto.getLanguage());

if (compositeDtoById == null) {
Expand All @@ -160,6 +164,9 @@ public FestivalDetailDto getFestivalDetail(MemberInfoDto memberInfoDto, Long id,
boolean isPostInFavorite =
memberFavoriteService.isPostInFavorite(memberInfoDto.getMember(), FESTIVAL, id);

// 조회 수 증가
postViewCountService.increaseViewCount(id, memberInfoDto.getMember().getId());

return FestivalDetailDto.builder()
.id(compositeDtoById.getId())
.addressTag(compositeDtoById.getAddressTag())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface MarketRepositoryCustom {

MarketCompositeDto findCompositeDtoById(Long id, Language locale);

MarketCompositeDto findCompositeDtoByIdWithPessimisticLock(Long id, Language locale);

Page<MarketThumbnail> findMarketThumbnails(Language locale, List<AddressTag> addressTags,
Pageable pageable);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.LockModeType;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -54,6 +55,32 @@ public MarketCompositeDto findCompositeDtoById(Long id, Language language) {
.fetchOne();
}

@Override
public MarketCompositeDto findCompositeDtoByIdWithPessimisticLock(Long id, Language language) {
return queryFactory
.select(new QMarketCompositeDto(
market.id,
imageFile.originUrl,
imageFile.thumbnailUrl,
market.contact,
market.homepage,
marketTrans.language,
marketTrans.title,
marketTrans.content,
marketTrans.address,
marketTrans.addressTag,
marketTrans.time,
marketTrans.intro,
marketTrans.amenity
))
.from(market)
.leftJoin(market.firstImageFile, imageFile)
.leftJoin(market.marketTrans, marketTrans)
.where(market.id.eq(id).and(marketTrans.language.eq(language)))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
}

@Override
public Page<MarketThumbnail> findMarketThumbnails(Language language,
List<AddressTag> addressTags, Pageable pageable) {
Expand Down
Loading

0 comments on commit 33df2bd

Please sign in to comment.