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

9조 BE 코드리뷰 5회차 #96

Merged
merged 39 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
814bb5f
feat:[#79]- add feed
yooonwodyd Nov 3, 2024
3b565d1
Merge pull request #80 from yooonwodyd/weekly
yooonwodyd Nov 3, 2024
daa049c
Merge pull request #81 from kakao-tech-campus-2nd-step3/Master
yooonwodyd Nov 3, 2024
a200558
Merge pull request #82 from kakao-tech-campus-2nd-step3/develop
yooonwodyd Nov 3, 2024
f011449
feat:[#84]- refact jwt
yooonwodyd Nov 6, 2024
1f79ee2
feat:[#84]- add userImage
yooonwodyd Nov 6, 2024
7b1104f
feat:[#84]- refact User
yooonwodyd Nov 6, 2024
6298063
feat:[#84]- refact dto
yooonwodyd Nov 6, 2024
0cc6353
feat : [#85] ApiResponse 통일 DTO 생성
jupyter471 Nov 7, 2024
5a789bb
feat : [#85] 예외, 성공 경우 ApiResponse 사용 예시
jupyter471 Nov 7, 2024
f01fcb6
Merge pull request #86 from jupyter471/weekly
yooonwodyd Nov 7, 2024
1a7da80
Merge branch 'weekly' into weekly
yooonwodyd Nov 7, 2024
1c14c81
feat:[#84]- refact Controller
yooonwodyd Nov 7, 2024
43c1309
feat:[#84]- add sql
yooonwodyd Nov 7, 2024
b4e882f
Merge pull request #87 from yooonwodyd/weekly
yooonwodyd Nov 7, 2024
3136eae
Merge pull request #88 from kakao-tech-campus-2nd-step3/weekly
yooonwodyd Nov 7, 2024
eff003f
Merge pull request #89 from kakao-tech-campus-2nd-step3/develop
yooonwodyd Nov 7, 2024
e0c5699
feat:[#84]- add sql
yooonwodyd Nov 8, 2024
03833d3
feat : [#85] ReviewController 응답 형식 수정
jupyter471 Nov 8, 2024
d2b9d08
refactor:[#84]- refact Artist API
yooonwodyd Nov 8, 2024
30e6488
feat:[#84]- add data.sql
yooonwodyd Nov 8, 2024
880f720
feat : [#62] 한 상품에 대한 리뷰 조회 기능
jupyter471 Nov 8, 2024
093b1db
#83 chore(application.yaml): redis 설정 추가
sim-mer Nov 8, 2024
91ea66e
#83 chore(build.gradle): redis 의존성 추가
sim-mer Nov 8, 2024
a3bc59d
#83 feat: 작가 검색 시 팔로우 여부 응답에 추가
sim-mer Nov 8, 2024
b4cdd1e
#83 test(search): 작가 검색 기능 변경에 따른 테스트 코드 수정
sim-mer Nov 8, 2024
27be18f
#83 feat(product): 인기 검색어 분석을 위한 검색 기록 저장
sim-mer Nov 8, 2024
42ca391
#83 chore(config): Redis Config 작성
sim-mer Nov 8, 2024
877ea6f
#83 feat(search): 인기 검색어 스케줄링 및 조회 기능 작성
sim-mer Nov 8, 2024
5c9aaac
#83 feat(user): following 작가 id 검색 메서드 추가
sim-mer Nov 8, 2024
21db729
feat : [#62] 작가별, 상품별 리뷰 조회
jupyter471 Nov 8, 2024
e423ca5
feat : [#93] 상품 좋아요 등록 및 삭제
jupyter471 Nov 8, 2024
c2ece8d
Merge pull request #90 from yooonwodyd/weekly
yooonwodyd Nov 8, 2024
0994938
Merge pull request #92 from jupyter471/weekly
yooonwodyd Nov 8, 2024
f1e321a
Merge branch 'weekly' into feat/#83-popular-search
yooonwodyd Nov 8, 2024
59daef7
Merge pull request #91 from kakao-tech-campus-2nd-step3/feat/#83-popu…
yooonwodyd Nov 8, 2024
73be0c2
Merge pull request #94 from kakao-tech-campus-2nd-step3/weekly
yooonwodyd Nov 8, 2024
ccc701b
Merge pull request #95 from kakao-tech-campus-2nd-step3/develop
yooonwodyd Nov 8, 2024
f0bd608
fix: Redis 환경변수 주입 경로 수정
sim-mer Nov 10, 2024
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ dependencies {
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// Spring docs
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/helpmeCookies/Step3Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Step3Application {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.helpmeCookies.global.ApiResponse;

import jakarta.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
@RequiredArgsConstructor
public class ApiResponse<T> {

private final int code;
private final String message;
private T data;

public static<T> ApiResponse<T> success(SuccessCode successCode, T data) {
return new ApiResponse<T>(successCode.getCode(), successCode.getMessage(),data);
}

public static ApiResponse<Void> success(SuccessCode successCode) {
return new ApiResponse<>(successCode.getCode(), successCode.getMessage(),null);
}

public static ApiResponse<Void> error(HttpStatus errorCode, String message) {
return new ApiResponse<>(errorCode.value(), message,null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.helpmeCookies.global.ApiResponse;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@AllArgsConstructor
@Getter
public enum SuccessCode {
OK(200, "OK"),
CREATED_SUCCESS(201, "저장에 성공했습니다"),
NO_CONTENT(204, "삭제에 성공했습니다.");

private final int code;
private final String message;

}
12 changes: 12 additions & 0 deletions src/main/java/com/helpmeCookies/global/config/ProPertyConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.helpmeCookies.global.config;

import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import com.helpmeCookies.global.jwt.JwtProperties;

@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class ProPertyConfig {
}
40 changes: 40 additions & 0 deletions src/main/java/com/helpmeCookies/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.helpmeCookies.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);

Choose a reason for hiding this comment

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

Lettuce도 좋지만, Redission을 사용하시면 분산락 기능도 사용하실수 있어요~
분산락 기능이 필요없으시다면 Lettuce로도 충분한것 같습니다. 다만 분산락은 애플리케이션에서 많이 활용되는 측면이 있어서
나중에 한번 공부해보시면 좋을것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 한번 공부해보겠습니다!

}

@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}



}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.helpmeCookies.global.exception;

import com.helpmeCookies.global.ApiResponse.ApiResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

Expand All @@ -10,8 +13,8 @@
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public String handleResourceNotFoundException() {
return "해당 리소스를 찾을 수 없습니다.";
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(ResourceNotFoundException e) {
return ResponseEntity.badRequest().body(ApiResponse.error(HttpStatus.BAD_REQUEST, e.getMessage()));
}

@ExceptionHandler(DuplicateRequestException.class)
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/helpmeCookies/global/jwt/JwtProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.helpmeCookies.global.jwt;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Getter;
import lombok.Setter;

@Component
@ConfigurationProperties(prefix = "jwt")
@Getter
@Setter
public class JwtProperties {
private String secret;
private long accessTokenExpireTime;
private long refreshTokenExpireTime;

public JwtProperties() {
}
}
11 changes: 6 additions & 5 deletions src/main/java/com/helpmeCookies/global/jwt/JwtProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class JwtProvider implements InitializingBean {
private String secret = "4099a46b-39db-4860-a61b-2ae76ea24c43";
private long accessTokenExpireTime = 1800000; // 30 minutes;
private long refreshTokenExpireTime = 259200000; // 3 days;

private final JwtProperties jwtProperties;
private Key secretKey;
private static final String ROLE = "role";
private static final String IS_ACCESS_TOKEN = "isAccessToken";
Expand Down Expand Up @@ -92,7 +93,7 @@ private JwtUser claimsToJwtUser(Claims claims) {
}

private String generateToken(JwtUser jwtUser, boolean isAccessToken) {
long expireTime = isAccessToken ? accessTokenExpireTime : refreshTokenExpireTime;
long expireTime = isAccessToken ? jwtProperties.getAccessTokenExpireTime() : jwtProperties.getRefreshTokenExpireTime();
Date expireDate = new Date(System.currentTimeMillis() + expireTime);
return Jwts.builder()
.signWith(secretKey)
Expand All @@ -112,6 +113,6 @@ private Claims extractClaims(String rawToken) {

@Override
public void afterPropertiesSet() {
secretKey = new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName());
secretKey = new SecretKeySpec(jwtProperties.getSecret().getBytes(), SignatureAlgorithm.HS256.getJcaName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;

private static final String AUTHORIZATION_HEADER = "Authorization";

@Override
Expand All @@ -46,6 +45,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null,
jwtUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
log.info("유효하지 않은 토큰 발생");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "토큰이 유효하지 않습니다.");
}

filterChain.doFilter(request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/actuator/**",
"/v1/**",
"swagger-ui/**",
"/test/signup"
"/test/signup",
"/v1/artists/**"
).permitAll()
.anyRequest().authenticated()
).exceptionHandling((exception) -> exception
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.helpmeCookies.product.controller;

import com.helpmeCookies.global.ApiResponse.ApiResponse;
import com.helpmeCookies.global.ApiResponse.SuccessCode;
import com.helpmeCookies.global.jwt.JwtUser;
import com.helpmeCookies.product.dto.ImageUpload;
import static com.helpmeCookies.product.util.SortUtil.convertProductSort;

Expand All @@ -10,11 +13,18 @@
import com.helpmeCookies.product.dto.ProductResponse;
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.product.service.ProductImageService;
import com.helpmeCookies.product.service.ProductLikeService;
import com.helpmeCookies.product.service.ProductService;
import com.helpmeCookies.product.util.ProductSort;
import com.helpmeCookies.review.dto.ReviewResponse;
import com.helpmeCookies.review.service.ReviewService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

Expand All @@ -27,6 +37,13 @@ public class ProductController implements ProductApiDocs {

private final ProductService productService;
private final ProductImageService productImageService;
private final ReviewService reviewService;
private final ProductLikeService productLikeService;

@PostMapping("/successTest")
public ResponseEntity<ApiResponse<Void>> saveTest() {
return ResponseEntity.ok(ApiResponse.success(SuccessCode.OK));
}

@PostMapping
public ResponseEntity<Void> saveProduct(@RequestBody ProductRequest productRequest) {
Expand Down Expand Up @@ -82,4 +99,36 @@ public ResponseEntity<ProductPage.Paging> getProductsByPage(

return ResponseEntity.ok(productService.getProductsByPage(query, pageable));
}

@GetMapping("/feed")
public ResponseEntity<ProductPage.Paging> getProductsWithRandomPaging(
@RequestParam(name = "size", required = false, defaultValue = "20") int size
) {
Pageable pageable = PageRequest.of(0, size);
return ResponseEntity.ok(productService.getProductsWithRandomPaging(pageable));
}

@GetMapping("/{productId}/reviews")
public ResponseEntity<ApiResponse<Page<ReviewResponse>>> getAllReviewsByProduct(
@PathVariable("productId") Long productId,
@PageableDefault(size = 7) Pageable pageable) {
return ResponseEntity.ok(ApiResponse.success(SuccessCode.OK,reviewService.getAllReviewByProduct(productId,pageable)));
}

@PostMapping("/{productId}/likes")
public ResponseEntity<ApiResponse<Void>> postProductLike(
@PathVariable("productId") Long productId,
@AuthenticationPrincipal JwtUser jwtUser) {
productLikeService.productLike(jwtUser.getId(), productId);
return ResponseEntity.ok(ApiResponse.success(SuccessCode.OK));
}

@DeleteMapping("/{productId}/likes")
public ResponseEntity<ApiResponse<Void>> deleteProductlike(
@PathVariable("productId") Long productId,
@AuthenticationPrincipal JwtUser jwtUser) {
productLikeService.deleteProductLike(jwtUser.getId(), productId);

return ResponseEntity.ok(ApiResponse.success(SuccessCode.NO_CONTENT));
}
}
8 changes: 7 additions & 1 deletion src/main/java/com/helpmeCookies/product/entity/Like.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;

@Entity
Expand All @@ -25,4 +24,11 @@ public class Like {
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;

protected Like(){};

Choose a reason for hiding this comment

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

👍


public Like(User user, Product product) {
this.user = user;
this.product = product;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.helpmeCookies.product.repository;

import com.helpmeCookies.product.entity.Like;
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.user.entity.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductLikeRepository extends JpaRepository<Like, Long> {
Optional<Like> findDistinctFirstByUserAndProduct(User user, Product product);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ public interface ProductRepository extends JpaRepository<Product, Long> {
"WHERE MATCH(p.name) AGAINST (:query IN BOOLEAN MODE)",
nativeQuery = true) // Index 사용
Page<ProductSearch> findByNameWithIdx(@Param("query") String query, Pageable pageable);


@Query("SELECT p FROM Product p ORDER BY FUNCTION('RAND')")
Page<ProductSearch> findAllRandom(Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.helpmeCookies.product.service;

import com.helpmeCookies.product.entity.Like;
import com.helpmeCookies.product.entity.Product;
import com.helpmeCookies.product.repository.ProductLikeRepository;
import com.helpmeCookies.product.repository.ProductRepository;
import com.helpmeCookies.user.entity.User;
import com.helpmeCookies.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProductLikeService {
private final ProductLikeRepository productLikeRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;

@Transactional
public void productLike(Long userId, Long productId) {

Choose a reason for hiding this comment

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

like를 동시에 두번 요청하면 어떻게 될까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

아! 이미 좋아요 되었을 때 예외처리가 안되어 있네요..!! 감사합니다!! 관련 처리 반영하겠습니다

User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 유저Id입니다." + userId));
Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상품Id입니다." + productId));
Like like = new Like(user, product);
productLikeRepository.save(like);
}

@Transactional
public void deleteProductLike(Long userId, Long productId) {
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 유저Id입니다." + userId));
Product product = productRepository.findById(productId).orElseThrow(() -> new IllegalArgumentException("유효하지 않은 상품Id입니다." + productId));

Like like = productLikeRepository.findDistinctFirstByUserAndProduct(user,product).orElseThrow(() -> new IllegalArgumentException("존재하지 않는 상품 찜 항목입니다."));
productLikeRepository.delete(like);
}
}
Loading
Loading