Skip to content

Commit

Permalink
Merge pull request #123 from UMC5th-bias/develop
Browse files Browse the repository at this point in the history
[DEPLOY] main <- develop 병합
  • Loading branch information
JungYoonShin committed Aug 25, 2024
2 parents f794044 + f99a863 commit 98926a4
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 3 deletions.
19 changes: 19 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 📄 Work Description
- 설명


# ⚙️ ISSUE
- closed #이슈번호


# 📷 Screenshot
- 동영상, 사진, 로그 등등
- ex) 큐알 성공 이미지, 스웨거, 포스트맨 등


# 💬 To Reviewers
리뷰어들에게 하고 싶은 말


# 🔗 Reference
문제를 해결하면서 도움이 되었거나, 참고했던 사이트(코드링크)
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
compileOnly 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-validation'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
Expand Down Expand Up @@ -60,6 +61,11 @@ dependencies {
implementation 'com.google.cloud:google-cloud-storage:2.20.1'
implementation 'org.springframework.cloud:spring-cloud-gcp-starter-storage:1.2.8.RELEASE'
implementation 'net.coobird:thumbnailator:0.4.14'

// STOMP
implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.5.1'
implementation 'org.webjars:stomp-websocket:2.3.4'
}

tasks.named('bootBuildImage') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.favoriteplace.app.controller;

import com.favoriteplace.app.domain.travel.Pilgrimage;
import com.favoriteplace.app.dto.travel.PilgrimageDto;
import com.favoriteplace.app.repository.PilgrimageRepository;
import com.favoriteplace.app.service.PilgrimageCommandService;
import com.favoriteplace.global.exception.ErrorCode;
import com.favoriteplace.global.exception.RestApiException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

