Skip to content

Commit 167871d

Browse files
Merge pull request #268 from DevKor-github/develop
#264 ~ #267 반영해 운영 배포
2 parents 90e400c + 27548ef commit 167871d

12 files changed

Lines changed: 398 additions & 23 deletions

File tree

gradlew

100644100755
File mode changed.

src/main/java/org/devkor/apu/saerok_server/domain/admin/stat/application/StatAggregationService.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,11 @@ private void aggregateUserCompletedTotal(LocalDate date) {
137137
var end = endExclusive(date);
138138
long total = em.createQuery("""
139139
SELECT COUNT(u) FROM User u
140-
WHERE u.signupStatus = :completed
141-
AND (u.deletedAt IS NULL OR u.deletedAt >= :end)
140+
WHERE (u.signupStatus = :completed AND u.deletedAt IS NULL)
141+
OR (u.signupStatus = :withdrawn AND u.deletedAt IS NOT NULL AND u.deletedAt >= :end)
142142
""", Long.class)
143143
.setParameter("completed", SignupStatusType.COMPLETED)
144+
.setParameter("withdrawn", SignupStatusType.WITHDRAWN)
144145
.setParameter("end", end)
145146
.getSingleResult();
146147

src/main/java/org/devkor/apu/saerok_server/domain/collection/api/CollectionController.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.CreateCollectionImageResponse;
1414
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.CreateCollectionResponse;
1515
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetCollectionDetailResponse;
16+
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetCollectionEditDataResponse;
17+
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetNearbyCollectionsResponse;
1618
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.MyCollectionsResponse;
1719
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.PresignResponse;
18-
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.*;
20+
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.UpdateCollectionResponse;
1921
import org.devkor.apu.saerok_server.domain.collection.application.CollectionCommandService;
2022
import org.devkor.apu.saerok_server.domain.collection.application.CollectionImageCommandService;
2123
import org.devkor.apu.saerok_server.domain.collection.application.CollectionQueryService;
2224
import org.devkor.apu.saerok_server.domain.collection.application.dto.GetNearbyCollectionsCommand;
25+
import org.devkor.apu.saerok_server.domain.collection.application.NearbyCollectionsMode;
2326
import org.devkor.apu.saerok_server.domain.collection.mapper.CollectionWebMapper;
2427
import org.devkor.apu.saerok_server.global.security.principal.UserPrincipal;
2528
import org.springframework.http.HttpStatus;
@@ -372,10 +375,14 @@ public void deleteCollectionImage(
372375
summary = "주위의 컬렉션 조회 (인증: optional)",
373376
security = @SecurityRequirement(name = "bearerAuth"),
374377
description = """
375-
주위의 컬렉션을 조회합니다.
378+
중심점 주위의 컬렉션을 조회합니다.
376379
377-
내 지도 기능은 isMineOnly = true, 우리 지도 기능은 false를 사용하면 됩니다.
378-
- 비회원인데 isMineOnly = true이면 400 Bad Request
380+
2가지 조회 모드 중 상황에 알맞은 것을 선택하세요.
381+
- DIST : 기존의 방식으로서, 중심점에서 가까운 순으로 limit 개수만큼 가져옵니다.
382+
limit를 지정하지 않으면 무제한으로 설정됩니다.
383+
384+
- EVEN : 새로 도입된 방식으로서, 요청한 영역 안에서 지리적으로 고르게 limit 개수만큼 가져옵니다.
385+
limit를 지정하지 않으면 무제한으로 설정됩니다.
379386
""",
380387
responses = {
381388
@ApiResponse(
@@ -396,11 +403,15 @@ public GetNearbyCollectionsResponse getNearbyCollections(
396403
@Parameter(description = "검색 반경 (m)", example = "500", required = true)
397404
@RequestParam Double radiusMeters,
398405
@Parameter(description = "주위의 내 컬렉션만 조회 여부. 비회원은 false만 허용", example = "false")
399-
@RequestParam(required = false, defaultValue = "false") Boolean isMineOnly
406+
@RequestParam(required = false, defaultValue = "false") Boolean isMineOnly,
407+
@Parameter(description = "조회할 최대 개수 (미지정 시 전체)", example = "50")
408+
@RequestParam(required = false) Integer limit,
409+
@Parameter(description = "조회 모드 (DIST: 거리순, EVEN: 균등 분포)", example = "DIST")
410+
@RequestParam(required = false, defaultValue = "DIST") NearbyCollectionsMode mode
400411
) {
401412
Long userId = userPrincipal == null ? null : userPrincipal.getId();
402413
return collectionQueryService.getNearbyCollections(
403-
new GetNearbyCollectionsCommand(userId, latitude, longitude, radiusMeters, isMineOnly)
414+
new GetNearbyCollectionsCommand(userId, latitude, longitude, radiusMeters, isMineOnly, limit, mode)
404415
);
405416
}
406417
}

src/main/java/org/devkor/apu/saerok_server/domain/collection/application/CollectionQueryService.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package org.devkor.apu.saerok_server.domain.collection.application;
22

3-
import lombok.RequiredArgsConstructor;
43
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetCollectionDetailResponse;
54
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetCollectionEditDataResponse;
65
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.GetNearbyCollectionsResponse;
76
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.MyCollectionsResponse;
87
import org.devkor.apu.saerok_server.domain.collection.application.dto.GetCollectionEditDataCommand;
98
import org.devkor.apu.saerok_server.domain.collection.application.dto.GetNearbyCollectionsCommand;
109
import org.devkor.apu.saerok_server.domain.collection.application.helper.CollectionImageUrlService;
10+
import org.devkor.apu.saerok_server.domain.collection.application.strategy.NearbyCollectionsStrategy;
1111
import org.devkor.apu.saerok_server.domain.collection.core.entity.AccessLevelType;
1212
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
1313
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollectionImage;
@@ -32,10 +32,11 @@
3232
import java.util.List;
3333
import java.util.Map;
3434
import java.util.Optional;
35+
import java.util.function.Function;
36+
import java.util.stream.Collectors;
3537

3638
@Service
3739
@Transactional(readOnly = true)
38-
@RequiredArgsConstructor
3940
public class CollectionQueryService {
4041

4142
private final CollectionRepository collectionRepository;
@@ -46,6 +47,30 @@ public class CollectionQueryService {
4647
private final UserRepository userRepository;
4748
private final UserProfileImageUrlService userProfileImageUrlService;
4849
private final CollectionImageUrlService collectionImageUrlService;
50+
private final Map<NearbyCollectionsMode, NearbyCollectionsStrategy> nearbyCollectionsStrategyMap;
51+
52+
public CollectionQueryService(
53+
CollectionRepository collectionRepository,
54+
CollectionImageRepository collectionImageRepository,
55+
CollectionLikeRepository collectionLikeRepository,
56+
CollectionCommentRepository collectionCommentRepository,
57+
CollectionWebMapper collectionWebMapper,
58+
UserRepository userRepository,
59+
UserProfileImageUrlService userProfileImageUrlService,
60+
CollectionImageUrlService collectionImageUrlService,
61+
List<NearbyCollectionsStrategy> nearbyCollectionsStrategies
62+
) {
63+
this.collectionRepository = collectionRepository;
64+
this.collectionImageRepository = collectionImageRepository;
65+
this.collectionLikeRepository = collectionLikeRepository;
66+
this.collectionCommentRepository = collectionCommentRepository;
67+
this.collectionWebMapper = collectionWebMapper;
68+
this.userRepository = userRepository;
69+
this.userProfileImageUrlService = userProfileImageUrlService;
70+
this.collectionImageUrlService = collectionImageUrlService;
71+
this.nearbyCollectionsStrategyMap = nearbyCollectionsStrategies.stream()
72+
.collect(Collectors.toUnmodifiableMap(NearbyCollectionsStrategy::getMode, Function.identity()));
73+
}
4974

5075
public GetCollectionEditDataResponse getCollectionEditDataResponse(GetCollectionEditDataCommand command) {
5176
userRepository.findById(command.userId()).orElseThrow(() -> new BadRequestException("유효하지 않은 사용자 id예요"));
@@ -139,9 +164,19 @@ public GetNearbyCollectionsResponse getNearbyCollections(GetNearbyCollectionsCom
139164
throw new BadRequestException("비회원 사용자는 isMineOnly = false만 사용 가능해요.");
140165
}
141166

142-
// 1) 근거리 컬렉션 조회 (PostGIS native)
167+
if (command.limit() != null && command.limit() < 1) {
168+
throw new BadRequestException("limit은 1 이상의 양수여야 해요.");
169+
}
170+
171+
if (command.radiusMeters() == null || command.radiusMeters() <= 0) {
172+
throw new BadRequestException("radiusMeters는 0보다 커야 해요.");
173+
}
174+
175+
// 1) 근거리 컬렉션 조회 (전략 기반)
143176
Point refPoint = PointFactory.create(command.latitude(), command.longitude());
144-
List<UserBirdCollection> collections = collectionRepository.findNearby(refPoint, command.radiusMeters(), command.userId(), command.isMineOnly());
177+
NearbyCollectionsStrategy strategy = Optional.ofNullable(nearbyCollectionsStrategyMap.get(command.mode()))
178+
.orElseThrow(() -> new BadRequestException("지원하지 않는 조회 모드예요."));
179+
List<UserBirdCollection> collections = strategy.findCollections(command, refPoint);
145180

146181
GetNearbyCollectionsResponse response = new GetNearbyCollectionsResponse();
147182
if (collections.isEmpty()) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.devkor.apu.saerok_server.domain.collection.application;
2+
3+
public enum NearbyCollectionsMode {
4+
DIST,
5+
EVEN
6+
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.devkor.apu.saerok_server.domain.collection.application.dto;
22

3+
import org.devkor.apu.saerok_server.domain.collection.application.NearbyCollectionsMode;
4+
35
public record GetNearbyCollectionsCommand(
46
Long userId,
57
Double latitude,
68
Double longitude,
79
Double radiusMeters,
8-
boolean isMineOnly
10+
boolean isMineOnly,
11+
Integer limit,
12+
NearbyCollectionsMode mode
913
) {
1014
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.devkor.apu.saerok_server.domain.collection.application.strategy;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.devkor.apu.saerok_server.domain.collection.application.dto.GetNearbyCollectionsCommand;
5+
import org.devkor.apu.saerok_server.domain.collection.application.NearbyCollectionsMode;
6+
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
7+
import org.devkor.apu.saerok_server.domain.collection.core.repository.CollectionRepository;
8+
import org.locationtech.jts.geom.Point;
9+
import org.springframework.stereotype.Component;
10+
11+
import java.util.List;
12+
13+
@Component
14+
@RequiredArgsConstructor
15+
public class DistancePriorityNearbyCollectionsStrategy implements NearbyCollectionsStrategy {
16+
17+
private final CollectionRepository collectionRepository;
18+
19+
@Override
20+
public NearbyCollectionsMode getMode() {
21+
return NearbyCollectionsMode.DIST;
22+
}
23+
24+
@Override
25+
public List<UserBirdCollection> findCollections(GetNearbyCollectionsCommand command, Point refPoint) {
26+
return collectionRepository.findNearby(
27+
refPoint,
28+
command.radiusMeters(),
29+
command.userId(),
30+
command.isMineOnly(),
31+
command.limit()
32+
);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.devkor.apu.saerok_server.domain.collection.application.strategy;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.devkor.apu.saerok_server.domain.collection.application.dto.GetNearbyCollectionsCommand;
5+
import org.devkor.apu.saerok_server.domain.collection.application.NearbyCollectionsMode;
6+
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
7+
import org.devkor.apu.saerok_server.domain.collection.core.repository.CollectionRepository;
8+
import org.locationtech.jts.geom.Point;
9+
import org.springframework.stereotype.Component;
10+
11+
import java.util.List;
12+
13+
@Component
14+
@RequiredArgsConstructor
15+
public class EvenDistributionNearbyCollectionsStrategy implements NearbyCollectionsStrategy {
16+
17+
private static final double CELL_STABILITY_COEFFICIENT = 1.5;
18+
private static final double CELL_SIZE_MIN = 80.0;
19+
20+
private final CollectionRepository collectionRepository;
21+
22+
@Override
23+
public NearbyCollectionsMode getMode() {
24+
return NearbyCollectionsMode.EVEN;
25+
}
26+
27+
@Override
28+
public List<UserBirdCollection> findCollections(GetNearbyCollectionsCommand command, Point refPoint) {
29+
long candidateCount = collectionRepository.countNearbyCandidates(
30+
refPoint,
31+
command.radiusMeters(),
32+
command.userId(),
33+
command.isMineOnly()
34+
);
35+
36+
if (candidateCount == 0) {
37+
return List.of();
38+
}
39+
40+
41+
long finalCount = command.limit() != null ? Math.min(command.limit(), candidateCount) : candidateCount;
42+
if (finalCount <= 0) {
43+
return List.of();
44+
}
45+
46+
double cellSize = calculateCellSize(command.radiusMeters(), finalCount);
47+
return collectionRepository.findNearbyEven(
48+
refPoint,
49+
command.radiusMeters(),
50+
command.userId(),
51+
command.isMineOnly(),
52+
finalCount,
53+
cellSize
54+
);
55+
}
56+
57+
private double calculateCellSize(double radiusMeters, long count) {
58+
double cellCount = Math.max(count * CELL_STABILITY_COEFFICIENT, 1.0);
59+
double cellArea = (Math.PI * Math.pow(radiusMeters, 2)) / cellCount;
60+
double rawCellSize = Math.sqrt(cellArea);
61+
double cellSizeMax = radiusMeters / 2.0;
62+
return clamp(rawCellSize, CELL_SIZE_MIN, cellSizeMax);
63+
}
64+
65+
private double clamp(double value, double min, double max) {
66+
return Math.min(Math.max(value, min), max);
67+
}
68+
69+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.devkor.apu.saerok_server.domain.collection.application.strategy;
2+
3+
import org.devkor.apu.saerok_server.domain.collection.application.dto.GetNearbyCollectionsCommand;
4+
import org.devkor.apu.saerok_server.domain.collection.application.NearbyCollectionsMode;
5+
import org.devkor.apu.saerok_server.domain.collection.core.entity.UserBirdCollection;
6+
import org.locationtech.jts.geom.Point;
7+
8+
import java.util.List;
9+
10+
public interface NearbyCollectionsStrategy {
11+
12+
NearbyCollectionsMode getMode();
13+
14+
List<UserBirdCollection> findCollections(GetNearbyCollectionsCommand command, Point refPoint);
15+
}

0 commit comments

Comments
 (0)