From 390da86ab74010cf0e41cd07f2b7fe9cd4ea1567 Mon Sep 17 00:00:00 2001 From: lehojun Date: Thu, 12 Jun 2025 16:23:30 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20#44=20=EC=98=A4=EB=8A=98,=20=EB=88=84?= =?UTF-8?q?=EC=A0=81,=20=EB=AA=A9=ED=91=9C=20=EA=B3=B5=EB=B6=80=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/be/BeApplication.java | 2 + src/main/java/com/example/be/domain/User.java | 13 ++- .../example/be/repository/UserRepository.java | 5 ++ .../example/be/service/PostServiceImpl.java | 3 - .../example/be/service/StudyServiceImpl.java | 83 +++++++++++++++++++ .../example/be/service/UserServiceImpl.java | 46 ++-------- .../be/web/controller/PostController.java | 9 -- .../be/web/controller/StudyController.java | 39 +++++++++ .../be/web/controller/UserController.java | 6 +- .../java/com/example/be/web/dto/UserDTO.java | 11 +++ 10 files changed, 161 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/example/be/service/StudyServiceImpl.java create mode 100644 src/main/java/com/example/be/web/controller/StudyController.java diff --git a/src/main/java/com/example/be/BeApplication.java b/src/main/java/com/example/be/BeApplication.java index d543dcf..c4f75dc 100644 --- a/src/main/java/com/example/be/BeApplication.java +++ b/src/main/java/com/example/be/BeApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; +@EnableScheduling @SpringBootApplication public class BeApplication { diff --git a/src/main/java/com/example/be/domain/User.java b/src/main/java/com/example/be/domain/User.java index b009827..3068895 100644 --- a/src/main/java/com/example/be/domain/User.java +++ b/src/main/java/com/example/be/domain/User.java @@ -3,6 +3,9 @@ import com.example.be.domain.enums.LoginType; import jakarta.persistence.*; import lombok.*; +import org.springframework.cglib.core.Local; + +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @@ -34,10 +37,16 @@ public class User { private String providerId; - private String gptKey; - private LocalDateTime createDate; + @Column(columnDefinition = "int default 0") + private int todayStudyTime; + + @Column(columnDefinition = "int default 0") + private int totalStudyTime; + + @Column(columnDefinition = "int default 0") + private int goalStudyTime; //Relationships @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) diff --git a/src/main/java/com/example/be/repository/UserRepository.java b/src/main/java/com/example/be/repository/UserRepository.java index 68fd197..e32fd0e 100644 --- a/src/main/java/com/example/be/repository/UserRepository.java +++ b/src/main/java/com/example/be/repository/UserRepository.java @@ -2,6 +2,7 @@ import com.example.be.domain.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import java.util.Optional; import java.util.UUID; @@ -15,4 +16,8 @@ public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); Optional findByEmail(String email); + + @Modifying + @Query("update User u Set u.todayStudyTime = 0") + void updateTodayStudyTime(); } diff --git a/src/main/java/com/example/be/service/PostServiceImpl.java b/src/main/java/com/example/be/service/PostServiceImpl.java index 86af5ad..6b00e99 100644 --- a/src/main/java/com/example/be/service/PostServiceImpl.java +++ b/src/main/java/com/example/be/service/PostServiceImpl.java @@ -1,7 +1,6 @@ package com.example.be.service; -import com.example.be.apiPayload.ApiResponse; import com.example.be.apiPayload.code.status.ErrorStatus; import com.example.be.apiPayload.exception.handler.PostHandler; import com.example.be.apiPayload.exception.handler.UserHandler; @@ -13,10 +12,8 @@ import com.example.be.web.dto.CommentDTO; import com.example.be.web.dto.CommonDTO; import com.example.be.web.dto.PostDTO; -import com.example.be.web.dto.UserDTO; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/com/example/be/service/StudyServiceImpl.java b/src/main/java/com/example/be/service/StudyServiceImpl.java new file mode 100644 index 0000000..b3036e2 --- /dev/null +++ b/src/main/java/com/example/be/service/StudyServiceImpl.java @@ -0,0 +1,83 @@ +package com.example.be.service; + +import com.example.be.apiPayload.code.status.ErrorStatus; +import com.example.be.apiPayload.exception.handler.UserHandler; +import com.example.be.domain.User; +import com.example.be.repository.UserRepository; +import com.example.be.web.dto.CommonDTO; +import com.example.be.web.dto.UserDTO; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class StudyServiceImpl { + private final JwtUtilServiceImpl jwtUtilService; + private final UserRepository userRepository; + + private User getUserFromRequest(HttpServletRequest request) { + try { + String accessToken = jwtUtilService.extractTokenFromCookie(request, "accessToken"); + if (accessToken != null) { + String userId = jwtUtilService.getUserIdFromToken(accessToken); + return userRepository.findByUserId(UUID.fromString(userId)).orElse(null); + } + } catch (Exception e) { + throw new UserHandler(ErrorStatus._NOT_FOUND_COOKIE); + } + return null; + } + + public CommonDTO.IsSuccessDTO addStudyTime(HttpServletRequest request, int time) { + User user= getUserFromRequest(request); + + if (user != null) { + user.setTodayStudyTime(user.getTodayStudyTime()+ time); + user.setTotalStudyTime(user.getTotalStudyTime()+ time); + userRepository.save(user); + } + + return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); + } + + public CommonDTO.IsSuccessDTO addStudyGoalTime(HttpServletRequest request, int time) { + User user= getUserFromRequest(request); + + if (user != null) { + user.setGoalStudyTime(time); + userRepository.save(user); + } + + return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); + } + + public UserDTO.studyTimeResponseDto getStudyTime(HttpServletRequest request) { + User user= getUserFromRequest(request); + + int today = user.getTodayStudyTime(); + int total = user.getTotalStudyTime(); + int goal = user.getGoalStudyTime(); + + return UserDTO.studyTimeResponseDto.builder() + .userId(user.getId()) + .todayStudyTime(today/60 +"시간 " + today%60+"분") + .totalStudyTime(total/60 + "시간 " + total%60 + "분") + .goalStudyTime(goal/60 + "시간 " + goal%60 + "분") + .build(); + } + + //매일 자정에 오늘 공부 시간 초기화 + @Scheduled(cron = "0 0 0 * * *") + @Transactional + public void resetTodayStudyTime() { + userRepository.updateTodayStudyTime(); + } + + + +} diff --git a/src/main/java/com/example/be/service/UserServiceImpl.java b/src/main/java/com/example/be/service/UserServiceImpl.java index 7885e94..117e31b 100644 --- a/src/main/java/com/example/be/service/UserServiceImpl.java +++ b/src/main/java/com/example/be/service/UserServiceImpl.java @@ -4,7 +4,6 @@ import com.example.be.apiPayload.exception.handler.UserHandler; import com.example.be.domain.RefreshToken; import com.example.be.domain.User; -import com.example.be.domain.enums.LoginType; import com.example.be.repository.RefreshTokenRepository; import com.example.be.repository.UserRepository; import com.example.be.web.dto.CommonDTO; @@ -15,10 +14,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.net.URLEncoder; import java.time.LocalDateTime; import java.util.UUID; @@ -27,7 +27,7 @@ @RequiredArgsConstructor public class UserServiceImpl extends SimpleUrlAuthenticationSuccessHandler { private final UserRepository userRepository; - private final JwtUtilServiceImpl jwtUtil; + private final JwtUtilServiceImpl jwtUtilService; private final RefreshTokenRepository refreshTokenRepository; @Value("${jwt.access-token.expiration-time}") @@ -72,7 +72,7 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet refreshTokenRepository.deleteByUserId(user.getUserId()); // RefreshToken 재발급 - String refreshToken = jwtUtil.generateRefreshToken(user.getUserId(), REFRESH_TOKEN_EXPIRATION_TIME); + String refreshToken = jwtUtilService.generateRefreshToken(user.getUserId(), REFRESH_TOKEN_EXPIRATION_TIME); RefreshToken newRefreshToken = RefreshToken.builder() .userId(user.getUserId()) @@ -82,29 +82,14 @@ public CommonDTO.IsSuccessDTO login(UserDTO.LoginRequestDto request, HttpServlet refreshTokenRepository.save(newRefreshToken); // AccessToken 발급 - String accessToken = jwtUtil.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME); - - String origin = httpRequest.getHeader("Origin"); -// boolean isLocalhost = origin != null && origin.contains("localhost"); -// -// // 액세스 토큰 쿠키 설정 -// if (isLocalhost) { -// // 로컬 개발 환경: SameSite=None, Secure=false -// response.addHeader("Set-Cookie", -// String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", -// accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); -// response.addHeader("Set-Cookie", -// String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=None", -// refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); -// } else { - // 배포 환경: SameSite=None, Secure=true + String accessToken = jwtUtilService.generateAccessToken(user.getUserId(), ACCESS_TOKEN_EXPIRATION_TIME); + response.addHeader("Set-Cookie", String.format("accessToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", accessToken, (int) (ACCESS_TOKEN_EXPIRATION_TIME / 1000))); response.addHeader("Set-Cookie", String.format("refreshToken=%s; Path=/; Max-Age=%d; HttpOnly; Secure; SameSite=None", refreshToken, (int) (REFRESH_TOKEN_EXPIRATION_TIME / 1000))); -// } return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); } @@ -116,7 +101,7 @@ public UserDTO.UserResponseDto getUserInfo(String accessToken) { } // 토큰에서 사용자 ID 추출 - String userId = jwtUtil.getUserIdFromToken(accessToken); + String userId = jwtUtilService.getUserIdFromToken(accessToken); // 사용자 정보 조회 User user = userRepository.findByUserId(UUID.fromString(userId)) @@ -138,27 +123,12 @@ public CommonDTO.IsSuccessDTO logout(HttpServletResponse response, HttpServletRe if(cookies == null) { throw new UserHandler(ErrorStatus._NOT_FOUND_COOKIE); } - - // Origin 헤더로 환경 판단 -// String origin = request.getHeader("Origin"); -// boolean isSecure = origin == null || !origin.contains("localhost"); - - // 쿠키 삭제 - addHeader 방식 사용 -// if (isSecure) { - // 배포 환경 response.addHeader("Set-Cookie", "accessToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None"); response.addHeader("Set-Cookie", "refreshToken=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=None"); -// } else { -// // 로컬 환경 -// response.addHeader("Set-Cookie", -// "accessToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None"); -// response.addHeader("Set-Cookie", -// "refreshToken=; Path=/; Max-Age=0; HttpOnly; SameSite=None"); -// } return CommonDTO.IsSuccessDTO.builder().isSuccess(true).build(); } - } + diff --git a/src/main/java/com/example/be/web/controller/PostController.java b/src/main/java/com/example/be/web/controller/PostController.java index 3b0f032..5506564 100644 --- a/src/main/java/com/example/be/web/controller/PostController.java +++ b/src/main/java/com/example/be/web/controller/PostController.java @@ -1,25 +1,16 @@ package com.example.be.web.controller; import com.example.be.apiPayload.ApiResponse; -import com.example.be.apiPayload.code.status.ErrorStatus; -import com.example.be.apiPayload.exception.handler.UserHandler; -import com.example.be.domain.User; -import com.example.be.repository.UserRepository; -import com.example.be.service.JwtUtilServiceImpl; import com.example.be.service.PostLikeServiceImpl; import com.example.be.service.PostServiceImpl; -import com.example.be.service.UserServiceImpl; import com.example.be.web.dto.CommonDTO; import com.example.be.web.dto.PostDTO; -import com.example.be.web.dto.UserDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequestMapping("/post") @RequiredArgsConstructor diff --git a/src/main/java/com/example/be/web/controller/StudyController.java b/src/main/java/com/example/be/web/controller/StudyController.java new file mode 100644 index 0000000..aa75aca --- /dev/null +++ b/src/main/java/com/example/be/web/controller/StudyController.java @@ -0,0 +1,39 @@ +package com.example.be.web.controller; + +import com.example.be.apiPayload.ApiResponse; +import com.example.be.service.StudyServiceImpl; +import com.example.be.service.UserServiceImpl; +import com.example.be.web.dto.CommonDTO; +import com.example.be.web.dto.UserDTO; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/study") +@RequiredArgsConstructor +public class StudyController { + + private final StudyServiceImpl studyService; + + @PostMapping("/{time}") + @Operation(summary = "오늘, 누적 공부 시간 추가 API") + public ApiResponse addStudyTime(HttpServletRequest request, @PathVariable("time")int time) { + return ApiResponse.onSuccess(studyService.addStudyTime(request, time)); + } + + @PostMapping("/goal/{time}") + @Operation(summary = "목표 공부 시간 수정 API") + public ApiResponse addStudyGoalTime(HttpServletRequest request, @PathVariable("time")int time) { + return ApiResponse.onSuccess(studyService.addStudyGoalTime(request, time)); + } + + @GetMapping("/time") + @Operation(summary = "오늘, 누적, 목표 공부 시간 조회 API") + public ApiResponse getStudyTime(HttpServletRequest request) { + return ApiResponse.onSuccess(studyService.getStudyTime(request)); + } + + +} diff --git a/src/main/java/com/example/be/web/controller/UserController.java b/src/main/java/com/example/be/web/controller/UserController.java index fced790..236b5c6 100644 --- a/src/main/java/com/example/be/web/controller/UserController.java +++ b/src/main/java/com/example/be/web/controller/UserController.java @@ -1,18 +1,14 @@ package com.example.be.web.controller; import com.example.be.apiPayload.ApiResponse; -import com.example.be.apiPayload.code.status.ErrorStatus; -import com.example.be.apiPayload.exception.handler.UserHandler; import com.example.be.service.JwtUtilServiceImpl; import com.example.be.service.UserServiceImpl; import com.example.be.web.dto.CommonDTO; import com.example.be.web.dto.UserDTO; -import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @@ -49,4 +45,6 @@ public ApiResponse userInfo(HttpServletRequest request) public ApiResponse logout(HttpServletResponse response, HttpServletRequest request) { return ApiResponse.onSuccess(userService.logout(response, request)); } + + } \ No newline at end of file diff --git a/src/main/java/com/example/be/web/dto/UserDTO.java b/src/main/java/com/example/be/web/dto/UserDTO.java index 6d2737f..5f56593 100644 --- a/src/main/java/com/example/be/web/dto/UserDTO.java +++ b/src/main/java/com/example/be/web/dto/UserDTO.java @@ -37,4 +37,15 @@ public static class LoginRequestDto { private String email; private String password; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class studyTimeResponseDto { + private Long userId; + private String todayStudyTime; + private String totalStudyTime; + private String goalStudyTime; + } }