Skip to content

Commit eddc9f3

Browse files
authored
Merge pull request #152 from swyp-app-team-4/feat#151-point-eligibility
[Feat] 사용자 혼잡도 공유 시 포인트 획득 분기 처리 기능 구현
2 parents c57274c + d961d9c commit eddc9f3

3 files changed

Lines changed: 167 additions & 6 deletions

File tree

src/main/java/boombimapi/domain/congestion/application/MemberCongestionService.java

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
import boombimapi.domain.place.command.entity.MemberPlace;
1414
import boombimapi.domain.place.command.repository.MemberPlaceRepository;
1515
import boombimapi.domain.point.application.PointService;
16+
import boombimapi.domain.point.infrastructure.repository.MemberPointGrantRepository;
17+
import boombimapi.global.geo.GeoDistance;
1618
import boombimapi.global.infra.exception.error.BoombimException;
19+
import java.util.Set;
1720
import lombok.RequiredArgsConstructor;
1821
import org.springframework.stereotype.Service;
1922

@@ -25,6 +28,7 @@ public class MemberCongestionService {
2528
private final MemberPlaceRepository memberPlaceRepository;
2629
private final MemberCongestionRepository memberCongestionRepository;
2730
private final CongestionLevelRepository congestionLevelRepository;
31+
private final MemberPointGrantRepository memberPointGrantRepository;
2832
private final PointService pointService;
2933

3034
public CreateMemberCongestionResponse createMemberCongestion(
@@ -46,7 +50,11 @@ public CreateMemberCongestionResponse createMemberCongestion(
4650
congestionMessage = congestionLevel.getMessage();
4751
}
4852

49-
pointService.earnPointForCongestion(member, 10L);
53+
boolean pointReceivable = checkPointReceivable(memberId, request.latitude(), request.longitude());
54+
55+
if (pointReceivable) {
56+
pointService.earnPointForCongestion(member, 10L);
57+
}
5058

5159
MemberCongestion memberCongestion = MemberCongestion.of(
5260
member,
@@ -58,7 +66,55 @@ public CreateMemberCongestionResponse createMemberCongestion(
5866
);
5967

6068
return CreateMemberCongestionResponse
61-
.from(memberCongestionRepository.save(memberCongestion));
69+
.of(memberCongestionRepository.save(memberCongestion), pointReceivable);
6270
}
6371

72+
private boolean checkPointReceivable(
73+
String memberId,
74+
double latitude,
75+
double longitude
76+
) {
77+
final int DAILY_LIMIT = 5;
78+
final long WINDOW_MS = 3_600_000L;
79+
80+
int todayCount = memberPointGrantRepository.getTodayCount(memberId);
81+
if (todayCount >= DAILY_LIMIT) {
82+
return false;
83+
}
84+
85+
long currentTimeMillis = System.currentTimeMillis();
86+
87+
memberPointGrantRepository.removeStaledKeys(
88+
memberId,
89+
currentTimeMillis,
90+
WINDOW_MS
91+
);
92+
93+
Set<String> recentCoordinates = memberPointGrantRepository.rangeByScore(
94+
memberId,
95+
currentTimeMillis - WINDOW_MS,
96+
currentTimeMillis
97+
);
98+
99+
for (String coordinate : recentCoordinates) {
100+
int commaIndex = coordinate.indexOf(',');
101+
double prevLat = Double.parseDouble(coordinate.substring(0, commaIndex));
102+
double prevLon = Double.parseDouble(coordinate.substring(commaIndex + 1));
103+
double distance = GeoDistance.haversineMeters(latitude, longitude, prevLat, prevLon);
104+
if (distance <= 300.0) {
105+
return false;
106+
}
107+
}
108+
109+
memberPointGrantRepository.addRecentCoordinate(
110+
memberId,
111+
latitude,
112+
longitude,
113+
currentTimeMillis
114+
);
115+
116+
memberPointGrantRepository.incrementToday(memberId);
117+
118+
return true;
119+
}
64120
}

src/main/java/boombimapi/domain/congestion/dto/response/CreateMemberCongestionResponse.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
public record CreateMemberCongestionResponse(
66
Long memberCongestionId,
7-
String memberPlaceName
7+
String memberPlaceName,
8+
boolean pointReceived
89
) {
910

10-
public static CreateMemberCongestionResponse from(
11-
MemberCongestion memberCongestion
11+
public static CreateMemberCongestionResponse of(
12+
MemberCongestion memberCongestion,
13+
boolean pointReceived
1214
) {
1315
return new CreateMemberCongestionResponse(
1416
memberCongestion.getId(),
15-
memberCongestion.getMemberPlace().getName()
17+
memberCongestion.getMemberPlace().getName(),
18+
pointReceived
1619
);
1720
}
1821

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package boombimapi.domain.point.infrastructure.repository;
2+
3+
import java.time.LocalDate;
4+
import java.time.ZoneId;
5+
import java.time.format.DateTimeFormatter;
6+
import java.util.Locale;
7+
import java.util.Set;
8+
import java.util.concurrent.TimeUnit;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.data.redis.core.StringRedisTemplate;
11+
import org.springframework.stereotype.Repository;
12+
13+
@Repository
14+
@RequiredArgsConstructor
15+
public class MemberPointGrantRepository {
16+
17+
private final StringRedisTemplate redisTemplate;
18+
19+
private static final String RECENT_KEY_PREFIX = "point:recent:";
20+
private static final String DAILY_KEY_PREFIX = "point:daily:";
21+
22+
private static final ZoneId KST = ZoneId.of("Asia/Seoul");
23+
private static final DateTimeFormatter DAY_FORMAT = DateTimeFormatter.BASIC_ISO_DATE;
24+
25+
public void removeStaledKeys(
26+
String memberId,
27+
long currentMs,
28+
long windowMs
29+
) {
30+
redisTemplate.opsForZSet().removeRangeByScore(
31+
RECENT_KEY_PREFIX + memberId,
32+
0,
33+
currentMs - windowMs
34+
);
35+
}
36+
37+
public Set<String> rangeByScore(
38+
String memberId,
39+
long fromMs,
40+
long toMs
41+
) {
42+
return redisTemplate.opsForZSet().rangeByScore(
43+
RECENT_KEY_PREFIX + memberId,
44+
fromMs,
45+
toMs
46+
);
47+
}
48+
49+
public void addRecentCoordinate(
50+
String memberId,
51+
double latitude,
52+
double longitude,
53+
long currentMs
54+
) {
55+
String key = RECENT_KEY_PREFIX + memberId;
56+
String memberCoordinate = String.format(
57+
Locale.ROOT,
58+
"%.15f,%.15f",
59+
latitude,
60+
longitude
61+
);
62+
63+
redisTemplate.opsForZSet().add(key, memberCoordinate, currentMs);
64+
redisTemplate.expire(key, 2, TimeUnit.HOURS);
65+
}
66+
67+
public int getTodayCount(
68+
String memberId
69+
) {
70+
String key = todayKey(memberId);
71+
String value = redisTemplate.opsForValue().get(key);
72+
73+
if (value == null) {
74+
return 0;
75+
}
76+
return Integer.parseInt(value);
77+
}
78+
79+
public void incrementToday(
80+
String memberId
81+
) {
82+
String key = todayKey(memberId);
83+
Long after = redisTemplate.opsForValue().increment(key);
84+
85+
if (after == null) {
86+
return;
87+
}
88+
89+
if (after == 1L) {
90+
redisTemplate.expire(key, 3, TimeUnit.DAYS);
91+
}
92+
93+
}
94+
95+
private String todayKey(
96+
String memberId
97+
) {
98+
String date = LocalDate.now(KST).format(DAY_FORMAT);
99+
return DAILY_KEY_PREFIX + memberId + ":" + date;
100+
}
101+
102+
}

0 commit comments

Comments
 (0)