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

[Feat] 수강신청 기능 구현 #14

Merged
merged 5 commits into from
Mar 31, 2024
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
3 changes: 2 additions & 1 deletion doochul/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'com.google.firebase:firebase-admin:9.2.0'

runtimeOnly 'mysql:mysql-connector-java:8.0.28'
runtimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.batch:spring-batch-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:3.12.4'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.doochul.application;

import java.time.Duration;
import lombok.RequiredArgsConstructor;
import org.doochul.domain.membership.MemberShip;
import org.doochul.domain.membership.MemberShipRepository;
import org.doochul.domain.product.Product;
import org.doochul.domain.product.ProductRepository;
import org.doochul.domain.user.User;
import org.doochul.domain.user.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class MemberShipService {

private final MemberShipRepository memberShipRepository;
private final ProductRepository productRepository;
private final UserRepository userRepository;
private final RedisService redisService;

public Long save(final Long userId, final Long productId) {
final Product product = productRepository.findById(productId).orElseThrow();
final User user = userRepository.findById(userId).orElseThrow();

final String key = Long.toString(userId);
if (redisService.setNX(key, "apply", Duration.ofSeconds(5))) {
final Long id = memberShipRepository.save(MemberShip.of(user, product, product.getCount())).getId();
redisService.delete(key);
return id;
}
throw new IllegalArgumentException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class MemberShip extends BaseEntity {
private Long id;

@ManyToOne
@JoinColumn(name = "student_id")
@JoinColumn(name = "user_id")
private User student;

@ManyToOne
Expand All @@ -32,12 +32,17 @@ public class MemberShip extends BaseEntity {

private Integer remainingCount;

public MemberShip(final User student, final Product product, final Integer remainingCount) {
public MemberShip(final Long id, final User student, final Product product, final Integer remainingCount) {
this.id = id;
this.student = student;
this.product = product;
this.remainingCount = remainingCount;
}

public static MemberShip of(final User student, final Product product, final Integer remainingCount) {
return new MemberShip(null, student, product, remainingCount);
}

public void decreasedCount() {
validateMinRemainingCount();
remainingCount -= 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public class Product extends BaseEntity {

private Integer count;

public Product(final String name, final ProductType type, final User teacher, final Integer count) {
public Product(final Long id, final String name, final ProductType type, final User teacher, final Integer count) {
this.id = id;
this.name = name;
this.type = type;
this.teacher = teacher;
Expand Down
14 changes: 8 additions & 6 deletions doochul/src/main/java/org/doochul/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.doochul.domain.BaseEntity;

@Entity
Expand All @@ -36,13 +37,14 @@ public class User extends BaseEntity {
@Enumerated(EnumType.STRING)
private Identity identity;

public User(
final String name,
final String deviceToken,
final String passWord,
final Gender gender,
final Identity identity
public User(final Long id,
final String name,
final String deviceToken,
final String passWord,
final Gender gender,
final Identity identity
) {
this.id = id;
this.name = name;
this.deviceToken = deviceToken;
this.passWord = passWord;
Expand Down
16 changes: 16 additions & 0 deletions doochul/src/main/java/org/doochul/ui/MemberShipController.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
package org.doochul.ui;

import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.doochul.application.MemberShipService;
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("/memberShip")
public class MemberShipController {

private final MemberShipService memberShipService;

@PostMapping("/apply/{productId}")
public ResponseEntity<Void> apply(@AuthenticationPrincipal final Long userId, @PathVariable final Long productId) {
Long memberShipId = memberShipService.save(userId, productId);
return ResponseEntity.created(URI.create("/memberShips" + memberShipId)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.doochul.application;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.times;

import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.doochul.domain.membership.MemberShip;
import org.doochul.domain.membership.MemberShipRepository;
import org.doochul.domain.product.Product;
import org.doochul.domain.product.ProductRepository;
import org.doochul.domain.product.ProductType;
import org.doochul.domain.user.Gender;
import org.doochul.domain.user.Identity;
import org.doochul.domain.user.User;
import org.doochul.domain.user.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

@SpringBootTest
public class MemberShipServiceTest {

@MockBean
private MemberShipRepository memberShipRepository;

@Autowired
private ProductRepository productRepository;

@Autowired
private UserRepository userRepository;

@Autowired
private RedisService redisService;

@Test
void memberShip_save() throws InterruptedException {
// Given
User user = new User(2L, "JEON", "deviceToken1234", "password1234", Gender.MEN, Identity.GENERAL);
User teacher = new User(1L, "Faker", "deviceToken123", "password123", Gender.MEN, Identity.TEACHER);

userRepository.save(user);
userRepository.save(teacher);

Product product = new Product(1L, "페이커", ProductType.LOL, teacher, 10);
productRepository.save(product);

ExecutorService executorService = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(5);

given(memberShipRepository.save(any())).willReturn(new MemberShip(1L, user, product, 10));

for (int i = 0; i < 5; i++) {
executorService.submit(() -> {
try {
final String key = Long.toString(user.getId());
if (redisService.setNX(key, "apply", Duration.ofSeconds(5))) {
memberShipRepository.save(MemberShip.of(user, product, product.getCount()));
redisService.delete(key);
}
} finally {
latch.countDown();
}
});
}

latch.await();
executorService.shutdown();

then(memberShipRepository).should(times(1)).save(any());
}
}