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

test & feat: 포인트 도메인 테스트 & 좋아요 기능과 리뷰 조회 기능 성능 개선 및 동시성 제어 #26

Merged
merged 6 commits into from
Nov 4, 2023
Merged
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ dependencies {
//DB
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.redisson:redisson-spring-boot-starter:3.24.3'

//네이버 클라우드
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.inq.wishhair.wesharewishhair.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.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Value("${spring.data.redis.host}")
public String host;
@Value("${spring.data.redis.port}")
public int port;

@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

@Bean
public RedisTemplate<String, Long> cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Long.class));
return redisTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.inq.wishhair.wesharewishhair.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulerConfig {
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public enum ErrorCode {
FLASK_SERVER_EXCEPTION("FLASK_001", "Flask 서버 요청 간 에러가 발생하였습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
FLASK_RESPONSE_ERROR("FLASK_002", "Flask 서버의 응답값의 형식이 올바르지 않습니다.", HttpStatus.INTERNAL_SERVER_ERROR),

AOP_GENERIC_EXCEPTION("AOP_001", "AOP 에서 발생한 Generic 에러 입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
AOP_GENERIC_EXCEPTION("AOP_001", "AOP 에서 발생한 Generic 에러 입니다.", HttpStatus.INTERNAL_SERVER_ERROR),

REDIS_FAIL_ACQUIRE_LOCK("REDIS_001", "서버 일시적 오류입니다. 재시도 해주세요", HttpStatus.INTERNAL_SERVER_ERROR);

private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.inq.wishhair.wesharewishhair.global.utils;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class RedisUtils {

private final RedisTemplate<String, Long> redisTemplate;
private final long expireTime;

public RedisUtils(
RedisTemplate<String, Long> redisTemplate,
@Value("${spring.data.redis.expire-time}") long expireTime
) {
this.redisTemplate = redisTemplate;
this.expireTime = expireTime;
}

public void setData(Long key, Long value) {
redisTemplate
.opsForValue()
.set(String.valueOf(key), value, expireTime, TimeUnit.MILLISECONDS);
}

public void increaseData(Long key) {
redisTemplate.opsForValue().increment(String.valueOf(key));
}

public void decreaseData(Long key) {
redisTemplate.opsForValue().decrement(String.valueOf(key));
}

public Optional<Long> getData(Long key) {
return Optional.ofNullable(
redisTemplate.opsForValue().get(String.valueOf(key))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.inq.wishhair.wesharewishhair.hairstyle.application.query.HairStyleQueryRepository;
Expand All @@ -23,6 +24,7 @@

import lombok.RequiredArgsConstructor;

@Repository
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class HairStyleQueryDslRepository implements HairStyleQueryRepository {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.inq.wishhair.wesharewishhair.review.domain.entity.Review;

import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
Expand All @@ -25,11 +27,11 @@ public class Photo {
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "hair_style_id")
@JoinColumn(name = "hair_style_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private HairStyle hairStyle;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "review_id")
@JoinColumn(name = "review_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private Review review;

@Column(nullable = false, updatable = false, unique = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public PagedResponse<PointResponse> getPointHistories(
final Long userId,
final Pageable pageable
) {

Slice<PointLog> result = pointLogRepository.findByUserIdOrderByNew(userId, pageable);
return toPagedResponse(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.inq.wishhair.wesharewishhair.global.exception.ErrorCode;
import com.inq.wishhair.wesharewishhair.global.exception.WishHairException;
import com.inq.wishhair.wesharewishhair.point.application.dto.PointUseRequest;
import com.inq.wishhair.wesharewishhair.point.domain.PointLog;
import com.inq.wishhair.wesharewishhair.point.domain.PointLogRepository;
Expand All @@ -22,36 +24,61 @@ public class PointService {
private final ApplicationEventPublisher eventPublisher;
private final PointLogRepository pointLogRepository;

private void saveNewPointLog(
User user,
PointType pointType,
int dealAmount,
int prePoint
) {
PointLog newPointLog = PointLog.addPointLog(
user,
pointType,
dealAmount,
prePoint
);
pointLogRepository.save(newPointLog);
}

@Transactional
public void usePoint(final PointUseRequest request, final Long userId) {
public boolean usePoint(final PointUseRequest request, final Long userId) {

User user = userFindService.findByUserId(userId);
insertPointHistory(PointType.USE, request.dealAmount(), user);

pointLogRepository.findByUserOrderByCreatedDateDesc(user)
.ifPresentOrElse(
lastPointLog -> saveNewPointLog(
user,
PointType.USE,
request.dealAmount(),
lastPointLog.getPoint()
),
() -> {
throw new WishHairException(ErrorCode.POINT_NOT_ENOUGH);
});

eventPublisher.publishEvent(request.toRefundMailEvent(user.getName()));
return true;
}

@Transactional
public void chargePoint(int dealAmount, Long userId) {
public boolean chargePoint(int dealAmount, Long userId) {
User user = userFindService.findByUserId(userId);
insertPointHistory(PointType.CHARGE, dealAmount, user);
}

private void insertPointHistory(
final PointType pointType,
final int dealAmount,
final User user
) {
pointLogRepository.findByUserOrderByCreatedDateDesc(user)
.ifPresent(lastPointLog -> {
PointLog newPointLog = PointLog.addPointLog(
user,
pointType,
dealAmount,
lastPointLog.getPoint()
);
pointLogRepository.save(newPointLog);
});
.ifPresentOrElse(
lastPointLog -> saveNewPointLog(
user,
PointType.CHARGE,
dealAmount,
lastPointLog.getPoint()
),
() -> saveNewPointLog(
user,
PointType.CHARGE,
dealAmount,
0
)
);

return true;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@
import com.inq.wishhair.wesharewishhair.user.domain.entity.User;

import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "point_log")
public class PointLog extends BaseEntity {

@Id
Expand All @@ -37,7 +41,11 @@ public class PointLog extends BaseEntity {
private int point;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false, updatable = false)
@JoinColumn(
name = "user_id",
nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)
)
private User user;

private PointLog(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public enum PointType {
CHARGE(
"충전",
(chargeAmount, prePoint) -> {
if (chargeAmount <= 0) {
if (chargeAmount < 0) {
throw new WishHairException(POINT_INVALID_POINT_RANGE);
}
return prePoint + chargeAmount;
Expand All @@ -22,7 +22,10 @@ public enum PointType {
"사용",
(useAmount, prePoint) -> {
int point = prePoint - useAmount;
if (useAmount <= 0 || point < 0) {
if (useAmount < 0) {
throw new WishHairException(POINT_INVALID_POINT_RANGE);
}
if (point < 0) {
throw new WishHairException(POINT_NOT_ENOUGH);
}
return point;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/users/point")
@RequestMapping("/api/points")
@RequiredArgsConstructor
public class PointController {

Expand All @@ -26,7 +26,6 @@ public ResponseEntity<Success> usePoint(
final @RequestBody PointUseRequest request,
final @FetchAuthInfo AuthInfo authInfo
) {

pointService.usePoint(request, authInfo.userId());

return ResponseEntity.ok(new Success());
Expand Down
Loading