package com.sinata.appointment.service; import com.sinata.appointment.constants.IntegrationConstant; import com.sinata.appointment.dto.ExportTaskStatusVo; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Service; @Slf4j @Service @RequiredArgsConstructor public class ExportApplicationService { private static final String STATUS_QUEUED = "QUEUED"; private static final String STATUS_RUNNING = "RUNNING"; private static final String STATUS_COMPLETED = "COMPLETED"; private static final String STATUS_FAILED = "FAILED"; private static final String APPOINTMENT_EXPORT_TYPE = "appointment"; private final ObjectProvider rabbitTemplateProvider; private final AppointmentApplicationService appointmentApplicationService; private final LocalExportAsyncService localExportAsyncService; private final ConcurrentHashMap taskStore = new ConcurrentHashMap<>(); public String enqueueExport(String exportType, Map criteria) { String normalizedType = normalizeType(exportType); String taskId = UUID.randomUUID().toString().replace("-", ""); Map safeCriteria = criteria == null ? Map.of() : new HashMap<>(criteria); RabbitTemplate rabbitTemplate = rabbitTemplateProvider.getIfAvailable(); if (rabbitTemplate == null) { taskStore.put(taskId, ExportTaskSnapshot.queued(taskId, normalizedType)); localExportAsyncService.process(taskId, normalizedType, safeCriteria); return taskId; } taskStore.put(taskId, ExportTaskSnapshot.queued(taskId, normalizedType)); log.info("Enqueue export taskId={} type={}", taskId, normalizedType); Map payload = new HashMap<>(); payload.put("taskId", taskId); payload.put("type", normalizedType); payload.put("criteria", safeCriteria); rabbitTemplate.convertAndSend(IntegrationConstant.EXPORT_TASK_QUEUE, payload); return taskId; } public ExportTaskStatusVo getTaskStatus(String taskId) { ExportTaskSnapshot snapshot = taskStore.get(taskId); if (snapshot == null) { return null; } ExportTaskStatusVo vo = new ExportTaskStatusVo(); vo.setTaskId(snapshot.taskId()); vo.setType(snapshot.type()); vo.setStatus(snapshot.status()); vo.setFileName(snapshot.fileName()); vo.setErrorMessage(snapshot.errorMessage()); return vo; } public Path getExportFilePath(String taskId) { ExportTaskSnapshot snapshot = taskStore.get(taskId); if (snapshot == null || snapshot.filePath() == null || !Files.exists(snapshot.filePath())) { return null; } return snapshot.filePath(); } void processLocalExport(String taskId, String exportType, Map criteria) { taskStore.computeIfPresent(taskId, (_k, v) -> v.toRunning()); try { if (!APPOINTMENT_EXPORT_TYPE.equals(normalizeType(exportType))) { throw new IllegalArgumentException("unsupported export type: " + exportType); } byte[] bytes = appointmentApplicationService.exportExcel( asString(criteria.get("visitedUserName")), asLong(criteria.get("visitedDeptId")), asString(criteria.get("accessArea")), asString(criteria.get("localUnit")), asLocalDate(criteria.get("visitDateStart")), asLocalDate(criteria.get("visitDateEnd")), asString(criteria.get("displayStatus")), asString(criteria.get("approvalStatus")), asString(criteria.get("visitorKeyword")), asString(criteria.get("visitReasonCode"))); Path exportDir = Paths.get(System.getProperty("java.io.tmpdir"), "reservation-mini-program-exports"); Files.createDirectories(exportDir); Path filePath = exportDir.resolve(taskId + ".xlsx"); Files.write(filePath, bytes); taskStore.put( taskId, new ExportTaskSnapshot(taskId, normalizeType(exportType), STATUS_COMPLETED, filePath, filePath.getFileName().toString(), null)); } catch (Exception ex) { log.error("Local export failed taskId={} type={}", taskId, exportType, ex); taskStore.put( taskId, new ExportTaskSnapshot(taskId, normalizeType(exportType), STATUS_FAILED, null, null, ex.getMessage())); } } private String normalizeType(String exportType) { return exportType == null || exportType.isBlank() ? APPOINTMENT_EXPORT_TYPE : exportType.trim(); } private String asString(Object value) { return value == null ? null : value.toString().trim(); } private Long asLong(Object value) { if (value == null) { return null; } try { return Long.parseLong(value.toString().trim()); } catch (NumberFormatException ex) { return null; } } private LocalDate asLocalDate(Object value) { if (value == null) { return null; } try { return LocalDate.parse(value.toString().trim()); } catch (DateTimeParseException ex) { return null; } } private record ExportTaskSnapshot( String taskId, String type, String status, Path filePath, String fileName, String errorMessage) { private static ExportTaskSnapshot queued(String taskId, String type) { return new ExportTaskSnapshot(taskId, type, STATUS_QUEUED, null, null, null); } private ExportTaskSnapshot toRunning() { return new ExportTaskSnapshot(taskId, type, STATUS_RUNNING, filePath, fileName, errorMessage); } } }