Skip to content
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

feat: 미션 상태 업데이트 배치 스케줄러 구현 #81

Merged
merged 2 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.3'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation("org.springframework.boot:spring-boot-starter-quartz")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

testImplementation 'org.springframework.boot:spring-boot-starter-test'

// swagger
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.nexters.goalpanzi.application.mission;

import com.nexters.goalpanzi.application.mission.dto.request.MissionFilter;
import com.nexters.goalpanzi.application.mission.dto.response.MemberRankResponse;
import com.nexters.goalpanzi.application.mission.dto.response.MissionDetailResponse;
import com.nexters.goalpanzi.application.mission.dto.response.MissionsResponse;
Expand All @@ -22,7 +21,6 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

@Transactional(readOnly = true)
@RequiredArgsConstructor
Expand Down Expand Up @@ -60,23 +58,22 @@ private void validateAlreadyJoin(final Member member, final Mission mission) {
});
}

public MissionsResponse findAllByMemberId(final Long memberId, final List<MissionFilter> filter) {
public MissionsResponse findAllByMemberId(final Long memberId, final List<MissionStatus> filter) {
Member member = memberRepository.getMember(memberId);
List<MissionMember> missionMembers = missionMemberRepository.findAllWithMissionByMemberId(memberId)
List<MissionMember> missionMembers = missionMemberRepository.findAllWithMissionByMemberId(memberId);
if (filter == null || filter.isEmpty()) {
return MissionsResponse.of(member, missionMembers);
}

List<MissionMember> filteredMissionMembers = missionMembers
.stream()
.filter(it -> isMissionStatusMatching(filter, it.getMission()))
.filter(it -> isMissionStatusMatching(filter, it))
.toList();
return MissionsResponse.of(member, missionMembers);
return MissionsResponse.of(member, filteredMissionMembers);
}

private boolean isMissionStatusMatching(final List<MissionFilter> filters, final Mission mission) {
for (MissionFilter filter : filters) {
MissionStatus missionStatus = filter.toMissionStatus();
if (missionStatus != null && Objects.equals(missionStatus, MissionStatus.fromMission(mission))) {
return true;
}
}
return filters.isEmpty();
private boolean isMissionStatusMatching(final List<MissionStatus> filters, final MissionMember missionMember) {
return filters.contains(missionMember.getMissionStatus());
}

@Transactional
Expand All @@ -99,4 +96,17 @@ public MemberRankResponse getMissionRank(final Long missionId, final Long member

return MemberRankResponse.from(memberRanks.getRankByMember(member));
}

@Transactional
public void batchUpdateStatus() {
List<Mission> missions = missionRepository.findAll();
missions.forEach(mission -> {
List<MissionMember> missionMembers = missionMemberRepository.findAllByMissionId(mission.getId());
int memberCount = missionMembers.size();
missionMembers
.forEach(missionMember -> {
missionMember.updateMissionStatus(mission, memberCount);
});
});
}
Comment on lines +101 to +111
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,9 @@ private void validateAuthority(final Long memberId, final Mission mission) {
throw new ForbiddenException(ErrorCode.CANNOT_DELETE_MISSION);
}
}

@Transactional
public void batchUpdate(final Long missionId) {

}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드는 아직 구현 전인가용??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 이거 지워놓을게 !

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.nexters.goalpanzi.application.mission.dto.response;

import com.nexters.goalpanzi.domain.mission.MissionStatus;
import io.swagger.v3.oas.annotations.media.Schema;

public record MissionResponse(
@Schema(description = "미션 ID", requiredMode = Schema.RequiredMode.REQUIRED)
Long missionId,
@Schema(description = "목표 행동", requiredMode = Schema.RequiredMode.REQUIRED)
String description
String description,
@Schema(description = "미션 상태", requiredMode = Schema.RequiredMode.REQUIRED)
MissionStatus missionStatus
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ public record MissionsResponse(
List<MissionResponse> missions
) {

public static MissionsResponse of(Member member, List<MissionMember> missionVerifications) {
public static MissionsResponse of(Member member, List<MissionMember> missionMembers) {
return new MissionsResponse(
new ProfileResponse(member.getNickname(), member.getCharacterType()),
missionVerifications.stream()
missionMembers.stream()
.map(missionVerification -> new MissionResponse(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 iterator 부분 missionMember로 해도 좋을 것 같아요
(이전에 missionVerifications여서 missionVerification으로 남아 있는 것 같아요 :)

missionVerification.getMission().getId(),
missionVerification.getMission().getDescription())
missionVerification.getMission().getDescription(),
missionVerification.getMissionStatus())
)
.toList()
);
Expand Down
20 changes: 18 additions & 2 deletions src/main/java/com/nexters/goalpanzi/domain/mission/Mission.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.nexters.goalpanzi.domain.mission;

import com.nexters.goalpanzi.common.time.TimeUtil;
import com.nexters.goalpanzi.domain.common.BaseEntity;
import com.nexters.goalpanzi.infrastructure.jpa.DaysOfWeekConverter;
import jakarta.persistence.*;
Expand Down Expand Up @@ -113,8 +114,11 @@ private void validateMission() {
}

public boolean isMissionPeriod() {
LocalDate today = LocalDate.now();
return !today.isBefore(this.missionStartDate.toLocalDate()) && !today.isAfter(missionEndDate.toLocalDate());
LocalDateTime missionStart = getMissionUploadStartDateTime();
LocalDateTime missionEnd = getMissionUploadEndDateTime();

LocalDateTime today = LocalDateTime.now();
return !today.isBefore(missionStart) && !today.isAfter(missionEnd);
}

public boolean isMissionDay() {
Expand All @@ -131,6 +135,18 @@ public boolean isExpired() {
return today.isAfter(missionEndDate.toLocalDate());
}

public LocalDateTime getMissionUploadStartDateTime() {
return TimeUtil.combineDateAndTime(
missionStartDate, TimeUtil.of(uploadStartTime)
);
}

public LocalDateTime getMissionUploadEndDateTime() {
return TimeUtil.combineDateAndTime(
missionEndDate, TimeUtil.of(uploadEndTime)
);
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.nexters.goalpanzi.domain.member.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
Expand All @@ -19,7 +21,6 @@
import java.util.Objects;

import static com.nexters.goalpanzi.exception.ErrorCode.CAN_NOT_JOIN_MISSION;
import static com.nexters.goalpanzi.exception.ErrorCode.EXCEED_MAX_PERSONNEL;

@Entity
@SQLRestriction("deleted_at is NULL")
Expand All @@ -44,6 +45,13 @@ public class MissionMember extends BaseEntity {
@Column(name = "verification_count")
private Integer verificationCount;

@Column(name = "check_completed")
private Boolean checkCompleted;

@Enumerated(EnumType.STRING)
@Column(name = "mission_status")
private MissionStatus missionStatus;

public MissionMember(final Member member, final Mission mission, final Integer verificationCount) {
this.member = member;
this.mission = mission;
Expand All @@ -61,6 +69,13 @@ public void verify() {
this.verificationCount++;
}

public void updateMissionStatus(
final Mission mission,
final Integer currentMemberCount
) {
missionStatus = MissionStatus.fromMission(mission, currentMemberCount, this);
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,23 @@
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;

import static com.nexters.goalpanzi.exception.ErrorCode.UNKNOWN_MISSION;

@Getter
public enum MissionStatus {

PENDING("대기"),
ONGOING("진행중"),
// 미션 시작 전
CREATED("생성"),

// 미션 진행 기간,
CANCELED("취소"),
IN_PROGRESS("진행중"),
DELETED("삭제"),

// 미션 종료일 이후
PENDING_COMPLETION("종료 대기"),
COMPLETED("완료");

private final String description;
Expand All @@ -18,21 +29,35 @@ public enum MissionStatus {
this.description = description;
}

public static MissionStatus fromMission(final Mission mission) {
public static MissionStatus fromMission(
final Mission mission,
final Integer currentMemberCount,
final MissionMember missionMember
) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime missionStart = TimeUtil.combineDateAndTime(
mission.getMissionStartDate(), TimeUtil.of(mission.getUploadStartTime())
);
LocalDateTime missionEnd = TimeUtil.combineDateAndTime(
mission.getMissionEndDate(), TimeUtil.of(mission.getUploadEndTime())
);
LocalDateTime missionStart = mission.getMissionUploadStartDateTime();
LocalDateTime missionEnd = mission.getMissionUploadEndDateTime();

if (now.isBefore(missionStart)) {
return PENDING;
} else if (now.isAfter(missionEnd)) {
return CREATED;
}

if (mission.isMissionPeriod() && currentMemberCount <= 1) {
return CANCELED;
}

if (mission.isMissionPeriod() && currentMemberCount > 1) {
return IN_PROGRESS;
}

if (now.isAfter(missionEnd) && !missionMember.getCheckCompleted()) {
return PENDING_COMPLETION;
}

if (now.isAfter(missionEnd) && missionMember.getCheckCompleted()) {
return COMPLETED;
} else {
return ONGOING;
}
Comment on lines 41 to 59
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍


throw new IllegalArgumentException(UNKNOWN_MISSION.getMessage(mission, missionMember));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum ErrorCode {
INVALID_INVITATION_CODE("초대코드가 올바르지 않습니다."),
INVALID_UPLOAD_TIME_OF_DAY("올바르지 않은 미션 인증 업로드 시간대입니다."),
CANNOT_DELETE_MISSION("미션 삭제 권한이 없습니다."),
UNKNOWN_MISSION("정의되지 않은 미션상태입니다."),

// MISSION MEMBER
ALREADY_EXISTS_MISSION_MEMBER("이미 참여한 미션입니다. [%s]"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.nexters.goalpanzi.application.mission.dto.response.MissionsResponse;
import com.nexters.goalpanzi.common.argumentresolver.LoginMemberId;
import com.nexters.goalpanzi.domain.mission.InvitationCode;
import com.nexters.goalpanzi.domain.mission.MissionStatus;
import com.nexters.goalpanzi.presentation.mission.dto.JoinMissionRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -30,7 +31,7 @@ public class MissionMemberController implements MissionMemberControllerDocs {
@GetMapping("/mission-members/me")
public ResponseEntity<MissionsResponse> getMissions(
@LoginMemberId final Long memberId,
@RequestParam(required = false, defaultValue = "PENDING,ONGOING") List<MissionFilter> filter
@RequestParam(required = false) List<MissionStatus> filter
) {
return ResponseEntity.ok(missionMemberService.findAllByMemberId(memberId, filter));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.nexters.goalpanzi.presentation.mission;

import com.nexters.goalpanzi.application.mission.dto.request.MissionFilter;
import com.nexters.goalpanzi.application.mission.dto.response.MemberRankResponse;
import com.nexters.goalpanzi.application.mission.dto.response.MissionDetailResponse;
import com.nexters.goalpanzi.application.mission.dto.response.MissionsResponse;
import com.nexters.goalpanzi.common.argumentresolver.LoginMemberId;
import com.nexters.goalpanzi.domain.mission.MissionStatus;
import com.nexters.goalpanzi.presentation.mission.dto.JoinMissionRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -21,7 +21,7 @@ public interface MissionMemberControllerDocs {
@Operation(summary = "내가 참여한 미션 조회")
ResponseEntity<MissionsResponse> getMissions(
@Parameter(hidden = true) @LoginMemberId final Long memberId,
@RequestParam(required = false) List<MissionFilter> filter
@RequestParam(required = false) List<MissionStatus> filter
);

@Operation(summary = "미션 참여", description = "초대코드로 미션에 참여합니다.")
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/com/nexters/goalpanzi/schedule/AbstractJob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.nexters.goalpanzi.schedule;

import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.ScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.scheduling.quartz.QuartzJobBean;

public abstract class AbstractJob<T extends Trigger> extends QuartzJobBean implements Job {

private static final String JOB_PREFIX = "MissionMateService-Job-";
private static final String TRIGGER_PREFIX = "MissionMate-Trigger-";

public JobDetail getJobDetail() {
return JobBuilder.newJob(this.getClass())
.withIdentity(JOB_PREFIX + this.getClass().getSimpleName())
.build();
}

public Trigger getTrigger() {
return TriggerBuilder.newTrigger()
.withSchedule(getScheduleBuilder())
.withIdentity(TRIGGER_PREFIX + this.getClass().getSimpleName())
.build();
}

protected abstract ScheduleBuilder<T> getScheduleBuilder();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.nexters.goalpanzi.schedule;

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.Trigger;

public interface CustomAutomationJob extends Job {
Trigger getTrigger();
JobDetail getJobDetail();
}
Loading
Loading