# 通用异步任务模块 `pjcloud-common-async-task` 提供可由各业务模块复用的异步事务执行、Redis 进度保存和统一进度查询接口。 ## 接入方式 业务模块增加依赖后,Spring Boot 会自动装配 `AsyncTaskExecutorService`、`AsyncTaskProgressService` 和查询接口: ```xml com.bsd pjcloud-common-async-task ``` 提交任务时由业务方指定稳定的 Redis 业务命名空间、展示名称和总数。接口同步返回初始进度,业务动作进入公共模块的受控专用线程池并在单个事务中执行: ```java AsyncTaskProgressVO progress = asyncTaskExecutorService.submit( "user:import:teacher", "老师用户导入", users.size(), reporter -> users.forEach(user -> { importUser(user); reporter.advance("正在导入:" + user.getName()); })); ``` 导出等需要在成功终态返回结果文件的任务使用兼容扩展入口,原有无结果任务的 `submit` 契约保持不变: ```java AsyncTaskProgressVO progress = asyncTaskExecutorService.submitWithResult( "user:export", "用户信息导出", 3, reporter -> { String fileUrl = exportAndUpload(reporter); return AsyncTaskResult.file(fileUrl, "用户信息.xlsx"); }); ``` 查询数据本身耗时且提交接口需要立即返回时,可以先用大于零的占位总数创建任务,再在异步线程查询完成后更新真实总数: ```java AsyncTaskProgressVO progress = asyncTaskExecutorService.submitWithResult( "user:export", "用户信息导出", 1, reporter -> { List users = queryUsers(); reporter.updateTotal(users.size(), "已查询 " + users.size() + " 位用户"); return exportUsers(users, reporter); }); ``` 调用端统一轮询: ```text GET /async-task/progress?redisKey={业务命名空间}&taskId={任务ID} ``` ## Redis 租户 Key 约定 项目 Redis 基础设施会根据当前租户上下文自动为 Redis Key 增加租户前缀。业务代码只负责定义业务命名空间和业务唯一标识,**不需要也不得再次手工拼接 `tenantId`**。 正确的业务 Key: ```text async:task:progress:user:import:teacher:{userId}:{taskId} ``` 错误的业务 Key: ```text async:task:progress:user:import:teacher:{tenantId}:{userId}:{taskId} ``` 错误示例会导致租户维度重复出现在最终 Redis Key 中,并可能造成同一业务在不同调用方式下无法读取彼此写入的数据。 “业务 Key 不拼接租户 ID”不代表异步线程不需要租户上下文。异步任务在访问 Redis、数据库或其他租户资源前,仍需恢复 `TenantContextHolder`,并在任务结束后清理上下文。本模块执行器已经统一处理这一步,业务动作不应重复设置租户上下文。 ## 事务与进度语义 - 整个业务动作由一个 Spring 事务包裹,业务抛出异常时整体回滚。 - `completed` 表示当前执行位置;失败并回滚时不代表最终成功写入数量。 - Redis 进度默认保留 24 小时,供页面刷新或短时重连后继续查询。 - 任务进度按当前登录用户隔离,调用端不能查询其他用户创建的任务。