diff --git a/.gitignore b/.gitignore index 35cf4ac..ab283fd 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,7 @@ out/ ### HTTP TEST ### *.http + application.log -/src/main/resources/logback.xml \ No newline at end of file +/src/main/resources/logback.xml + diff --git a/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java b/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java index 973fe1a..8be3e6f 100644 --- a/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java +++ b/src/main/java/com/soongsil/CoffeeChat/config/SecurityConfig.java @@ -27,6 +27,7 @@ @Configuration @EnableWebSecurity public class SecurityConfig { + private final CustomOAuth2UserService customOAuth2UserService; private final CustomSuccessHandler customSuccessHandler; private final JWTUtil jwtUtil; diff --git a/src/main/java/com/soongsil/CoffeeChat/config/async/AsyncConfig.java b/src/main/java/com/soongsil/CoffeeChat/config/async/AsyncConfig.java index 9d5af27..9467c53 100644 --- a/src/main/java/com/soongsil/CoffeeChat/config/async/AsyncConfig.java +++ b/src/main/java/com/soongsil/CoffeeChat/config/async/AsyncConfig.java @@ -8,6 +8,7 @@ @Configuration public class AsyncConfig { + @Bean(name = "mailExecutor") public Executor mailExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); @@ -19,3 +20,4 @@ public Executor mailExecutor() { return executor; } } + diff --git a/src/main/java/com/soongsil/CoffeeChat/controller/ApplicationController.java b/src/main/java/com/soongsil/CoffeeChat/controller/ApplicationController.java index e18f50b..0663e62 100644 --- a/src/main/java/com/soongsil/CoffeeChat/controller/ApplicationController.java +++ b/src/main/java/com/soongsil/CoffeeChat/controller/ApplicationController.java @@ -2,6 +2,7 @@ import static com.soongsil.CoffeeChat.enums.RequestUri.*; + import java.util.List; import org.springframework.http.ResponseEntity; @@ -20,6 +21,7 @@ import com.soongsil.CoffeeChat.dto.ApplicationGetResponseDto; import com.soongsil.CoffeeChat.dto.ApplicationMatchResponseDto; import com.soongsil.CoffeeChat.dto.Oauth.CustomOAuth2User; + import com.soongsil.CoffeeChat.service.ApplicationService; import io.swagger.v3.oas.annotations.Operation; @@ -33,6 +35,7 @@ @Tag(name = "APPLICATION", description = "Application 관련 api") public class ApplicationController { + private final ApplicationService applicationService; @PostMapping diff --git a/src/main/java/com/soongsil/CoffeeChat/controller/EmailController.java b/src/main/java/com/soongsil/CoffeeChat/controller/EmailController.java index ec67d2a..7c69c78 100644 --- a/src/main/java/com/soongsil/CoffeeChat/controller/EmailController.java +++ b/src/main/java/com/soongsil/CoffeeChat/controller/EmailController.java @@ -20,10 +20,12 @@ public class EmailController { private final EmailUtil emailUtil; + @GetMapping() public CompletableFuture sendAuthenticationMail(@RequestParam("email") String receiver) throws MessagingException, InterruptedException { return emailUtil.sendAuthenticationEmail(receiver); + } } diff --git a/src/main/java/com/soongsil/CoffeeChat/controller/MentorController.java b/src/main/java/com/soongsil/CoffeeChat/controller/MentorController.java index 91a75ef..a5700ce 100644 --- a/src/main/java/com/soongsil/CoffeeChat/controller/MentorController.java +++ b/src/main/java/com/soongsil/CoffeeChat/controller/MentorController.java @@ -4,6 +4,7 @@ import java.util.List; + import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; @@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; + import com.soongsil.CoffeeChat.dto.MentorGetListResponseDto; import com.soongsil.CoffeeChat.dto.MentorGetUpdateDetailDto; import com.soongsil.CoffeeChat.dto.MentorIntroductionUpdateRequestDto; @@ -23,6 +25,7 @@ import com.soongsil.CoffeeChat.dto.PossibleDateCreateGetResponseDto; import com.soongsil.CoffeeChat.enums.ClubEnum; import com.soongsil.CoffeeChat.enums.PartEnum; + import com.soongsil.CoffeeChat.service.MentorService; import io.swagger.v3.oas.annotations.Operation; @@ -33,8 +36,10 @@ @RestController @Tag(name = "MENTOR", description = "멘토 관련 api") public class MentorController { + private final MentorService mentorService; + public MentorController(MentorService mentorService) { this.mentorService = mentorService; } diff --git a/src/main/java/com/soongsil/CoffeeChat/controller/UserController.java b/src/main/java/com/soongsil/CoffeeChat/controller/UserController.java index da0af93..3cb9553 100644 --- a/src/main/java/com/soongsil/CoffeeChat/controller/UserController.java +++ b/src/main/java/com/soongsil/CoffeeChat/controller/UserController.java @@ -135,4 +135,10 @@ public ResponseEntity getUserInfo(Authentication authenticatio HttpStatus.OK); } + @PostMapping("/create") + public ResponseEntity createUsersAndMentors() { + userService.createUsersAndMentors(); + return ResponseEntity.ok("500 Users and Mentors have been created successfully."); + } + } diff --git a/src/main/java/com/soongsil/CoffeeChat/dto/ApplicationPerformanceRequestDto.java b/src/main/java/com/soongsil/CoffeeChat/dto/ApplicationPerformanceRequestDto.java new file mode 100644 index 0000000..3ef34a3 --- /dev/null +++ b/src/main/java/com/soongsil/CoffeeChat/dto/ApplicationPerformanceRequestDto.java @@ -0,0 +1,12 @@ +package com.soongsil.CoffeeChat.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ApplicationPerformanceRequestDto { + private ApplicationCreateRequest applicationCreateRequest; + private PerformanceRequest performanceRequest; + private int apiNum; +} diff --git a/src/main/java/com/soongsil/CoffeeChat/dto/PerformanceRequest.java b/src/main/java/com/soongsil/CoffeeChat/dto/PerformanceRequest.java new file mode 100644 index 0000000..34927ee --- /dev/null +++ b/src/main/java/com/soongsil/CoffeeChat/dto/PerformanceRequest.java @@ -0,0 +1,40 @@ +package com.soongsil.CoffeeChat.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +public class PerformanceRequest { + private int userCount; + private int durationInSeconds; + private int totalRequests; + + // Getters and setters + public int getUserCount() { + return userCount; + } + + public void setUserCount(int userCount) { + this.userCount = userCount; + } + + public int getDurationInSeconds() { + return durationInSeconds; + } + + public void setDurationInSeconds(int durationInSeconds) { + this.durationInSeconds = durationInSeconds; + } + + public int getTotalRequests() { + return totalRequests; + } + + public void setTotalRequests(int totalRequests) { + this.totalRequests = totalRequests; + } +} diff --git a/src/main/java/com/soongsil/CoffeeChat/dto/PerformanceResult.java b/src/main/java/com/soongsil/CoffeeChat/dto/PerformanceResult.java new file mode 100644 index 0000000..936af6e --- /dev/null +++ b/src/main/java/com/soongsil/CoffeeChat/dto/PerformanceResult.java @@ -0,0 +1,21 @@ +package com.soongsil.CoffeeChat.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class PerformanceResult { + private List averageTimesPerThread; + private double overallAverageTime; + + public PerformanceResult(List averageTimesPerThread, double overallAverageTime) { + this.averageTimesPerThread = averageTimesPerThread; + this.overallAverageTime = overallAverageTime; + } + + // Getters and Setters +} + diff --git a/src/main/java/com/soongsil/CoffeeChat/service/ApplicationService.java b/src/main/java/com/soongsil/CoffeeChat/service/ApplicationService.java index 34fc3e6..50b94a0 100644 --- a/src/main/java/com/soongsil/CoffeeChat/service/ApplicationService.java +++ b/src/main/java/com/soongsil/CoffeeChat/service/ApplicationService.java @@ -1,5 +1,6 @@ package com.soongsil.CoffeeChat.service; + import static com.soongsil.CoffeeChat.controller.exception.enums.ApplicationErrorCode.*; import static com.soongsil.CoffeeChat.controller.exception.enums.MentorErrorCode.*; import static com.soongsil.CoffeeChat.controller.exception.enums.PossibleDateErrorCode.*; @@ -15,9 +16,11 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import com.soongsil.CoffeeChat.controller.exception.CustomException; @@ -42,12 +45,15 @@ import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; @Service @RequiredArgsConstructor @Slf4j + public class ApplicationService { + private final EntityManager em; private final ApplicationRepository applicationRepository; private final MentorRepository mentorRepository; @@ -55,6 +61,25 @@ public class ApplicationService { private final UserRepository userRepository; private final PossibleDateRepository possibleDateRepository; private final EmailUtil emailUtil; + private final ThreadPoolTaskExecutor executor; + + public ApplicationService(EntityManager em, + ApplicationRepository applicationRepository, + MentorRepository mentorRepository, + MenteeRepository menteeRepository, + UserRepository userRepository, + PossibleDateRepository possibleDateRepository, + EmailUtil emailUtil, + @Qualifier("performanceExecutor") ThreadPoolTaskExecutor executor) { + this.em = em; + this.applicationRepository = applicationRepository; + this.mentorRepository = mentorRepository; + this.menteeRepository = menteeRepository; + this.userRepository = userRepository; + this.possibleDateRepository = possibleDateRepository; + this.emailUtil = emailUtil; + this.executor = executor; + } @Autowired private ApplicationContext applicationContext; // 프록시를 통해 자신을 호출하기 위해 ApplicationContext 주입 @@ -62,6 +87,7 @@ public class ApplicationService { @Autowired private RedisTemplate redisTemplate; + @Transactional public ApplicationCreateResponseDto createApplication(ApplicationCreateRequestDto request, String userName) throws Exception { @@ -160,6 +186,62 @@ public ApplicationCreateResponseDto createApplication(ApplicationCreateRequestDt ); } + @Transactional + public ApplicationCreateResponse createApplicationWithJPA(ApplicationCreateRequest request, String userName) throws Exception { + System.out.println("여긴들어옴"); + String lockKey = "lock:" + request.getMentorId() + ":" +request.getDate()+":"+ request.getStartTime(); + ValueOperations valueOperations = redisTemplate.opsForValue(); + + boolean isLockAcquired = valueOperations.setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS); + if (!isLockAcquired) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Lock을 획득하지 못하였습니다."); //409반환 + } + + try { + System.out.println("mentorid: " + request.getMentorId() + ", " + request.getDate() + ", " + request.getStartTime() + ", " + request.getEndTime()); + Mentor findMentor = mentorRepository.findById(request.getMentorId()).get(); + User findMentorUser = userRepository.findByMentor(findMentor); + User findMenteeUser = userRepository.findByUsername(userName); + Mentee findMentee = findMenteeUser.getMentee(); + + LocalTime startTime = request.getStartTime(); + LocalDate date = request.getDate(); + + // possibleDate 불러오는 JPQL + TypedQuery query = em.createQuery( + "SELECT p FROM PossibleDate p JOIN p.mentor m WHERE m.id = :mentorId AND p.startTime = :startTime AND p.date = :date", + PossibleDate.class); + query.setParameter("mentorId", request.getMentorId()); + query.setParameter("startTime", startTime); + query.setParameter("date", date); + + Optional possibleDateOpt = query.getResultList().stream().findFirst(); + + if (possibleDateOpt.isPresent()) { + PossibleDate possibleDate = possibleDateOpt.get(); + if (!possibleDate.isActive()) { + throw new ResponseStatusException(HttpStatus.GONE, "이미 신청된 시간입니다."); //410 반환 + } + System.out.println("possibleDate.getId() = " + possibleDate.getId()); + possibleDate.setActive(false); + possibleDateRepository.save(possibleDate); + } else { + throw new Exception("NOT FOUND"); + } + + Application savedApplication = applicationRepository.save(request.toEntity(findMentor, findMentee)); + + ApplicationService proxy = applicationContext.getBean(ApplicationService.class); + proxy.sendApplicationMatchedEmailAsync(findMenteeUser.getEmail(), findMentorUser.getName(), + findMenteeUser.getName(), savedApplication.getDate(), savedApplication.getStartTime(), + savedApplication.getEndTime()); + + return ApplicationCreateResponse.from(savedApplication); + } finally { + redisTemplate.delete(lockKey); + } + } + @Async("mailExecutor") public void sendApplicationMatchedEmailAsync(String email, String mentorName, String menteeName, LocalDate date, LocalTime startTime, LocalTime endTime) throws MessagingException { diff --git a/src/main/java/com/soongsil/CoffeeChat/service/MentorService.java b/src/main/java/com/soongsil/CoffeeChat/service/MentorService.java index 480f9ca..b6d0b51 100644 --- a/src/main/java/com/soongsil/CoffeeChat/service/MentorService.java +++ b/src/main/java/com/soongsil/CoffeeChat/service/MentorService.java @@ -2,10 +2,12 @@ import static com.soongsil.CoffeeChat.controller.exception.enums.MentorErrorCode.*; + import java.util.List; import java.util.Optional; import java.util.stream.Collectors; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,6 +36,7 @@ public class MentorService { private final MentorRepository mentorRepository; private final UserRepository userRepository; private final PossibleDateRepository possibleDateRepository; + private final ThreadPoolTaskExecutor executor; public List getMentorDtoListByPart(PartEnum part) { return mentorRepository.getMentorListByPart(part); //일반join @@ -45,6 +48,7 @@ public List getMentorDtoListByClub(ClubEnum club) { public List getMentorDtoListByPartAndClub(PartEnum part, ClubEnum club) { return mentorRepository.getMentorListByPartAndClub(part, club); + } public List findPossibleDateListByMentor(Long mentorId) { diff --git a/src/main/java/com/soongsil/CoffeeChat/service/RefreshTokenService.java b/src/main/java/com/soongsil/CoffeeChat/service/RefreshTokenService.java index ab805ab..de03671 100644 --- a/src/main/java/com/soongsil/CoffeeChat/service/RefreshTokenService.java +++ b/src/main/java/com/soongsil/CoffeeChat/service/RefreshTokenService.java @@ -89,9 +89,11 @@ public ResponseEntity reissueByRefreshToken(HttpServletRequest request, HttpS String role = jwtUtil.getRole(refresh); // Make new JWT + String newAccess = jwtUtil.createJwt("access", username, role, 1800000000L); String newRefresh = jwtUtil.createJwt("refresh", username, role, 86400000L); + // Refresh 토큰 저장: DB에 기존의 Refresh 토큰 삭제 후 새 Refresh 토큰 저장 refreshRepository.deleteByRefresh(refresh); addRefreshEntity(username, newRefresh, 86400000L); diff --git a/src/main/java/com/soongsil/CoffeeChat/service/UserService.java b/src/main/java/com/soongsil/CoffeeChat/service/UserService.java index b367775..128308a 100644 --- a/src/main/java/com/soongsil/CoffeeChat/service/UserService.java +++ b/src/main/java/com/soongsil/CoffeeChat/service/UserService.java @@ -1,9 +1,11 @@ package com.soongsil.CoffeeChat.service; + import java.util.HashMap; import java.util.Map; import com.soongsil.CoffeeChat.entity.Introduction; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -22,8 +24,10 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + @Service @RequiredArgsConstructor @Slf4j @@ -110,4 +114,5 @@ public UserGetUpdateDto findUserInfo(String username) { User user = userRepository.findByUsername(username); return UserGetUpdateDto.toDto(user); } + } diff --git a/src/main/java/com/soongsil/CoffeeChat/util/email/EmailUtil.java b/src/main/java/com/soongsil/CoffeeChat/util/email/EmailUtil.java index 4437131..e6f2e92 100644 --- a/src/main/java/com/soongsil/CoffeeChat/util/email/EmailUtil.java +++ b/src/main/java/com/soongsil/CoffeeChat/util/email/EmailUtil.java @@ -1,5 +1,6 @@ package com.soongsil.CoffeeChat.util.email; + import java.time.LocalDate; import java.time.LocalTime; import java.util.concurrent.CompletableFuture; @@ -18,6 +19,7 @@ public class EmailUtil { private final JavaMailSender javaMailSender; + private final ApplicationContext applicationContext; @Async("mailExecutor") public void sendMail(String receiver, String subject, String content) throws MessagingException { @@ -28,7 +30,21 @@ public void sendMail(String receiver, String subject, String content) throws Mes javaMailSender.send(message); } + public long sendAuthenticationEmailWithTiming(String receiver) throws MessagingException { + long startTime = System.currentTimeMillis(); + + // 프록시를 통해 비동기 메서드 호출 + EmailUtil proxy = applicationContext.getBean(EmailUtil.class); + proxy.sendAuthenticationEmailAsync(receiver); + + long endTime = System.currentTimeMillis(); + + // 실행 시간을 측정하여 반환 (메일 발송 대기 없이 즉시 반환) + return endTime - startTime; + } + @Async("mailExecutor") + public CompletableFuture sendAuthenticationEmail(String receiver) throws MessagingException, InterruptedException { @@ -37,7 +53,8 @@ public CompletableFuture sendAuthenticationEmail(String receiver) throws sendMail(receiver, "[COGO] 이메일 인증번호입니다.", createMessageTemplate("[COGO] 이메일 인증 안내", "이메일 인증을 완료하려면 아래의 인증 번호를 사용하여 계속 진행하세요:", code)); - return CompletableFuture.completedFuture(code); + + return endTime-startTime; } public void sendApplicationMatchedEmail(String receiver, String mentorName, String menteeName, LocalDate date,