package com.vincent.rsf.server.common.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.vincent.rsf.framework.common.Cools;
|
import com.vincent.rsf.framework.exception.CoolException;
|
import com.vincent.rsf.server.common.domain.BaseParam;
|
import com.vincent.rsf.server.common.service.AsyncListExportTaskService;
|
import com.vincent.rsf.server.common.service.ListExportHandler;
|
import com.vincent.rsf.server.common.service.ListExportService;
|
import com.vincent.rsf.server.system.entity.ExportTask;
|
import com.vincent.rsf.server.system.service.ExportTaskService;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.poi.ss.usermodel.Workbook;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.stereotype.Service;
|
|
import java.io.File;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.text.SimpleDateFormat;
|
import java.time.Instant;
|
import java.time.temporal.ChronoUnit;
|
import java.util.Date;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.UUID;
|
import java.util.function.Function;
|
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class AsyncListExportTaskServiceImpl implements AsyncListExportTaskService {
|
private final ExportTaskService exportTaskService;
|
private final ListExportService listExportService;
|
|
@Value("${logging.file.path:logs}")
|
private String loggingFilePath;
|
|
@Value("${rsf.export-task.expire-days:7}")
|
private int exportTaskExpireDays;
|
|
@Override
|
public ExportTask createTask(
|
String resourceKey,
|
String defaultReportTitle,
|
Map<String, Object> payload,
|
Long tenantId,
|
Long userId
|
) {
|
ExportTask task = new ExportTask();
|
task.setTaskCode(generateTaskCode());
|
task.setResourceKey(resourceKey);
|
task.setReportTitle(resolveReportTitle(payload, defaultReportTitle));
|
task.setStatus(ExportTask.STATUS_PENDING);
|
task.setPayloadJson(JSON.toJSONString(payload));
|
task.setDeleted(0);
|
task.setTenantId(tenantId);
|
task.setCreateBy(userId);
|
task.setUpdateBy(userId);
|
task.setCreateTime(new Date());
|
task.setUpdateTime(new Date());
|
task.setExpireTime(resolveExpireTime());
|
exportTaskService.save(task);
|
return task;
|
}
|
|
@Override
|
public ExportTask getTask(Long taskId, String resourceKey, Long tenantId, Long userId) {
|
return exportTaskService.getOne(
|
buildTaskQuery(taskId, tenantId, userId)
|
.eq(ExportTask::getResourceKey, resourceKey),
|
false
|
);
|
}
|
|
@Override
|
public ExportTask getTask(Long taskId, Long tenantId, Long userId) {
|
return exportTaskService.getOne(buildTaskQuery(taskId, tenantId, userId), false);
|
}
|
|
@Override
|
public File getDownloadFile(Long taskId, String resourceKey, Long tenantId, Long userId) {
|
ExportTask task = getTask(taskId, resourceKey, tenantId, userId);
|
return resolveDownloadFile(task);
|
}
|
|
@Override
|
public File getDownloadFile(Long taskId, Long tenantId, Long userId) {
|
ExportTask task = getTask(taskId, tenantId, userId);
|
return resolveDownloadFile(task);
|
}
|
|
private LambdaQueryWrapper<ExportTask> buildTaskQuery(Long taskId, Long tenantId, Long userId) {
|
return new LambdaQueryWrapper<ExportTask>()
|
.eq(ExportTask::getId, taskId)
|
.eq(ExportTask::getDeleted, 0)
|
.eq(ExportTask::getTenantId, tenantId)
|
.eq(ExportTask::getCreateBy, userId);
|
}
|
|
private File resolveDownloadFile(ExportTask task) {
|
if (task == null) {
|
throw new CoolException("导出任务不存在");
|
}
|
if (!Objects.equals(task.getStatus(), ExportTask.STATUS_SUCCESS)) {
|
throw new CoolException("导出任务尚未完成");
|
}
|
if (Cools.isEmpty(task.getFilePath())) {
|
throw new CoolException("导出文件不存在");
|
}
|
|
File file = new File(task.getFilePath());
|
if (!file.exists()) {
|
throw new CoolException("导出文件不存在");
|
}
|
return file;
|
}
|
|
@Override
|
@Async("taskExecutor")
|
public <T, P extends BaseParam> void executeAsync(
|
Long taskId,
|
String resourceKey,
|
Map<String, Object> payload,
|
Function<Map<String, Object>, P> paramBuilder,
|
ListExportHandler<T, P> exportHandler
|
) {
|
ExportTask task = exportTaskService.getById(taskId);
|
if (task == null) {
|
return;
|
}
|
|
task.setStatus(ExportTask.STATUS_PROCESSING);
|
task.setUpdateTime(new Date());
|
exportTaskService.updateById(task);
|
|
try {
|
ListExportService.ExportWorkbook exportWorkbook =
|
listExportService.prepareExportWorkbook(payload, paramBuilder, exportHandler);
|
File outputFile = createOutputFile(taskId, resourceKey, task.getReportTitle());
|
writeWorkbook(exportWorkbook.workbook(), outputFile);
|
|
task.setStatus(ExportTask.STATUS_SUCCESS);
|
task.setRowCount(exportWorkbook.rowCount());
|
task.setFileName(outputFile.getName());
|
task.setFilePath(outputFile.getAbsolutePath());
|
task.setErrorMsg(null);
|
task.setUpdateTime(new Date());
|
exportTaskService.updateById(task);
|
} catch (Exception error) {
|
log.error("异步导出失败, resourceKey={}, taskId={}", resourceKey, taskId, error);
|
task.setStatus(ExportTask.STATUS_FAILED);
|
task.setErrorMsg(truncateErrorMessage(error));
|
task.setUpdateTime(new Date());
|
exportTaskService.updateById(task);
|
}
|
}
|
|
private String resolveReportTitle(Map<String, Object> payload, String defaultReportTitle) {
|
Object metaObject = payload.get("meta");
|
if (metaObject instanceof Map<?, ?> metaMap) {
|
Object reportTitle = metaMap.get("reportTitle");
|
if (reportTitle != null && !String.valueOf(reportTitle).trim().isEmpty()) {
|
return String.valueOf(reportTitle).trim();
|
}
|
}
|
return defaultReportTitle;
|
}
|
|
private String generateTaskCode() {
|
return "EXP-" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "-"
|
+ UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
|
}
|
|
private Date resolveExpireTime() {
|
return Date.from(Instant.now().plus(Math.max(exportTaskExpireDays, 1), ChronoUnit.DAYS));
|
}
|
|
private File createOutputFile(Long taskId, String resourceKey, String reportTitle) {
|
String safeTitle = sanitizeFileName(reportTitle);
|
String safeResourceKey = sanitizePathSegment(resourceKey);
|
String dayFolder = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
File dir = new File(loggingFilePath, "export-tasks/" + safeResourceKey + "/" + dayFolder);
|
if (!dir.exists() && !dir.mkdirs()) {
|
throw new CoolException("导出目录创建失败");
|
}
|
|
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
return new File(dir, safeTitle + "-" + taskId + "-" + timestamp + ".xlsx");
|
}
|
|
private void writeWorkbook(Workbook workbook, File outputFile) throws IOException {
|
try (Workbook targetWorkbook = workbook; FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
targetWorkbook.write(outputStream);
|
outputStream.flush();
|
}
|
}
|
|
private String sanitizeFileName(String fileName) {
|
String normalized = Objects.toString(fileName, "导出报表").trim();
|
if (normalized.isEmpty()) {
|
normalized = "导出报表";
|
}
|
return normalized.replaceAll("[\\\\/:*?\"<>|]", "_");
|
}
|
|
private String sanitizePathSegment(String segment) {
|
String normalized = Objects.toString(segment, "default").trim();
|
if (Cools.isEmpty(normalized)) {
|
normalized = "default";
|
}
|
return normalized.replaceAll("[\\\\/:*?\"<>|]", "_");
|
}
|
|
private String truncateErrorMessage(Exception error) {
|
String message = error == null ? "" : Objects.toString(error.getMessage(), error.getClass().getSimpleName());
|
if (message.length() <= 500) {
|
return message;
|
}
|
return message.substring(0, 500);
|
}
|
}
|