Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

좋아요 기능 구현 및 테스트 구현 #73

Merged
merged 10 commits into from
Jun 19, 2024
Prev Previous commit
Next Next commit
feat : 좋아요 기능 구현
- 레디스 캐싱
bandalgomsu committed Jun 19, 2024
commit a374215593d1415c79a0d4fff05d5eb619d9cedb
Original file line number Diff line number Diff line change
@@ -4,12 +4,14 @@
import java.util.TimeZone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@EnableJpaAuditing
@SpringBootApplication
@EnableFeignClients
public class GraduateMinionsApplication {
public static void main(String[] args) {
SpringApplication.run(GraduateMinionsApplication.class, args);
21 changes: 21 additions & 0 deletions src/main/java/com/example/jolvre/common/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
@@ -17,4 +19,23 @@ public class RedisConfig {
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

@Bean
public RedisTemplate redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());

// 일반적인 key:value의 경우 시리얼라이저
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

// Hash를 사용할 경우 시리얼라이저
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());

// 모든 경우
redisTemplate.setDefaultSerializer(new StringRedisSerializer());

return redisTemplate;
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/example/jolvre/common/util/RedisUtil.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.example.jolvre.common.util;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
@@ -18,9 +20,22 @@ public String getData(String key) {//지정된 키(key)에 해당하는 데이

public void setData(String key, String value) {//지정된 키(key)에 값을 저장하는 메서드
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
redisTemplate.expire(key, 8, TimeUnit.HOURS);
valueOperations.set(key, value);
}

public Object getHashData(String key, Object hashKey) {
HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
return hashOperations.get(key, hashKey);
}

public void setHashData(String key, String hashKey, String value) {
HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
redisTemplate.expire(key, 6, TimeUnit.HOURS);
hashOperations.put(key, hashKey, value);

}

public void setDataExpire(String key, String value,
long duration) {//지정된 키(key)에 값을 저장하고, 지정된 시간(duration) 후에 데이터가 만료되도록 설정하는 메서드
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.jolvre.exhibition.api;

import com.example.jolvre.auth.PrincipalDetails;
import com.example.jolvre.exhibition.service.ExhibitLikeService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/exhibit/like")
public class ExhibitLikeController {
private final ExhibitLikeService exhibitLikeService;

@PostMapping("/{exhibitId}")
public ResponseEntity<Void> likeUpExhibit(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable Long exhibitId) {
exhibitLikeService.likeUpExhibit(exhibitId, principalDetails.getId());

return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ public class Exhibit extends BaseTimeEntity {
private String title;

@Column
private int up; //추천수
private int likes = 0; //추천수

@Column(length = 1000)
private String authorWord; //작가의 한마디
@@ -98,7 +98,6 @@ public Exhibit(User user, String title, String authorWord, String introduction,
this.productionMethod = productionMethod;
this.price = price;
this.forSale = forSale;
this.up = 0;
this.distribute = false;
this.thumbnail = thumbnail;
this.checkVirtualSpace = checkVirtualSpace;
@@ -132,8 +131,8 @@ public void addImage3D(String imageUrl) {
this.image3d = imageUrl;
}

public void up() {
this.up += 1;
public void likeUp() {
this.likes += 1;
}

public void startDistribute() {
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.jolvre.exhibition.service;

import com.example.jolvre.common.error.exhibition.ExhibitNotFoundException;
import com.example.jolvre.common.util.RedisUtil;
import com.example.jolvre.exhibition.entity.Exhibit;
import com.example.jolvre.exhibition.repository.ExhibitRepository;
import com.example.jolvre.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ExhibitLikeService {
private final ExhibitRepository exhibitRepository;
private final UserService userService;
private final RedisUtil redisUtil;

//전시 좋아요 (한 전시당 8시간마다 좋아요 가능)
public void likeUpExhibit(Long exhibitId, Long userId) {
String redisKey = "LIKE_" + exhibitId + userId;

if (redisUtil.getData(redisKey) != null) {
return;
}

Exhibit exhibit = exhibitRepository.findById(exhibitId).orElseThrow(ExhibitNotFoundException::new);
exhibit.likeUp();
exhibitRepository.save(exhibit);

redisUtil.setData(redisKey, String.valueOf(exhibit.getLikes()));
}
}
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@
import com.example.jolvre.exhibition.repository.ExhibitImageRepository;
import com.example.jolvre.exhibition.repository.ExhibitQueryDslRepository;
import com.example.jolvre.exhibition.repository.ExhibitRepository;
import com.example.jolvre.external.ModelApi;
import com.example.jolvre.user.entity.User;
import com.example.jolvre.user.service.UserService;
import jakarta.transaction.Transactional;
@@ -40,8 +39,7 @@ public class ExhibitService {
private final DiaryRepository diaryRepository;
private final ExhibitCommentRepository exhibitCommentRepository;
private final ExhibitQueryDslRepository exhibitQueryDslRepository;
private final ModelApi modelApi;


@Transactional
public ExhibitUploadResponse uploadExhibit(ExhibitUploadRequest request, Long userId) {

@@ -116,15 +114,15 @@ public ExhibitUploadResponse uploadExhibitAsync(ExhibitUploadRequest request, Lo
}

CompletableFuture<Void> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
save.setImage3d(modelApi.get3DModelUrl(request.getThumbnail()));
// save.setImage3d(model3D.get3DModelUrl(request.getThumbnail())); //여기다 모델 서버 연결
exhibitRepository.save(save);
return null;
});

return ExhibitUploadResponse.builder().exhibitId(save.getId()).build();
}

@Transactional
@Transactional //상세 전시 조회
public ExhibitInfoResponse getExhibitInfo(Long id) {
Exhibit exhibit = exhibitRepository.findById(id).orElseThrow(ExhibitNotFoundException::new);

@@ -140,7 +138,7 @@ public ExhibitInfoResponses getAllExhibitInfo() {
.build();
}

@Transactional
@Transactional //해당 유저에 모든 전시 조회
public ExhibitInfoResponses getAllUserExhibitInfo(Long userId) {
User user = userService.getUserById(userId);

@@ -160,7 +158,7 @@ public ExhibitInfoResponses getAllExhibitInfoByWorkType(String workType) {
.build();
}

@Transactional
@Transactional // 전시 삭제
public void deleteExhibit(Long exhibitId, Long userId) {
exhibitImageRepository.deleteAllByExhibitId(exhibitId);
diaryRepository.deleteAllByExhibitId(exhibitId);
@@ -171,18 +169,18 @@ public void deleteExhibit(Long exhibitId, Long userId) {
exhibitRepository.delete(exhibit);
}

@Transactional
@Transactional // 전시 아이디를 통한 전시 엔티티 조회
public Exhibit getExhibitById(Long id) {
return exhibitRepository.findById(id).orElseThrow(ExhibitNotFoundException::new);
}

@Transactional
@Transactional // 유저 아이디 + 전시 아이디 를 통한 전시 엔티티 조회
public Exhibit getExhibitByIdAndUserId(Long exhibitId, Long userId) {
return exhibitRepository.findByIdAndUserId(exhibitId, userId)
.orElseThrow(ExhibitNotFoundException::new);
}

@Transactional
@Transactional //전시 배포 처리
public void distributeExhibit(Long exhibitId, Long userId) {
Exhibit exhibit = exhibitRepository.findByIdAndUserId(exhibitId, userId)
.orElseThrow(ExhibitNotFoundException::new);
@@ -192,7 +190,7 @@ public void distributeExhibit(Long exhibitId, Long userId) {
exhibitRepository.save(exhibit);
}

@Transactional
@Transactional //전시 업데이트
public void updateExhibit(Long exhibitId, Long userId, ExhibitUpdateRequest request) {
Exhibit exhibit = exhibitRepository.findByIdAndUserId(exhibitId, userId).orElseThrow(
ExhibitNotFoundException::new);
@@ -221,7 +219,7 @@ public void updateExhibit(Long exhibitId, Long userId, ExhibitUpdateRequest requ
exhibitRepository.save(exhibit);
}

@Transactional
@Transactional //초대장 생성
public ExhibitInvitationResponse createInvitation(Long exhibitId) {
Exhibit exhibit = exhibitRepository.findById(exhibitId).orElseThrow(
ExhibitNotFoundException::new);
@@ -234,13 +232,9 @@ public ExhibitInvitationResponse createInvitation(Long exhibitId) {

}

// 키워드 기반 전시 조회
public Page<ExhibitInfoResponse> getExhibitInfoByKeyword(String keyword, Pageable pageable) {
// if (keyword == null) {
// return exhibitRepository.findAllByDistribute(true, pageable).map(ExhibitInfoResponse::toDTO);
// }
// return exhibitRepository.findByDistributeAndTitleContaining(true, keyword, pageable)
// .map(ExhibitInfoResponse::toDTO);
//
return exhibitQueryDslRepository.findAllByFilter(true, keyword, pageable);
}

}
34 changes: 19 additions & 15 deletions src/main/java/com/example/jolvre/external/ModelApi.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package com.example.jolvre.external;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;

@Component
@FeignClient(name = "3D Model API", url = "https://model.com/")
public interface ModelApi {
@GetMapping("/model/async")
String get3DModelUrl(@RequestBody MultipartFile image);
}

//package com.example.jolvre.external;
//
//import org.springframework.cloud.openfeign.FeignClient;
//import org.springframework.stereotype.Component;
//import org.springframework.web.bind.annotation.GetMapping;
//import org.springframework.web.bind.annotation.RequestBody;
//import org.springframework.web.multipart.MultipartFile;
//
//@Component
//public class ModelApi {
//
// @Component
// @FeignClient
// public interface Model3D {
// @GetMapping("/model/async")
// String get3DModelUrl(@RequestBody MultipartFile image);
// }
//}
//