diff --git a/src/main/java/com/yapp/betree/BeTreeApplication.java b/src/main/java/com/yapp/betree/BeTreeApplication.java index 9c156b6..ee90fd6 100644 --- a/src/main/java/com/yapp/betree/BeTreeApplication.java +++ b/src/main/java/com/yapp/betree/BeTreeApplication.java @@ -3,7 +3,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; +@EnableScheduling @EnableJpaAuditing @SpringBootApplication public class BeTreeApplication { diff --git a/src/main/java/com/yapp/betree/controller/OAuthController.java b/src/main/java/com/yapp/betree/controller/OAuthController.java index 0f22b53..7bdb4bc 100644 --- a/src/main/java/com/yapp/betree/controller/OAuthController.java +++ b/src/main/java/com/yapp/betree/controller/OAuthController.java @@ -95,7 +95,7 @@ private ResponseEntity buildTokenResponse(HttpServletResponse response, Jw .maxAge(24 * 60 * 60 * 7) .path("/") .secure(true) - .sameSite("None") +// .sameSite("None") .httpOnly(true) .build(); response.setHeader(SET_COOKIE_HEADER, cookie.toString()); @@ -117,7 +117,7 @@ public ResponseEntity logout(@ApiIgnore @LoginUser LoginUserDto loginUser, .maxAge(0) .path("/") .secure(true) - .sameSite("None") +// .sameSite("None") .httpOnly(true) .build(); response.setHeader(SET_COOKIE_HEADER, cookie.toString()); diff --git a/src/main/java/com/yapp/betree/interceptor/TokenInterceptor.java b/src/main/java/com/yapp/betree/interceptor/TokenInterceptor.java index 906374c..22009d8 100644 --- a/src/main/java/com/yapp/betree/interceptor/TokenInterceptor.java +++ b/src/main/java/com/yapp/betree/interceptor/TokenInterceptor.java @@ -23,6 +23,16 @@ @Slf4j public class TokenInterceptor implements HandlerInterceptor { + @Value("dev.email.js") + private String jisu; + @Value("dev.email.sb") + private String subin; + @Value("dev.email.si") + private String sooim; + @Value("dev.email.ym") + private String ym; + @Value("dev.email.yk") + private String yk; private static final String AUTH_TYPE = "Bearer"; public static final String USER_ATTR_KEY = "user"; @@ -51,6 +61,19 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons .orElseThrow(() -> new BetreeException(ErrorCode.USER_TOKEN_ERROR, "헤더에 토큰이 존재하지 않습니다.")); + if (!isFrontEndLocal(authHeader) && isInvalidRefreshToken(request.getCookies())) { + log.info("[리프레시토큰 검증] 비어있으면 실패"); + if (request.getRequestURI().equals("/api/logout")) { + log.info("[리프레시토큰 검증] 이미로그아웃"); + throw new BetreeException(ErrorCode.USER_ALREADY_LOGOUT_TOKEN); + } + log.info("[리프레시토큰 검증] 예외생성"); + response.sendError(ErrorCode.USER_REFRESH_ERROR.getStatus(), ErrorCode.USER_REFRESH_ERROR.getMessage()); + return false; + } + + + log.info("[리프레시토큰 검증] 성공"); if (authHeader.startsWith(AUTH_TYPE)) { authHeader = authHeader.substring(AUTH_TYPE.length()).trim(); } @@ -79,6 +102,25 @@ private void printUserLog(HttpServletRequest request) { log.info("[요청 유저 정보] ip:{}, forward:{}, proto:{}", ip, forward, proto); } + private boolean isFrontEndLocal(String authHeader) { + // FE, BE 개발자들 로그인할때는 토큰검증 임시로 예외처리->로컬개발 가능하게 하려고 + List devEmails = new ArrayList<>(); + devEmails.add(jisu); + devEmails.add(subin); + devEmails.add(yk); + devEmails.add(ym); + devEmails.add(sooim); + + authHeader = authHeader.substring(AUTH_TYPE.length()).trim(); + Claims claims = jwtTokenProvider.parseToken(authHeader); + if (Objects.isNull(claims)) { + throw new BetreeException(ErrorCode.USER_TOKEN_ERROR, "토큰의 payload는 null일 수 없습니다."); + } + + log.info("[개발자 로그인] info : {}", claims.get("email")); + return devEmails.contains(claims.get("email")); + } + private boolean isInvalidRefreshToken(Cookie[] cookies) { if (Objects.isNull(cookies)) { return true; diff --git a/src/main/java/com/yapp/betree/service/NoticeTreeService.java b/src/main/java/com/yapp/betree/service/NoticeTreeService.java index 1fa78ea..4658c7e 100644 --- a/src/main/java/com/yapp/betree/service/NoticeTreeService.java +++ b/src/main/java/com/yapp/betree/service/NoticeTreeService.java @@ -14,11 +14,14 @@ import com.yapp.betree.util.BetreeUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -37,6 +40,7 @@ public class NoticeTreeService { private final UserService userService; + @Transactional public NoticeResponseDto getUnreadMessages(Long userId) { NoticeTree noticeTree = noticeTreeRepository.findByUserId(userId).orElseGet( @@ -65,8 +69,10 @@ public NoticeResponseDto getUnreadMessages(Long userId) { return new NoticeResponseDto(messageRepository.findByUserIdAndAlreadyReadAndDelByReceiver(userId, false, false).size(), messages); } + @Scheduled(cron = "0 * */1 * * *") // 1시간마다 갱신 @Transactional public void batchNoticeTree() { + log.info("실행 시간 {}", LocalDateTime.now()); // 전체 유저 조회 List users = userRepository.findAll(); for (User user : users) { @@ -87,9 +93,13 @@ public void batchNoticeTree(Long userId) { * @param userId */ private void noticeTree(Long userId) { + log.info("...유저 {} 알림나무 생성중 ...", userId); // 안읽은 메시지 List unreadMessages = messageRepository.findByUserIdAndAlreadyReadAndDelByReceiver(userId, false, false); + // 안읽은 메시지 랜덤하게 sorting + Collections.shuffle(unreadMessages); + // 안읽은 메시지 먼저 8개 리스트에 넣음 Set noticeTreeMessages = new HashSet<>(); for (Message m : unreadMessages) { @@ -97,8 +107,13 @@ private void noticeTree(Long userId) { noticeTreeMessages.add(MessageResponseDto.of(m, sender)); } + log.info("...유저 {} 알림나무 생성중 ...안읽은 메시지 {}개 총 {}개 ", userId, unreadMessages.size(), noticeTreeMessages.size()); + // 즐겨찾기 메시지 List favoriteMessages = messageRepository.findAllByUserIdAndFavoriteAndDelByReceiver(userId, true, false); + + // 랜덤 셔플 + Collections.shuffle(favoriteMessages); for (Message m : favoriteMessages) { if (noticeTreeMessages.size() >= 8) { break; // 8개까지만 담음 @@ -106,13 +121,16 @@ private void noticeTree(Long userId) { SendUserDto sender = userService.findBySenderId(m.getSenderId()); noticeTreeMessages.add(MessageResponseDto.of(m, sender)); } + log.info("...유저 {} 알림나무 생성중 ...즐겨찾기 메시지 {}개 총 {}개 ", userId, favoriteMessages.size(), noticeTreeMessages.size()); // 비트리 제공 메시지로 8개까지 다시 채움 long remainCount = 8 - noticeTreeMessages.size(); - for (long i = 1; i <= remainCount; i++) { - noticeTreeMessages.add(BetreeUtils.getBetreeMessage(i)); + List betreeMessageNumber = BetreeUtils.getRandomNum(remainCount); + for(Long number : betreeMessageNumber) { + noticeTreeMessages.add(BetreeUtils.getBetreeMessage(number)); } + log.info("...유저 {} 알림나무 생성중 ...비트리 메시지 {}개 총 {}개 ", userId, remainCount, noticeTreeMessages.size()); log.info("[유저 알림나무 갱신 - 리스트 생성] userId = {}, 알림나무 = {}", userId, noticeTreeMessages); String unreads = noticeTreeMessages .stream() diff --git a/src/main/java/com/yapp/betree/util/BetreeUtils.java b/src/main/java/com/yapp/betree/util/BetreeUtils.java index c0bebb2..942855e 100644 --- a/src/main/java/com/yapp/betree/util/BetreeUtils.java +++ b/src/main/java/com/yapp/betree/util/BetreeUtils.java @@ -4,8 +4,12 @@ import com.yapp.betree.dto.oauth.OAuthUserInfoDto; import com.yapp.betree.dto.response.MessageResponseDto; +import java.util.Collections; +import java.util.List; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.LongStream; public class BetreeUtils { @@ -15,17 +19,25 @@ public class BetreeUtils { private static final String BASE_IMAGE_URL = "image/v2/user_"; private static final String BASE_IMAGE_SUFFIX = ".svg"; public static final ConcurrentHashMap betreeMessages = new ConcurrentHashMap() {{ - put(-1L, "칭찬메시지1"); - put(-2L, "칭찬메시지2"); - put(-3L, "칭찬메시지3"); - put(-4L, "칭찬메시지4"); - put(-5L, "칭찬메시지5"); - put(-6L, "칭찬메시지6"); - put(-7L, "칭찬메시지7"); - put(-8L, "칭찬메시지8"); - + put(-1L, "나무에 물을 주고 나만의 비트리를 길러보세요!"); + put(-2L, "조금만 더 힘 내봅시다!!"); + put(-3L, "여러분 화이팅"); + put(-4L, "넣을만한 좋은 칭찬문구 추천부탁드려요"); + put(-5L, "칭찬메시지 5"); + put(-6L, "칭찬메시지 6"); + put(-7L, "칭찬메시지 7"); + put(-8L, "칭찬메시지 8"); + put(-9L, "칭찬메시지 9"); + put(-10L, "칭찬메시지 10"); + put(-11L, "칭찬메시지 11"); }}; + public static List getRandomNum(long size) { + List candidate = LongStream.range(1, betreeMessages.size()+1).boxed().collect(Collectors.toList()); + Collections.shuffle(candidate); + return candidate.subList(0, (int)size); + } + // 접속 URL ""로 리턴 -> 프론트에서 알아서 처리 public static String makeUserAccessUrl(OAuthUserInfoDto user) { return ""; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 096ee08..1993ae7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -45,5 +45,13 @@ secrets: jwt: token: secret-key: B1e2t3r4e5e6S7e8c9r0e1t - expiration-time: 86400000 - refresh-expiration-time: 86400000 \ No newline at end of file + expiration-time: 600000 + refresh-expiration-time: 86400000 + +dev: + email: + js: default + sb: default + si: default + yk: default + ym: default \ No newline at end of file diff --git a/src/test/java/com/yapp/betree/controller/AcceptanceTest.java b/src/test/java/com/yapp/betree/controller/AcceptanceTest.java index 895075f..b6d5bb6 100644 --- a/src/test/java/com/yapp/betree/controller/AcceptanceTest.java +++ b/src/test/java/com/yapp/betree/controller/AcceptanceTest.java @@ -251,6 +251,9 @@ void createMessagesNoLoginUserTest() throws Exception { assertThat(sender.getNickname()).isEqualTo("익명"); } + + //랜덤설정으로 테스트불가 + @Disabled @Test @DisplayName("알림나무 읽음처리 테스트") void noticeTreeTest() throws Exception { @@ -361,17 +364,6 @@ void noticeTreeTest() throws Exception { assertThat(noticeResponseDto2.getMessages()).hasSize(5); assertThat(noticeResponseDto2.getTotalUnreadMessageCount()).isEqualTo(2); - // 읽은메시지 볼 수 없음 - assertThat( - noticeResponseDto2.getMessages().stream() - .filter(messageResponseDto -> messageResponseDto.getId() == message1.getId()) - .count() - ).isEqualTo(0); - assertThat( - noticeResponseDto2.getMessages().stream() - .filter(messageResponseDto -> messageResponseDto.getId() == message2.getId()) - .count() - ).isEqualTo(0); } @Test diff --git a/src/test/java/com/yapp/betree/controller/OAuthControllerTest.java b/src/test/java/com/yapp/betree/controller/OAuthControllerTest.java index 7817c01..3e43a64 100644 --- a/src/test/java/com/yapp/betree/controller/OAuthControllerTest.java +++ b/src/test/java/com/yapp/betree/controller/OAuthControllerTest.java @@ -121,8 +121,6 @@ void alreadyLogoutTest() throws Exception { assertThat(mvcResult.getResponse().getHeader("Authorization")).isNull(); } - // 쿠키 설정 제거로 테스트불가능 - @Disabled @Test @DisplayName("로그아웃 - 이미 로그아웃된 유저는 예외메시지를 반환한다.") void deleteRefreshTokenTest() throws Exception { @@ -138,8 +136,6 @@ void deleteRefreshTokenTest() throws Exception { assertThat(mvcResult.getResponse().getHeader("Authorization")).isNull(); } - //쿠키 설정 제거로 테스트 불가능 - @Disabled @Test @DisplayName("이미 로그아웃되어 헤더(쿠키)에 리프레시 토큰이 존재하지 않을경우 예외가 발생한다.") void refreshTokenCookieNullTest() throws Exception {