举个例子,现在有一个接口,就拿若依的批量给用户授权角色接口来说。
原来的接口是这样的
bash
/**
* 批量选择用户授权
*/
@PreAuthorize("@ss.hasPermi('system:role:edit')")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/selectAll")
public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
{
List<Long> userIdList = Arrays.stream(userIds).collect(Collectors.toList());
sysRoleService.insertAuthUsers(roleId, Arrays.asList(userIdList));
return AjaxResult.success(taskId);
}
直接在前端获取到用户id然后就直接在sysRoleService.insertAuthUsers方法中进行数据库操作了。
如果前端用户数据量太大的话,这样返回会很慢,导致前端加载时间长,现在简单的改造这个方法。
首先写一个线程池,执行异步的时候调用。
bash
@Configuration
@EnableAsync
public class AsyncThreadPoolConfig implements AsyncConfigurer {
private static ThreadPoolTaskExecutor executor;
//获取单前机器cpu数量
private static final int cpu = Runtime.getRuntime().availableProcessors();
//设置核心线程数
private static final int corePoolSize = cpu;
//设置最大线程数
private static final int maxPoolSize = 1000;
//设置线程空闲时间(秒)
private static final int keepAliveTime = 60;
//设置主线程等待时间
private static final int awaitTerminationSeconds = 120;
//缓存队列数
private static final int queueCapacity = 200;
//线程池前缀名
private static final String threadNamePrefix = "ryTaskExecutor-";
public static ThreadPoolTaskExecutor getExecutor() {
return executor;
}
@Bean("ryTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
//等待队列大小
executor.setQueueCapacity(queueCapacity);
//空闲时间
executor.setKeepAliveSeconds(keepAliveTime);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setAwaitTerminationSeconds(awaitTerminationSeconds);
executor.setWaitForTasksToCompleteOnShutdown(true);
// RejectedExecutionHandler:当pool已经达到max-size的时候,如何处理新任务
// CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
异步任务进度条
bash
public class AsyncTaskProgress implements Serializable {
private static final long serialVersionUID = 1L;
private String status; // 任务状态:PENDING-进行中,SUCCESS-成功,FAILURE-失败
private Integer progress; // 任务进度:0-100
private String result; // 任务结果(仅在状态为SUCCESS时有用)
private String error; // 任务失败原因(仅在状态为FAILURE时有用)
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Integer getProgress() {
return progress;
}
public void setProgress(Integer progress) {
this.progress = progress;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
新建异步任务实现类
bash
@Service
public class AsyncTaskService {
private static final String REDIS_PREFIX = "ASYNC_TASK_"; // Redis中保存异步任务进度对象的前缀
@Autowired
public RedisTemplate redisTemplate;
//生成异步任务的id,用于前端查询改任务的进度使用
public String generateTaskId() {
return UUID.randomUUID().toString();
}
}
开启异步任务
bash
public void startAsyncTask(Long roleId, List<Long> userIds, String taskId) {
CompletableFuture.runAsync(() -> {
AsyncTaskProgress taskProgress = new AsyncTaskProgress();
int size = userIds.size();
int total = 0;
taskProgress.setStatus("PENDING"); //任务状态:进行中
taskProgress.setProgress(total);//任务进度: 0
taskProgress.setResult(null); //任务结果:空
taskProgress.setError(null); //任务错误:空
//在此进行异步任务的业务处理
try {
for (Long userId : userIds) {
Thread.sleep(1000);//停一秒
total++;
int percent = (int) (total * 100.0 / size);
sysRoleService.insertAuthUsers(roleId, Arrays.asList(userId));
taskProgress.setProgress(percent);
saveAsyncTaskProgress(taskId,taskProgress);
}
taskProgress.setProgress(100);
taskProgress.setResult("Task result"); // 任务结果
taskProgress.setStatus("SUCCESS"); // 任务状态:成功
saveAsyncTaskProgress(taskId,taskProgress);
} catch (Exception e) {
taskProgress.setError(e.getMessage());
taskProgress.setStatus("FAILURE");
saveAsyncTaskProgress(taskId,taskProgress);
}
}, AsyncThreadPoolConfig.getExecutor());
}
//保存进度条进度
private void saveAsyncTaskProgress(String taskId, AsyncTaskProgress taskProgress) {
redisTemplate.opsForValue().set(REDIS_PREFIX + taskId, taskProgress, 30, TimeUnit.MINUTES); // 将任务进度对象保存到Redis中,有效期30分钟
public AsyncTaskProgress getAsyncTaskProgress(String taskId) {
AsyncTaskProgress taskProgress = (AsyncTaskProgress) redisTemplate.opsForValue().get(REDIS_PREFIX + taskId); // 获取任务进度对象
if (taskProgress == null) {
throw new RuntimeException("无法获取异步任务进度,可能已经过期或不存在!"); // 如果获取不到,抛出异常
}
return taskProgress;
}
}
角色授权用户接口改成这样
bash
/**
* 批量选择用户授权
*/
@PreAuthorize("@ss.hasPermi('system:role:edit')")
@Log(title = "角色管理", businessType = BusinessType.GRANT)
@PutMapping("/authUser/selectAll")
public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds)
{
List<Long> userIdList = Arrays.stream(userIds).collect(Collectors.toList());
roleService.checkRoleDataScope(roleId);
String taskId = asyncTaskService.generateTaskId();
asyncTaskService.startAsyncTask(roleId, userIdList,taskId);
return AjaxResult.success(taskId);
}
此时还需要写一个根据任务id获取进度条的接口以供前端轮询。
bash
@GetMapping("/progress/{taskId}")
public AsyncTaskProgress getAsyncTaskProgress(@PathVariable("taskId") String taskId) {
return asyncTaskService.getAsyncTaskProgress(taskId);
}
前端改造
在授权页面加上一个进度条,这里直接用element-ui的组件。
bash
<el-progress v-if="taskStatus" :percentage="progress"></el-progress>
执行保存操作
bash
/** 选择授权用户操作 */
handleSelectUser() {
const roleId = this.queryParams.roleId;
const userIds = this.userIds.join(",");
if (userIds == "") {
this.$modal.msgError("请选择要分配的用户");
return;
}
this.taskStatus = true;
//这个方法就是调用/authUser/selectAll接口
authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => {
// this.$modal.msgSuccess(res.msg);
// if (res.code === 200) {
// this.visible = false;
// this.$emit("ok");
// }
this.taskId = res.msg;
this.sleep(1000);
const pollTaskInterval = setInterval(async () => {
//这个方法就是调用/progress/{taskId}接口
getProgress(this.taskId).then(res => {
console.log(res)
const taskProgress = res;
this.progress = taskProgress.progress;
if (taskProgress.status === 'SUCCESS') {
this.visible = false;
this.taskProgress = false;
this.$emit("ok");
this.progress = 0;
clearInterval(pollTaskInterval) // 停止轮询
} else if (taskProgress.status === 'FAILURE') {
this.taskStatus = 'FAILURE' // 将任务状态设置为失败
this.taskError = taskProgress.error // 将失败原因保存到状态中
clearInterval(pollTaskInterval) // 停止轮询
}
})
}, 1000) // 每1秒轮询一次任务进度
});
},
完整的前端页面代码
bash
<template>
<!-- 授权用户 -->
<el-dialog title="选择用户" :visible.sync="visible" width="800px" top="5vh" append-to-body>
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table @row-click="clickRow" ref="table" :data="userList" @selection-change="handleSelectionChange" height="260px">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleSelectUser">确 定</el-button>
<el-button @click="visible = false">取 消</el-button>
</div>
<el-progress v-if="taskStatus" :percentage="progress"></el-progress>
</el-dialog>
</template>
<script>
import { unallocatedUserList, authUserSelectAll, getProgress } from "@/api/system/role";
export default {
dicts: ['sys_normal_disable'],
props: {
// 角色编号
roleId: {
type: [Number, String]
}
},
data() {
return {
// 遮罩层
visible: false,
// 选中数组值
userIds: [],
// 总条数
total: 0,
// 未授权用户数据
userList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
roleId: undefined,
userName: undefined,
phonenumber: undefined
},
taskId:"",
progress:0,
taskStatus: false,
};
},
methods: {
// 显示弹框
show() {
this.queryParams.roleId = this.roleId;
this.getList();
this.visible = true;
},
clickRow(row) {
this.$refs.table.toggleRowSelection(row);
},
// 多选框选中数据
handleSelectionChange(selection) {
this.userIds = selection.map(item => item.userId);
},
// 查询表数据
getList() {
unallocatedUserList(this.queryParams).then(res => {
this.userList = res.rows;
this.total = res.total;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 选择授权用户操作 */
handleSelectUser() {
const roleId = this.queryParams.roleId;
const userIds = this.userIds.join(",");
if (userIds == "") {
this.$modal.msgError("请选择要分配的用户");
return;
}
this.taskStatus = true;
authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => {
// this.$modal.msgSuccess(res.msg);
// if (res.code === 200) {
// this.visible = false;
// this.$emit("ok");
// }
this.taskId = res.msg;
this.sleep(1000);
const pollTaskInterval = setInterval(async () => {
getProgress(this.taskId).then(res => {
console.log(res)
const taskProgress = res;
this.progress = taskProgress.progress;
if (taskProgress.status === 'SUCCESS') {
this.visible = false;
this.taskProgress = false;
this.$emit("ok");
this.progress = 0;
clearInterval(pollTaskInterval) // 停止轮询
} else if (taskProgress.status === 'FAILURE') {
this.taskStatus = 'FAILURE' // 将任务状态设置为失败
this.taskError = taskProgress.error // 将失败原因保存到状态中
clearInterval(pollTaskInterval) // 停止轮询
}
})
}, 1000) // 每1秒轮询一次任务进度
});
},
sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return true;
}
}
}
};
</script>