通用导出功能-实现思路
实现思路:
AOP 环绕通知,通过注解@ExcelExport
来注释一个查询接口,通过环绕通知处理这个查询接口。
@Around("@annotation(excelExport)")
public Object excelExport(ProceedingJoinPoint joinPoint, ExcelExport excelExport) throws Throwable {
// ...
}
异步时候将任务处理成一个线程任务,并将任务绑定一个uuid,放在一个 ConcurrentHashMap
中,key
是uuid
,value
是Future
任务本身。
private Map<String, Future> futureMap = new ConcurrentHashMap();
// ...
private String asyncExecute(ExportDataHelper.ExportTask exportTask) {
if (this.exportAsyncTemplate == null) {
// ...
} else {
UUID uuid = UUID.randomUUID();
exportTask.getDto().setTaskCode(uuid.toString());
return this.exportAsyncTemplate.submit(exportTask.getDto(), () -> {
Future future = this.executorService.submit(exportTask);
log.info(uuid + "导出参数:" + getArgs(exportTask.args) + "导出方法:" + exportTask.method.getName());
this.futureManager.put(uuid, future);
return uuid.toString();
});
}
}
提供统一的查询接口,查看任务进行状态,并且通过userid
关联任务,保证在操作随机导出任务时,只有创建人可以取消自己创建的导出任务。
取消任务,需要先核对当前角色是否为对应任务的创建人,之后调用取消方法。取消方法中会尝试根据uuid
找到上述futureMap
中的线程任务,之后尝试通过java.util.concurrent.Future#cancel
方法取消异步任务,并返回取消结果。
public TaskState cancel(String uuid) {
Future future = (Future)this.futureMap.remove(uuid);
if (future == null) {
LOGGER.warn("未找到该导出任务[" + uuid + "]");
return ExportFutureManager.TaskState.REMOVED;
} else if (future.isCancelled()) {
LOGGER.warn("该导出任务[" + uuid + "]已取消");
return ExportFutureManager.TaskState.CANCELED;
} else if (future.isDone()) {
LOGGER.warn("该导出任务[" + uuid + "]已完成");
return ExportFutureManager.TaskState.DONE;
} else {
return future.cancel(true) ? ExportFutureManager.TaskState.SUCCESS : ExportFutureManager.TaskState.FAILED;
}
}
存在问题:
由于整个过程中,对任务的记录只存在于内存中的ConcurrentHashMap
中,当服务异常宕机时,会丢失全部任务。此时会造成mysql
中的任务记录与实际任务记录不一致,需要人为干预。
设计初,考虑导出数据本身相对并不重要,可以人为手动重新导出,此实现为平衡业务需求与系统复杂度的结果。