@Controller
@Slf4j
@RequiredArgsConstructor
public class PilgrimageSocketController {
private final PilgrimageCommandService pilgrimageService;
private final PilgrimageRepository pilgrimageRepository;

/**
* 테스트 컨트롤러
* 요청 /app/test
* 응답 /pub/pilgrimage
* @param testMsg
* @return
*/
@MessageMapping("/test")
@SendTo("/pub/pilgrimage")
public String pilgrimageCertify(String testMsg){
log.info("socket message: " + testMsg);
return testMsg;
}

/**
* 위도/경도 전달 시 상태 변경 알리는 컨트롤러
* 요청 컨트롤러 /app/location/{pilgrimageId}
* 응답 컨트롤러 /pub/statusUpdate/{pilgrimageId}
* @param pilgrimageId 성지순례 ID
* @param userLocation 위도/경도
* @return
*/
@MessageMapping("/location/{pilgrimageId}")
@SendTo("/pub/statusUpdate/{pilgrimageId}")
public Boolean checkUserLocation(@DestinationVariable Long pilgrimageId, PilgrimageDto.PilgrimageCertifyRequestDto userLocation) {
Pilgrimage pilgrimage = pilgrimageRepository.findById(pilgrimageId)
.orElseThrow(()->new RestApiException(ErrorCode.PILGRIMAGE_NOT_FOUND));

boolean isUserAtPilgrimage = pilgrimageService.isUserAtPilgrimage(pilgrimage, userLocation.getLatitude(), userLocation.getLongitude());

if (!isUserAtPilgrimage) {
// 여기에 이벤트 동작 추가
return false;
}
return true;
}

/**
* 최초 접근 시 버튼 상태 전달하는 컨트롤러
* 요청 컨트롤러 /app/connect/{pilgrimageId}
* 응답 컨트롤러 /pub/statusUpdate/{pilgrimageId}
* @param pilgrimageId 성지순례 ID
* @return
*/
@MessageMapping("/connect/{pilgrimageId}")
@SendTo("/pub/statusUpdate/{pilgrimageId}")
public Boolean sendInitialStatus(@DestinationVariable Long pilgrimageId) {
log.info("User connected to pilgrimage: " + pilgrimageId);
return true; // 초기 버튼 상태
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.favoriteplace.app.dto.travel;

import lombok.*;

public class PilgrimageSocketDto {
@Data
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class ButtonState {
private Boolean certifyButtonEnabled;
private Boolean guestbookButtonEnabled;
private Boolean multiGuestbookButtonEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import com.favoriteplace.app.domain.travel.*;
import com.favoriteplace.app.dto.CommonResponseDto;
import com.favoriteplace.app.dto.travel.PilgrimageDto;
import com.favoriteplace.app.dto.travel.PilgrimageSocketDto;
import com.favoriteplace.app.repository.*;
import com.favoriteplace.global.exception.ErrorCode;
import com.favoriteplace.global.exception.RestApiException;
import com.favoriteplace.global.websocket.RedisService;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -31,14 +33,15 @@
@Transactional
@RequiredArgsConstructor
public class PilgrimageCommandService {
private final MemberRepository memberRepository;
private final RallyRepository rallyRepository;
private final PilgrimageRepository pilgrimageRepository;
private final LikedRallyRepository likedRallyRepository;
private final VisitedPilgrimageRepository visitedPilgrimageRepository;
private final PointHistoryRepository pointHistoryRepository;
private final CompleteRallyRepository completeRallyRepository;
private final AcquiredItemRepository acquiredItemRepository;
private final EntityManager em;
private final RedisService redisService;

/***
* 랠리 찜하기
Expand Down Expand Up @@ -127,8 +130,8 @@ private boolean checkCompleteRally(Member member, Pilgrimage pilgrimage, Long co
}

private boolean checkCoordinate(PilgrimageDto.PilgrimageCertifyRequestDto form, Pilgrimage pilgrimage) {
return pilgrimage.getLatitude() + 0.00135 < form.getLatitude() || pilgrimage.getLatitude() - 0.00135 > form.getLatitude()
|| pilgrimage.getLongitude() + 0.00135 < form.getLongitude() || pilgrimage.getLongitude() - 0.00135 > form.getLongitude();
return pilgrimage.getLatitude() + 0.00135 >= form.getLatitude() && pilgrimage.getLatitude() - 0.00135 <= form.getLatitude()
&& pilgrimage.getLongitude() + 0.00135 >= form.getLongitude() && pilgrimage.getLongitude() - 0.00135 <= form.getLongitude();
}

private void successVisitedAndPointProcess(Member member, Pilgrimage pilgrimage) {
Expand All @@ -139,4 +142,74 @@ private void successVisitedAndPointProcess(Member member, Pilgrimage pilgrimage)
member.updatePoint(15L);
log.info("clear");
}

public boolean isUserAtPilgrimage(Pilgrimage pilgrimage, Double latitude, Double longitude) {
return (pilgrimage.getLatitude() + 0.00135 >= latitude && pilgrimage.getLatitude() - 0.00135 <= latitude) &&
(pilgrimage.getLongitude() + 0.00135 >= longitude && pilgrimage.getLongitude() - 0.00135 <= longitude);
}

/**
* 웹소켓 버튼 상태 이벤트
* @param memberId
* @param pilgrimageId
* @param latitude
* @param longitude
* @return
*/
public PilgrimageSocketDto.ButtonState determineButtonState(Long memberId,
Long pilgrimageId,
double latitude, double longitude) {
Pilgrimage pilgrimage = pilgrimageRepository.findById(pilgrimageId)
.orElseThrow(() -> new RestApiException(ErrorCode.PILGRIMAGE_NOT_FOUND));

Member member = memberRepository.findById(memberId)
.orElseThrow(()->new RestApiException(ErrorCode.USER_NOT_FOUND));

// 사용자가 인증 장소에 있는지 확인
boolean nearPilgrimage = isUserAtPilgrimage(pilgrimage, latitude, longitude);
// redis에 사용자의 인증 기록이 남아있는지 확인 (24시간 이후 만료됨)
boolean isCertificationExpired = redisService.isCertificationExpired(member, pilgrimage);
// 사용자가 지난 24시간 내에 인증 버튼 눌렀는지 확인
boolean certifiedInLast24Hours = checkIfCertifiedInLast24Hours(member, pilgrimage);
// 사용자가 이번 인증하기에 이미 방명록을 작성했는지 확인
boolean hasWrittenGuestbook = checkIfGuestbookWritten(member, pilgrimage);
// 사용자가 24시간 전에 작성한 방명록이 1개 이상인지 확인
boolean hasMultiWrittenGuestbook = checkIfMultiGuestbookWritten(member, pilgrimage);

PilgrimageSocketDto.ButtonState newState = new PilgrimageSocketDto.ButtonState();
if (nearPilgrimage && !certifiedInLast24Hours) {
newState.setCertifyButtonEnabled(true); // 인증하기 버튼 활성화
newState.setGuestbookButtonEnabled(false); // 방명록 쓰기 버튼 비활성화
}
else if (certifiedInLast24Hours && !hasWrittenGuestbook) {
newState.setCertifyButtonEnabled(false); // 인증하기 버튼 비활성화
newState.setGuestbookButtonEnabled(true); // 방명록 쓰기 버튼 활성화
}
else {
newState.setCertifyButtonEnabled(false); // 인증하기 버튼 비활성화
newState.setGuestbookButtonEnabled(false); // 방명록 쓰기 버튼 비활성화
}
return newState;
}

private boolean checkIfCertifiedInLast24Hours(Member member, Pilgrimage pilgrimage) {
// 인증 버튼을 누른 시간 확인
List<VisitedPilgrimage> visitedPilgrimages = visitedPilgrimageRepository
.findByPilgrimageAndMemberOrderByCreatedAtDesc(pilgrimage, member);

ZoneId serverZoneId = ZoneId.of("Asia/Seoul");
ZonedDateTime nowInServerTimeZone = ZonedDateTime.now(serverZoneId);

return false;
}

private boolean checkIfGuestbookWritten(Member member, Pilgrimage pilgrimage) {
// 방명록 작성 여부 확인
return false;
}

private boolean checkIfMultiGuestbookWritten(Member member, Pilgrimage pilgrimage) {
// 방명록 다회 작성 여부 확인
return false;
}
}
37 changes: 37 additions & 0 deletions src/main/java/com/favoriteplace/app/service/WebSocketService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.favoriteplace.app.service;

import com.favoriteplace.app.domain.travel.Pilgrimage;
import com.favoriteplace.app.dto.travel.PilgrimageSocketDto;
import com.favoriteplace.app.repository.PilgrimageRepository;
import com.favoriteplace.global.exception.ErrorCode;
import com.favoriteplace.global.exception.RestApiException;
import com.google.api.gax.rpc.ApiException;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@RequiredArgsConstructor
public class WebSocketService {
private final SimpMessagingTemplate messagingTemplate;
private final PilgrimageCommandService pilgrimageService;
private final PilgrimageRepository pilgrimageRepository;
private Map<Long, PilgrimageSocketDto.ButtonState> lastButtonStateCache = new ConcurrentHashMap<>();

public void handleLocationUpdate(Long userId, Long pilgrimageId, double latitude, double longitude) {
Pilgrimage pilgrimage = pilgrimageRepository.findById(pilgrimageId)
.orElseThrow(()->new RestApiException(ErrorCode.PILGRIMAGE_NOT_FOUND));

PilgrimageSocketDto.ButtonState newState = pilgrimageService.determineButtonState(userId, pilgrimage.getId(), latitude, longitude);

PilgrimageSocketDto.ButtonState lastState = lastButtonStateCache.get(userId);

if (lastState == null || !newState.equals(lastState)) {
lastButtonStateCache.put(userId, newState);
messagingTemplate.convertAndSend("/pub/statusUpdate/" + pilgrimageId, newState);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

// 1. Request Header 에서 JWT 토큰 추출
String token = resolveToken((HttpServletRequest) request);

if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) {
chain.doFilter(request, response);
return;
}

String requestURI = httpServletRequest.getRequestURI();

// 2. validateToken 으로 토큰 유효성 검사
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.favoriteplace.global.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

// Key serializer
template.setKeySerializer(new StringRedisSerializer());

// Value serializer
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

return template;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.favoriteplace.global.security.provider;

import com.favoriteplace.app.dto.member.MemberDto.TokenInfo;
import com.favoriteplace.global.security.CustomUserDetails;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
Expand All @@ -10,6 +11,8 @@
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.util.Date;
import java.util.List;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand Down
Loading

0 comments on commit 98926a4

Please sign in to comment.