-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] #630 솝탬프 박수 관련 푸시알림 구현 #631
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
Changes from all commits
cd20d22
2438386
5030c7e
e1c26ae
92e28b7
dc2580a
32e36bf
ba16ed5
9a0f091
0cd47d7
801988a
3d0ea82
60b3155
b576dfe
a02e204
0e9270b
941603e
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,20 @@ | ||
| package org.sopt.app.application.stamp; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import org.sopt.app.common.event.Event; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public class ClapEvent extends Event { | ||
|
|
||
| private final Long ownerUserId; | ||
| private final Long stampId; | ||
| private final int oldClapTotal; | ||
| private final int newClapTotal; | ||
|
|
||
| public static ClapEvent of(Long ownerUserId, Long stampId, int oldClapTotal, int newClapTotal) { | ||
| return new ClapEvent(ownerUserId, stampId, oldClapTotal, newClapTotal); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package org.sopt.app.application.stamp; | ||
|
|
||
| import org.sopt.app.interfaces.postgres.ClapMilestoneGuard; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import java.util.Optional; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.val; | ||
| import org.sopt.app.application.mission.MissionService; | ||
| import org.sopt.app.application.platform.PlatformService; | ||
| import org.sopt.app.application.platform.dto.PlatformUserInfoResponse; | ||
| import org.sopt.app.application.soptamp.SoptampUserFinder; | ||
| import org.sopt.app.common.exception.NotFoundException; | ||
| import org.sopt.app.common.response.ErrorCode; | ||
| import org.sopt.app.common.utils.HttpHeadersUtils; | ||
| import org.sopt.app.presentation.poke.PokeResponse; | ||
| import org.sopt.app.presentation.stamp.ClapRequest; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.HttpEntity; | ||
| import org.springframework.http.HttpMethod; | ||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.event.TransactionPhase; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
| import org.springframework.web.client.RestTemplate; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class ClapEventListener { | ||
|
|
||
| private final RestTemplate restTemplate = new RestTemplate(); | ||
| private final HttpHeadersUtils headersUtils; | ||
|
|
||
| private final StampService stampService; | ||
| private final MissionService missionService; | ||
| private final PlatformService platformService; | ||
| private final SoptampUserFinder soptampUserFinder; | ||
|
|
||
| private final ClapMilestoneGuard clapMilestoneGuard; | ||
|
|
||
| @Value("${makers.push.server}") | ||
| private String baseURI; | ||
|
|
||
| @Async | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| public void onClap(ClapEvent event) { | ||
| final int oldClapTotal = event.getOldClapTotal(); | ||
| final int newClapTotal = event.getNewClapTotal(); | ||
|
|
||
| Long missionId = stampService.getMissionIdByStampId(event.getStampId()); | ||
| String missionTitle = missionService.getMissionTitleById(missionId); | ||
|
|
||
| val ownerProfile = platformService.getPlatformUserInfoResponse(event.getOwnerUserId()); | ||
| String ownerName = ownerProfile.name(); | ||
| String ownerPart = Optional.ofNullable(ownerProfile.getLatestActivity()) | ||
| .map(PlatformUserInfoResponse.SoptActivities::part) | ||
| .orElseThrow(() -> new NotFoundException(ErrorCode.USER_PART_NOT_FOUND)); | ||
| String nickname = soptampUserFinder.findById(event.getOwnerUserId()).getNickname(); | ||
|
|
||
| if (crossed(oldClapTotal, newClapTotal, 1) && clapMilestoneGuard.tryMark(event.getStampId(), 1)) { | ||
| send(ClapRequest.ClapAlarmRequest.of(event.getOwnerUserId(), missionTitle, nickname)); | ||
| } | ||
|
|
||
| if (crossed(oldClapTotal, newClapTotal, 100) | ||
| && clapMilestoneGuard.tryMark(event.getStampId(), 100)) { | ||
| send(ClapRequest.ClapAlarmRequest.of(event.getOwnerUserId(), 100, missionTitle, ownerName, ownerPart, nickname)); | ||
| } else if (crossed(oldClapTotal, newClapTotal, 500) | ||
| && clapMilestoneGuard.tryMark(event.getStampId(), 500)) { | ||
| send(ClapRequest.ClapAlarmRequest.of(event.getOwnerUserId(), 500, missionTitle, ownerName, ownerPart, nickname)); | ||
| } | ||
|
|
||
| // 한 번에 여러 구간(2000, 3000)을 넘어도 낮은 것만 처리 | ||
| for (int k = 1000; k <= 10000; k += 1000) { | ||
| if (crossed(oldClapTotal, newClapTotal, k) | ||
| && clapMilestoneGuard.tryMark(event.getStampId(), k)) { | ||
| send(ClapRequest.ClapAlarmRequest.of(k, missionTitle, nickname)); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private boolean crossed(int oldTotal, int newTotal, int threshold) { | ||
| return oldTotal < threshold && newTotal >= threshold; | ||
| } | ||
|
|
||
| private void send(ClapRequest.ClapAlarmRequest body) { | ||
| val entity = new HttpEntity<>(body, headersUtils.createHeadersForSend()); | ||
| restTemplate.exchange( | ||
| baseURI, | ||
| HttpMethod.POST, | ||
| entity, | ||
| PokeResponse.PokeAlarmStatusResponse.class | ||
| ); | ||
| } | ||
|
Comment on lines
+87
to
+95
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. 알림 메세지의 응답 형식이 고정이면 공통적으로 사용되는 response를 만들어도 좋을 것 같네요 ~ |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package org.sopt.app.interfaces.postgres; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.intellij.lang.annotations.Language; | ||
| import org.springframework.jdbc.core.JdbcTemplate; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class ClapMilestoneGuard { | ||
| private final JdbcTemplate jdbcTemplate; | ||
|
|
||
| @Language("PostgreSQL") | ||
| private static final String SQL = """ | ||
|
|
||
| INSERT INTO clap_milestone_hit (stamp_id, milestone, created_at) | ||
| VALUES (?, ?, now()) | ||
| ON CONFLICT (stamp_id, milestone) DO NOTHING | ||
| """; | ||
|
|
||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
|
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. ClapEventListener.onClap가 @transactional(REQUIRES_NEW) 이고, |
||
| public boolean tryMark(long stampId, int milestone) { | ||
| return jdbcTemplate.update(SQL, stampId, milestone) == 1; | ||
| } | ||
| } | ||
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.
메세지 전송이 성공한 경우, early return 을 해줘도 좋을 것 같아요~