Files
2026-10-21 19:39:59 +08:00

3.1 KiB
Raw Permalink Blame History

通用异步任务模块

pjcloud-common-async-task 提供可由各业务模块复用的异步事务执行、Redis 进度保存和统一进度查询接口。

接入方式

业务模块增加依赖后,Spring Boot 会自动装配 AsyncTaskExecutorService、AsyncTaskProgressService 和查询接口:

<dependency>
	<groupId>com.bsd</groupId>
	<artifactId>pjcloud-common-async-task</artifactId>
</dependency>

提交任务时由业务方指定稳定的 Redis 业务命名空间、展示名称和总数。接口同步返回初始进度,业务动作进入公共模块的受控专用线程池并在单个事务中执行:

AsyncTaskProgressVO progress = asyncTaskExecutorService.submit(
		"user:import:teacher", "老师用户导入", users.size(),
		reporter -> users.forEach(user -> {
			importUser(user);
			reporter.advance("正在导入:" + user.getName());
		}));

导出等需要在成功终态返回结果文件的任务使用兼容扩展入口,原有无结果任务的 submit 契约保持不变:

AsyncTaskProgressVO progress = asyncTaskExecutorService.submitWithResult(
		"user:export", "用户信息导出", 3, reporter -> {
			String fileUrl = exportAndUpload(reporter);
			return AsyncTaskResult.file(fileUrl, "用户信息.xlsx");
});

查询数据本身耗时且提交接口需要立即返回时,可以先用大于零的占位总数创建任务,再在异步线程查询完成后更新真实总数:

AsyncTaskProgressVO progress = asyncTaskExecutorService.submitWithResult(
		"user:export", "用户信息导出", 1, reporter -> {
			List<UserVO> users = queryUsers();
			reporter.updateTotal(users.size(), "已查询 " + users.size() + " 位用户");
			return exportUsers(users, reporter);
		});

调用端统一轮询:

GET /async-task/progress?redisKey={业务命名空间}&taskId={任务ID}

Redis 租户 Key 约定

项目 Redis 基础设施会根据当前租户上下文自动为 Redis Key 增加租户前缀。业务代码只负责定义业务命名空间和业务唯一标识,不需要也不得再次手工拼接 tenantId。

正确的业务 Key:

async:task:progress:user:import:teacher:{userId}:{taskId}

错误的业务 Key:

async:task:progress:user:import:teacher:{tenantId}:{userId}:{taskId}

错误示例会导致租户维度重复出现在最终 Redis Key 中,并可能造成同一业务在不同调用方式下无法读取彼此写入的数据。

“业务 Key 不拼接租户 ID”不代表异步线程不需要租户上下文。异步任务在访问 Redis、数据库或其他租户资源前,仍需恢复 TenantContextHolder,并在任务结束后清理上下文。本模块执行器已经统一处理这一步,业务动作不应重复设置租户上下文。

事务与进度语义

  • 整个业务动作由一个 Spring 事务包裹,业务抛出异常时整体回滚。
  • completed 表示当前执行位置;失败并回滚时不代表最终成功写入数量。
  • Redis 进度默认保留 24 小时,供页面刷新或短时重连后继续查询。
  • 任务进度按当前登录用户隔离,调用端不能查询其他用户创建的任务。