背景
使用springboot+mybatisPlus进行业务开发 为列表返回设置翻译值,由于需要翻译的字段较多考虑使用异步提升接口效率,但是由于使用了mybatisPlus的全局租户拦截,需要获取用户信息导致报错。
解决方案:
采用 Spring 的 TaskDecorator
方案来传播安全上下文
实现代码:
package com.minex.ams.infrastructure.config.thread; import org.springframework.core.task.TaskDecorator; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @Component public class SecurityContextCopyingDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // 获取当前线程的安全上下文 SecurityContext context = SecurityContextHolder.getContext(); return () -> { try { // 设置新的安全上下文到当前线程 SecurityContextHolder.setContext(context); // 执行实际的任务 runnable.run(); } finally { // 清理操作,确保不会意外地保留上下文 SecurityContextHolder.clearContext(); } }; } }
package com.minex.ams.infrastructure.config.thread; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class AsyncConfig { @Bean(name = "contextAwareExecutor") public ThreadPoolTaskExecutor taskExecutor(SecurityContextCopyingDecorator decorator) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); // 根据需要调整 executor.setMaxPoolSize(10); // 根据需要调整 executor.setQueueCapacity(25); // 根据需要调整 executor.setTaskDecorator(decorator); // 设置 TaskDecorator executor.initialize(); return executor; } }
具体使用:
@Resource
private ThreadPoolTaskExecutor executor;
private ThreadPoolTaskExecutor executor;
private void handleNames(List<WireVO> records) { // 使用CompletableFuture并行加载不同种类的数据 CompletableFuture<Void> projectFuture = CompletableFuture.runAsync(() -> { List<Long> projectIds = records.stream().map(WireVO::getProjectId).collect(Collectors.toList()); if (!projectIds.isEmpty()) { List<Project> projects = projectRepository.lambdaQuery().in(Project::getId, projectIds).list(); records.forEach(t -> t.setProjectName(projects.stream() .filter(project -> project.getId().equals(t.getProjectId())) .findFirst().orElse(new Project()).getName())); } },executor); CompletableFuture<Void> departmentFuture = CompletableFuture.runAsync(() -> { List<Long> pmIds = records.stream().map(WireVO::getPmId).collect(Collectors.toList()); if (!pmIds.isEmpty()) { List<Department> departments = departmentRepository.lambdaQuery().in(Department::getId, pmIds).list(); records.forEach(t -> t.setManagementName(departments.stream() .filter(department -> department.getId().equals(t.getPmId())) .findFirst().orElse(new Department()).getName())); } },executor); CompletableFuture<Void> deviceModelFuture = CompletableFuture.runAsync(() -> { List<Long> deviceModelIds = records.stream().map(WireVO::getDeviceModelId).collect(Collectors.toList()); if (!deviceModelIds.isEmpty()) { List<DeviceModel> deviceModels = deviceModelRepository.lambdaQuery().in(DeviceModel::getId, deviceModelIds).list(); records.forEach(t -> t.setDeviceModelName(deviceModels.stream() .filter(deviceModel -> deviceModel.getId().equals(t.getDeviceModelId())) .findFirst().orElse(new DeviceModel()).getName())); } },executor); // 等待所有异步任务完成 CompletableFuture.allOf(projectFuture, departmentFuture, deviceModelFuture).join(); }
通过这种方式,你可以在异步任务中正确访问 SecurityContextHolder.getContext().getAuthentication(),因为安全上下文已经被适当地传播到了子线程中。