1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
| @Slf4j @Service public class ExcelTaskService {
private static final String KEY_PREFIX = "excel:task:"; private static final Duration EXPIRE = Duration.ofHours(24);
@Resource private StringRedisTemplate redisTemplate; @Resource private ExcelCommonService excelCommonService; @Value("${excel.task-dir}") private String taskDir;
public void save(ExcelTaskInfo task) { task.setUpdateTime(LocalDateTime.now()); redisTemplate.opsForValue().set( KEY_PREFIX + task.getTaskId(), JSON.toJSONString(task), EXPIRE ); }
public ExcelTaskInfo get(String taskId) { String json = redisTemplate.opsForValue().get(KEY_PREFIX + taskId); if (StrUtil.isBlank(json)) { throw new IllegalArgumentException("任务不存在或已过期: " + taskId); } return JSON.parseObject(json, ExcelTaskInfo.class); }
public void updateProgress(String taskId, long incSuccess, long incFail, String msg) { ExcelTaskInfo task = get(taskId); task.setSuccess(task.getSuccess() + incSuccess); task.setFail(task.getFail() + incFail); if (StrUtil.isNotBlank(msg)) { task.setMessage(msg); } save(task); }
public String submitImport(MultipartFile file, Class<?> pojoClazz, ExcelReadCallback<?> callback) { String taskId = IdUtil.fastSimpleUUID(); String origName = file.getOriginalFilename(); Path savedPath = Path.of(taskDir, taskId + "_" + (origName == null ? "upload.xlsx" : origName)); try { file.transferTo(savedPath); } catch (IOException e) { throw new RuntimeException("文件保存失败", e); }
ExcelTaskInfo task = ExcelTaskInfo.builder() .taskId(taskId) .type(ExcelTaskInfo.TaskType.IMPORT) .status(ExcelTaskInfo.TaskStatus.PENDING) .fileName(savedPath.getFileName().toString()) .createTime(LocalDateTime.now()) .build(); save(task);
doImportAsync(taskId, savedPath, pojoClazz, callback); return taskId; }
@Async("excelTaskExecutor") public <T> void doImportAsync(String taskId, Path filePath, Class<T> pojoClazz, ExcelReadCallback<T> callback) { ExcelTaskInfo task = get(taskId); task.setStatus(ExcelTaskInfo.TaskStatus.RUNNING); save(task);
List<ExcelErrorRow> errors = Collections.synchronizedList(new ArrayList<>());
try (InputStream in = Files.newInputStream(filePath)) { ExcelReadCallback<T> wrapper = new ExcelReadCallback<>() { @Override public void onBatch(List<T> batch, int startNo) { try { callback.onBatch(batch, startNo); updateProgress(taskId, batch.size(), 0, null); } catch (Exception e) { for (int i = 0; i < batch.size(); i++) { errors.add(new ExcelErrorRow(startNo + i, "批量处理失败: " + e.getMessage(), JSON.toJSONString(batch.get(i)))); } updateProgress(taskId, 0, batch.size(), "出现批量错误,请下载错误报告查看"); } }
@Override public void onRowError(int rowNo, String message, T rowData) { errors.add(new ExcelErrorRow(rowNo, message, JSON.toJSONString(rowData))); updateProgress(taskId, 0, 1, null); } };
excelCommonService.read(in, pojoClazz, wrapper);
if (!errors.isEmpty()) { Path errPath = Path.of(taskDir, taskId + "_errors.xlsx"); try (OutputStream out = Files.newOutputStream(errPath)) { excelCommonService.writeErrorReport(out, errors); } task.setResultFile("/excel/download/" + errPath.getFileName()); task.setMessage("处理完成,存在 " + errors.size() + " 条错误数据"); } else { task.setMessage("全部导入成功"); } task.setStatus(ExcelTaskInfo.TaskStatus.SUCCESS);
} catch (Exception e) { log.error("导入任务失败 taskId={}", taskId, e); task.setStatus(ExcelTaskInfo.TaskStatus.FAILED); task.setMessage("系统异常: " + e.getMessage()); } finally { save(task); } }
public <T> String submitExport(String exportFileName, Class<T> pojoClazz, String sheetName, Function<Integer, List<T>> fetchData) { String taskId = IdUtil.fastSimpleUUID(); String safeName = (exportFileName == null ? "export" : exportFileName) + ".xlsx"; Path outPath = Path.of(taskDir, taskId + "_" + safeName);
ExcelTaskInfo task = ExcelTaskInfo.builder() .taskId(taskId) .type(ExcelTaskInfo.TaskType.EXPORT) .status(ExcelTaskInfo.TaskStatus.PENDING) .fileName(safeName) .createTime(LocalDateTime.now()) .build(); save(task);
doExportAsync(taskId, outPath, pojoClazz, sheetName, fetchData); return taskId; }
@Async("excelTaskExecutor") public <T> void doExportAsync(String taskId, Path outPath, Class<T> pojoClazz, String sheetName, Function<Integer, List<T>> fetchData) { ExcelTaskInfo task = get(taskId); task.setStatus(ExcelTaskInfo.TaskStatus.RUNNING); save(task);
try (OutputStream out = Files.newOutputStream(outPath)) { final long[] totalWritten = {0}; Function<Integer, List<T>> wrapper = offset -> { List<T> batch = fetchData.apply(offset); if (!batch.isEmpty()) { totalWritten[0] += batch.size(); updateProgress(taskId, batch.size(), 0, "已导出 " + totalWritten[0] + " 行"); } return batch; }; excelCommonService.writeBigData(out, pojoClazz, sheetName, wrapper);
task.setStatus(ExcelTaskInfo.TaskStatus.SUCCESS); task.setMessage("导出完成,共 " + totalWritten[0] + " 行"); task.setResultFile("/excel/download/" + outPath.getFileName()); } catch (Exception e) { log.error("导出任务失败 taskId={}", taskId, e); task.setStatus(ExcelTaskInfo.TaskStatus.FAILED); task.setMessage("导出失败: " + e.getMessage()); } finally { save(task); } } }
|