Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jobdri.jobdri_api.domain.analysis.controller;

import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncCancelResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncStatusResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncSubmitResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisResponse;
Expand Down Expand Up @@ -57,6 +58,19 @@ public ApiResponse<AnalysisAsyncStatusResponse> getAnalysisTask(
);
}

@Operation(summary = "자소서 분석 비동기 작업 취소", description = "taskId로 접수된 자소서 분석 비동기 작업을 취소합니다.")
@PostMapping("/async/{taskId}/cancel")
public ApiResponse<AnalysisAsyncCancelResponse> cancelAnalysisTask(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable Long mockApplyId,
@PathVariable String taskId
) {
return ApiResponse.onSuccess(
"자소서 분석 비동기 작업 취소에 성공했습니다.",
analysisAsyncFacadeService.cancel(getAuthenticatedUser(userDetails), mockApplyId, taskId)
);
}

@Operation(summary = "자소서 분석 비동기 작업 상태 SSE 구독", description = "taskId로 자소서 분석 비동기 작업 상태를 SSE 스트림으로 구독합니다.")
@GetMapping(value = "/async/{taskId}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamAnalysisTask(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.jobdri.jobdri_api.domain.analysis.dto.response;

public record AnalysisAsyncCancelResponse(
String taskId,
String status,
String message
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.Builder;

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

@Builder
public record AnalysisAsyncStatusResponse(
Expand All @@ -21,6 +22,12 @@ public record AnalysisAsyncStatusResponse(
LocalDateTime lastAttemptAt,
LocalDateTime startedAt,
LocalDateTime completedAt,
Boolean cancelRequested,
LocalDateTime cancelledAt,
String currentStep,
Integer progressPercent,
Integer estimatedRemainingSeconds,
List<AnalysisProgressStepResponse> steps,
AnalysisResponse result
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.jobdri.jobdri_api.domain.analysis.dto.response;

public record AnalysisProgressStepResponse(
String code,
String label,
String status
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ public class AnalysisAsyncTask extends CreatedAtEntity {
@Column(name = "completed_at")
private LocalDateTime completedAt;

@Column(name = "cancel_requested", nullable = false)
private boolean cancelRequested;

@Column(name = "cancelled_at")
private LocalDateTime cancelledAt;

@Column(name = "current_step", length = 60)
private String currentStep;

@Column(name = "progress_percent")
private Integer progressPercent;

@Column(name = "estimated_remaining_seconds")
private Integer estimatedRemainingSeconds;

public static AnalysisAsyncTask pending(Long userId, Long mockApplyId, int maxRetryCount) {
AnalysisAsyncTask task = new AnalysisAsyncTask();
task.taskId = UUID.randomUUID().toString();
Expand All @@ -88,6 +103,9 @@ public static AnalysisAsyncTask pending(Long userId, Long mockApplyId, int maxRe
task.retryCount = 0;
task.maxRetryCount = Math.max(0, maxRetryCount);
task.submittedAt = LocalDateTime.now();
task.cancelRequested = false;
task.currentStep = "VALIDATING_INPUT";
task.progressPercent = 0;
return task;
}

Expand All @@ -110,6 +128,8 @@ public void markRunning(String workerId, int retryCount, Instant messageSubmitte
}
this.status = TaskStatus.RUNNING;
this.message = "자소서 분석을 진행 중입니다.";
this.currentStep = "PREPARING_CONTEXT";
this.progressPercent = Math.max(resolveProgressPercent(), 10);
this.failureReason = null;
this.error = null;
this.workerId = workerId;
Expand All @@ -122,11 +142,17 @@ public void markRunning(String workerId, int retryCount, Instant messageSubmitte
}

public void markSuccess() {
if (isTerminal()) {
return;
}
this.status = TaskStatus.SUCCEEDED;
this.message = "자소서 분석이 완료되었습니다.";
this.error = null;
this.failureReason = null;
this.completedAt = LocalDateTime.now();
this.currentStep = "COMPLETED";
this.progressPercent = 100;
this.estimatedRemainingSeconds = 0;
}

public void markRetryScheduled(FailureReason failureReason, String errorMessage, int retryCount) {
Expand All @@ -139,14 +165,16 @@ public void markRetryScheduled(FailureReason failureReason, String errorMessage,
}
this.status = TaskStatus.PENDING;
this.message = "자소서 분석 재시도를 대기 중입니다.";
this.currentStep = "VALIDATING_INPUT";
this.progressPercent = 0;
this.failureReason = failureReason;
this.error = errorMessage;
this.retryCount = Math.max(0, retryCount);
this.completedAt = null;
}

public void markFailed(FailureReason failureReason, String errorMessage, int retryCount) {
if (status == TaskStatus.SUCCEEDED) {
if (isTerminal()) {
return;
}
this.status = TaskStatus.FAILED;
Expand All @@ -155,6 +183,29 @@ public void markFailed(FailureReason failureReason, String errorMessage, int ret
this.error = errorMessage;
this.retryCount = Math.max(0, retryCount);
this.completedAt = LocalDateTime.now();
this.progressPercent = 0;
this.estimatedRemainingSeconds = 0;
}

public void requestCancel() {
if (status == TaskStatus.SUCCEEDED || status == TaskStatus.FAILED) {
return;
}
this.cancelRequested = true;
if (status == TaskStatus.CANCELLED) {
if (cancelledAt == null) {
this.cancelledAt = LocalDateTime.now();
}
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
this.status = TaskStatus.CANCELLED;
this.message = "자소서 분석 작업이 취소되었습니다.";
this.error = null;
this.failureReason = null;
this.completedAt = LocalDateTime.now();
this.cancelledAt = this.completedAt;
this.progressPercent = 0;
this.estimatedRemainingSeconds = 0;
}

public void updateWorkerMetadata(String workerId, Long queueLatencyMillis) {
Expand All @@ -167,14 +218,19 @@ public void updateWorkerMetadata(String workerId, Long queueLatencyMillis) {
}

private boolean isTerminal() {
return status == TaskStatus.SUCCEEDED || status == TaskStatus.FAILED;
return status == TaskStatus.SUCCEEDED || status == TaskStatus.FAILED || status == TaskStatus.CANCELLED;
}

private int resolveProgressPercent() {
return progressPercent == null ? 0 : progressPercent;
}

public enum TaskStatus {
PENDING,
RUNNING,
SUCCEEDED,
FAILED
FAILED,
CANCELLED
}

public enum CreditStatus {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jobdri.jobdri_api.domain.analysis.service;

import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncCancelResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncStatusResponse;
import com.jobdri.jobdri_api.domain.analysis.dto.response.AnalysisAsyncSubmitResponse;
import com.jobdri.jobdri_api.domain.analysis.entity.AnalysisAsyncTask;
Expand Down Expand Up @@ -59,10 +60,21 @@ public AnalysisAsyncStatusResponse getTask(User user, Long mockApplyId, String t
.lastAttemptAt(status.lastAttemptAt())
.startedAt(status.startedAt())
.completedAt(status.completedAt())
.cancelRequested(status.cancelRequested())
.cancelledAt(status.cancelledAt())
.currentStep(status.currentStep())
.progressPercent(status.progressPercent())
.estimatedRemainingSeconds(status.estimatedRemainingSeconds())
.steps(status.steps())
.result(analysisService.getAnalysis(validatedUser, status.mockApplyId()))
.build();
}

public AnalysisAsyncCancelResponse cancel(User user, Long mockApplyId, String taskId) {
User validatedUser = userService.validateUser(user);
return analysisAsyncTaskService.cancelTask(validatedUser.getId(), mockApplyId, taskId);
}

private AnalysisAsyncSubmitResponse createAndProcessTask(User user, Long mockApplyId) {
PendingTaskResult pendingTaskResult = createPendingTask(user, mockApplyId);
if (!pendingTaskResult.created()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ private String channelKey(String taskId) {

private boolean isTerminal(AnalysisAsyncStatusResponse statusResponse) {
TaskStatus status = TaskStatus.valueOf(statusResponse.status());
return status == TaskStatus.SUCCEEDED || status == TaskStatus.FAILED;
return status == TaskStatus.SUCCEEDED || status == TaskStatus.FAILED || status == TaskStatus.CANCELLED;
}
}
Loading
Loading