-
Notifications
You must be signed in to change notification settings - Fork 1
지도 관련 API 3개 구현 #44
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
지도 관련 API 3개 구현 #44
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package busanVibe.busan.domain.place.service | ||
|
|
||
| import busanVibe.busan.domain.place.domain.Place | ||
| import busanVibe.busan.domain.place.domain.PlaceImage | ||
| import busanVibe.busan.domain.place.dto.PlaceMapResponseDTO | ||
| import busanVibe.busan.domain.place.enums.PlaceType | ||
| import busanVibe.busan.domain.place.repository.PlaceRepository | ||
| import busanVibe.busan.domain.review.domain.Review | ||
| import busanVibe.busan.domain.review.domain.repository.ReviewRepository | ||
| import busanVibe.busan.global.apiPayload.code.status.ErrorStatus | ||
| import busanVibe.busan.global.apiPayload.exception.handler.ExceptionHandler | ||
| import org.slf4j.LoggerFactory | ||
| import org.springframework.stereotype.Service | ||
| import org.springframework.transaction.annotation.Transactional | ||
| import java.math.BigDecimal | ||
| import java.math.RoundingMode | ||
| import java.time.LocalDateTime | ||
|
|
||
| @Service | ||
| class PlaceCongestionQueryService( | ||
| private val placeRepository: PlaceRepository, | ||
| private val placeRedisUtil: PlaceRedisUtil, | ||
| private val reviewRepository: ReviewRepository, | ||
| ) { | ||
|
|
||
| private val latitudeRange: Double = 0.05 | ||
| private val longitudeRange: Double = 0.05 | ||
|
|
||
| private val log = LoggerFactory.getLogger(PlaceCongestionQueryService::class.java) | ||
|
|
||
| @Transactional(readOnly = true) | ||
| fun getMap(type: PlaceType?, latitude: Double, longitude: Double): PlaceMapResponseDTO.MapListDto{ | ||
|
|
||
| // Place -> name, type, latitude, longitude | ||
| // Redis -> congestion level | ||
|
|
||
| // Place 목록 조회 | ||
| val placeList = placeRepository.findPlacesByLocationAndType( | ||
| BigDecimal(latitude - latitudeRange).setScale(6, RoundingMode.HALF_UP), | ||
| BigDecimal(latitude + latitudeRange).setScale(6, RoundingMode.HALF_UP), | ||
| BigDecimal(longitude - longitudeRange).setScale(6, RoundingMode.HALF_UP), | ||
| BigDecimal(longitude + longitudeRange).setScale(6, RoundingMode.HALF_UP), | ||
| type | ||
| ) | ||
|
|
||
| // DTO 변환 | ||
| val placeDtoList :List<PlaceMapResponseDTO.PlaceMapInfoDto> = placeList.map { | ||
| PlaceMapResponseDTO.PlaceMapInfoDto( | ||
| placeId = it.id, | ||
| name = it.name, | ||
| type = it.type.capitalEnglish, | ||
| latitude = it.latitude, | ||
| longitude = it.longitude, | ||
| congestionLevel = placeRedisUtil.getRedisCongestion(it.id) | ||
| ) | ||
| } | ||
|
Comment on lines
+47
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Potential N+1 performance issue with Redis calls The current implementation makes individual Redis calls for each place when fetching congestion levels. For large result sets, this could impact performance. Consider implementing a batch fetch method in // In PlaceRedisUtil
fun getRedisCongestionBatch(placeIds: List<Long>): Map<Long, Int> {
val keys = placeIds.map { "place:congestion:$it" }
val values = redisTemplate.opsForValue().multiGet(keys)
return placeIds.zip(values ?: emptyList()).associate { (id, value) ->
id to (value?.toIntOrNull() ?: generateAndStoreCongestion(id))
}
}Then update the mapping: + val congestionMap = placeRedisUtil.getRedisCongestionBatch(placeList.map { it.id })
val placeDtoList :List<PlaceMapResponseDTO.PlaceMapInfoDto> = placeList.map {
PlaceMapResponseDTO.PlaceMapInfoDto(
placeId = it.id,
name = it.name,
type = it.type.capitalEnglish,
latitude = it.latitude,
longitude = it.longitude,
- congestionLevel = placeRedisUtil.getRedisCongestion(it.id)
+ congestionLevel = congestionMap[it.id] ?: 0
)
}🤖 Prompt for AI Agents |
||
|
|
||
| return PlaceMapResponseDTO.MapListDto(placeDtoList) | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| fun getPlaceDefault(placeId: Long): PlaceMapResponseDTO.PlaceDefaultInfoDto{ | ||
|
|
||
| // Place -> name, imageList, address, openTime | ||
| // Review -> grade, count | ||
| // Image -> list | ||
| // Redis -> congestion | ||
|
|
||
| // 명소 조회 | ||
| val place: Place? = placeRepository.findByIdWithReviewAndImage(placeId) | ||
| place?: throw ExceptionHandler(ErrorStatus.PLACE_NOT_FOUND) | ||
|
|
||
| // 이미지 조회 | ||
| val placeImageSet: Set<PlaceImage> = place.placeImages | ||
| val placeImageList = placeImageSet.toList() | ||
| .sortedBy { it.createdAt } | ||
| .map { it.imgUrl } | ||
|
|
||
| // 리뷰 조회 | ||
| val reviewSet: Set<Review> = reviewRepository.findByPlace(place).toSet() | ||
| val grade = reviewSet.map { it.score }.average().toFloat() | ||
|
|
||
ggamnunq marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return PlaceMapResponseDTO.PlaceDefaultInfoDto( | ||
| id = place.id, | ||
| name = place.name, | ||
| congestionLevel = placeRedisUtil.getRedisCongestion(place.id), | ||
| grade = grade, | ||
| reviewAmount = reviewSet.size, | ||
| latitude = place.latitude, | ||
| longitude = place.longitude, | ||
| address = place.address, | ||
| isOpen = true, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded isOpen value doesn't reflect actual status The Consider implementing actual open/closed logic based on the place's open times: isOpen = determineIfOpen(place.openTimes, LocalDateTime.now())🤖 Prompt for AI Agents |
||
| imgList = placeImageList | ||
| ) | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| fun getCongestion(placeId: Long): PlaceMapResponseDTO.PlaceCongestionDto { | ||
|
|
||
| val current = LocalDateTime.now() | ||
| log.info("현재 시간: ${current}시") | ||
|
|
||
| val roundedBase = (current.hour / 3) * 3 | ||
|
|
||
| // 최근 6개 3시간 단위 시간 생성 (기준시간 포함 총 7개) | ||
| val hours = (6 downTo 0).map { i -> (roundedBase - i * 3 + 24) % 24 } | ||
|
|
||
| log.info("배열 담기 시작") | ||
|
|
||
| val byTimePercent: List<Float> = hours.map { hour -> | ||
| val adjustedDateTime = current.withHour(hour) | ||
| .withMinute(0).withSecond(0).withNano(0) | ||
| .let { | ||
| if (hour > current.hour) it.minusDays(1) else it | ||
| } | ||
| placeRedisUtil.getTimeCongestion(placeId, adjustedDateTime) | ||
| } | ||
|
|
||
| log.info("배열 담기 끝") | ||
|
|
||
| return PlaceMapResponseDTO.PlaceCongestionDto( | ||
| standardTime = roundedBase, | ||
| realTimeCongestionLevel = placeRedisUtil.getTimeCongestion(placeId, current).toInt(), | ||
| byTimePercent = byTimePercent | ||
| ) | ||
| } | ||
|
|
||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,67 @@ | ||
| package busanVibe.busan.domain.place.service | ||
|
|
||
| import org.slf4j.LoggerFactory | ||
| import org.springframework.data.redis.core.StringRedisTemplate | ||
| import org.springframework.stereotype.Component | ||
| import java.time.Duration | ||
| import java.time.LocalDateTime | ||
|
|
||
|
|
||
| @Component | ||
| class PlaceRedisUtil( | ||
| private val redisTemplate: StringRedisTemplate | ||
| ) { | ||
|
|
||
| private val log = LoggerFactory.getLogger("busanVibe.busan.domain.place") | ||
|
|
||
| // 임의로 혼잡도 생성하여 반환. 레디스 키 값으로 저장함. | ||
| fun getRedisCongestion(placeId: Long?): Int{ | ||
|
|
||
| val key = "place:congestion:$placeId" | ||
| val randomCongestion: Int = (Math.random() * 5 + 1).toInt() | ||
| val randomCongestion = getRandomCongestion().toInt().toString() | ||
|
|
||
| redisTemplate.opsForValue() | ||
| .set(key, randomCongestion.toString()) | ||
| .set(key, randomCongestion) | ||
|
|
||
| return Integer.parseInt(randomCongestion) | ||
| } | ||
|
Comment on lines
18
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method always overwrites existing congestion values The current implementation always generates a new random value and overwrites any existing congestion data. This doesn't preserve historical data and makes the congestion values inconsistent across calls. Consider checking if a value exists before overwriting: fun getRedisCongestion(placeId: Long?): Int{
val key = "place:congestion:$placeId"
- val randomCongestion = getRandomCongestion().toInt().toString()
-
- redisTemplate.opsForValue()
- .set(key, randomCongestion)
-
- return Integer.parseInt(randomCongestion)
+
+ val existingValue = redisTemplate.opsForValue().get(key)
+ if (existingValue != null) {
+ return existingValue.toIntOrNull() ?: 0
+ }
+
+ val randomCongestion = getRandomCongestion().toInt()
+ redisTemplate.opsForValue().set(key, randomCongestion.toString(), Duration.ofHours(1))
+ return randomCongestion
}🤖 Prompt for AI Agents |
||
|
|
||
| // 시간 혼잡도 설정 | ||
| fun setPlaceTimeCongestion(placeId: Long, dateTime: LocalDateTime) { | ||
| val roundedHour = (dateTime.hour / 3) * 3 | ||
| val key = "place:congestion:$placeId-${dateTime.year}-${dateTime.monthValue}-${dateTime.dayOfMonth}-$roundedHour" | ||
| val congestion = getRandomCongestion().toString() | ||
| val success = redisTemplate.opsForValue().setIfAbsent(key, congestion, Duration.ofHours(24)) | ||
|
|
||
| if (success == true) { | ||
| log.info("혼잡도 기록 저장 완료: $key, 저장된 혼잡도: $congestion") | ||
| } else { | ||
| val existing = redisTemplate.opsForValue().get(key) | ||
| log.info("이미 존재하는 혼잡도 기록: $key, 기존 혼잡도: $existing") | ||
| } | ||
| } | ||
|
|
||
| // 지정 시간 혼잡도 조회 | ||
| fun getTimeCongestion(placeId: Long, dateTime: LocalDateTime): Float { | ||
| val roundedHour = (dateTime.hour / 3) * 3 | ||
| val key = "place:congestion:$placeId-${dateTime.year}-${dateTime.monthValue}-${dateTime.dayOfMonth}-$roundedHour" | ||
|
|
||
| val value = redisTemplate.opsForValue().get(key) | ||
|
|
||
| return randomCongestion | ||
| return if (value != null) { | ||
| log.info("이미 존재하는 혼잡도 기록: $key, 기존 혼잡도: $value") | ||
| value.toFloatOrNull() ?: 0f | ||
| } else { | ||
| setPlaceTimeCongestion(placeId, dateTime.withHour(roundedHour)) | ||
| val newValue = redisTemplate.opsForValue().get(key) | ||
| newValue?.toFloatOrNull() ?: 0f | ||
| } | ||
| } | ||
|
|
||
| // 혼잡도 생성 (1.0 ~ 5.0 사이의 Float) | ||
| private fun getRandomCongestion(): Float { | ||
| return (Math.random() * 4 + 1).toFloat() | ||
| } | ||
|
|
||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix parameter name typo
There's a typo in the parameter name:
longtitudeshould belongitude.Also update the service call:
📝 Committable suggestion
🤖 Prompt for AI Agents