springboot异步任务带自定义返回结果和异步任务查看。
前言:由于多年在写的都是python,经常在做一些异步任务时,如导入导出报表这种,都是用celery来做异步生成表格,然后循环更新任务状态,任务结束后返回文件名或其他结果。最近某个项目采用java的springboot在写,刚好也要做些报表导入导出的事情,由此查了一下资料,捡了一些别人写好的东西,并撸了一遍车轮子,以下是相关工程代码,在此记录,下次遇到好直接搬运。
1.pom文件,在此强烈推荐阿里巴巴的easyexcel,真的好用,官网文档:https://www.yuque.com/easyexcel/doc/easyexcel
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
贴个目录结构

2.开启异步任务支持和配置
SpringTaskExecutor.java
package com.src.xxx;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync //开启异步任务支持
public class SpringTaskExecutor implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(20);
taskExecutor.initialize();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
AsyncTaskConstructor.java
package com.src.xxx.task;
public interface AsyncTaskConstructor {
public void async();
}
AsyncTaskExecutor.java
package com.src.xxx.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncTaskExecutor {
private static Logger LOG = LoggerFactory.getLogger(AsyncTaskExecutor.class);
@Async
public void executor(AsyncTaskConstructor asyncTaskGenerator, String taskInfo) {
LOG.info("AsyncTaskExecutor is executing async taskController:{}", taskInfo);
asyncTaskGenerator.async();
}
}
AsyncTaskManager.java
package com.src.xxx.task;
import com.src.xxx.entity.Task;
import com.src.xxx.service.taskService;
import com.src.xxx.tools.DatePattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 异步任务管理器
*/
@Component
public class AsyncTaskManager {
private final taskService taskService;
public AsyncTaskManager(taskService taskService) {
this.taskService = taskService;
}
@Autowired
AsyncTaskExecutor asyncTaskExecutor;
/**
* 初始化任务
* @param asyncTaskConstructor 异步任务构造器
* @return taskInfo
*/
public Task submit(AsyncTaskConstructor asyncTaskConstructor, String taskId) {
Task task = taskService.get_task(taskId);
if(task == null){
return null;
}
asyncTaskExecutor.executor(asyncTaskConstructor,taskId);
return task;
}
/**
* 保存任务信息
*/
public void setTaskInfo(Task task) {
taskService.update_task(task);
}
/**
* 获取任务信息
*
* @param taskId 任务ID
* @return
*/
public Task getTaskInfo(String taskId) {
return taskService.get_task(taskId);
}
}
AsyncTaskMonitor.java
package com.src.xxx.task;
import com.src.xxx.entity.Task;
import com.src.xxx.tools.DatePattern;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 异步任务监控
*/
@Component
@Aspect
public class AsyncTaskMonitor {
@Autowired
AsyncTaskManager manager;
private static Logger LOG = LoggerFactory.getLogger(AsyncTaskMonitor.class);
@Around("execution(* com.src.hospotal.task.AsyncTaskExecutor.*(..))")
public void taskHandle(ProceedingJoinPoint pjp) {
//获取taskId
String taskId = pjp.getArgs()[1].toString();
//获取任务信息
LOG.info("AsyncTaskMonitor is monitoring async taskController:{}", taskId);
try {
pjp.proceed();
} catch (Throwable throwable) {
Task task = manager.getTaskInfo(taskId);
task.setStatus(1);
task.setProgress(100);
task.setMsg("异步任务出现错误!");
task.setInfo(throwable.getMessage());
task.setUpdatetime(DatePattern.getnow());
manager.setTaskInfo(task);
LOG.error("AsyncTaskMonitor:async taskController {} is failed.Error info:{}", taskId, throwable.getMessage());
}
}
}
上面目录结构里面的代码就完了。
接下来是上面代码依赖的一些其他包的文件
entity里面的Task.java
package com.src.xxx.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel
public class Task {
//id,task_id,status,msg,info,progress,createtime
//@ExcelProperty 这个注解表示给表头命名,不加这个注解的,会默认已字段名
//@ExcelIgnore 这个注解表示,不导出该字段
@ExcelProperty("记录id")
@ApiModelProperty(value = "id")
private Integer id;
@ApiModelProperty(value = "任务ID")
private String taskId;
@ApiModelProperty(value = "0 进行中 1出错了 2已完成")
private Integer status;
@ApiModelProperty(value = "提示信息")
private String msg;
@ApiModelProperty(value = "返回结果原始数据,如果数据类型是json,可以取obj或自行")
private String info;
private Integer infoType;
private Object obj;
private Integer progress;
@ApiParam(hidden = true)
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createtime;
private String updatetime;
private Integer createuserid;
}
对应的数据库表结构
CREATE TABLE `xxx`.`task` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`task_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务ID',
`status` int(11) NOT NULL DEFAULT 0 COMMENT '0 进行中 1出错了 2已完成',
`msg` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '提示信息',
`info` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '任务返回对象信息',
`progress` int(11) NOT NULL DEFAULT 0 COMMENT '进度 0到100',
`info_type` int(11) NOT NULL DEFAULT 0 COMMENT '0普通字符串,1json字符串',
`createtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`createuserid` int(11) NOT NULL DEFAULT 0 COMMENT '创建者id',
`updatetime` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

dao里面的taskMapper.java

package com.src.xxx.dao;
import com.src.xxx.entity.Task;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface taskMapper {
//task_id,status,msg,info,progress,info_type,createuserid
@Insert("insert into task(task_id,status,msg,info,info_type,progress,createuserid,updatetime) values (#{taskId},#{status},#{msg},#{info},#{infoType},#{progress},#{createuserid},#{updatetime})")
boolean insert_task(Task task);
@Update("update task set status = #{status},msg = #{msg},info= #{info},info_type= #{infoType},progress = #{progress},updatetime= #{updatetime} where id=#{id}")
boolean update_task(Task task);
@Select("select * from task where task_id=#{taskId}")
Task get_task(String taskId);
@Select("select * from task where createuserid=#{createuserid} limit #{page},#{limit}")
List<Task> get_task_list(Integer createuserid, Integer page, Integer limit);
@Select("select count(*) from task where createuserid=#{createuserid} limit #{page},#{limit}")
Integer count(Integer createuserid, Integer page, Integer limit);
}
service
package com.src.xxx.service;
import com.src.xxx.entity.Pager;
import com.src.xxx.entity.Task;
public interface taskService {
//添加任务
boolean insert_task(Task task);
//更新任务
boolean update_task(Task task);
//获取某个任务
Task get_task(String taskId);
//获取任务列表
Pager<Task> get_task_list(Integer createuserid, Integer page, Integer limit);
// 不带分页的获取任务列表,为了测试
List<Task> get_task(Integer createuserid);
}
entity里面有个分页的 Pager.java
package com.src.xxx.entity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.util.List;
@Data
@ApiModel
public class Pager<T> {
private int page;//分页起始页
private int size;//每页记录数
private List<T> rows;//返回的记录集合
private long total;//总记录条数
}
serviceImpl中 taskServiceImpl.java
package com.src.xxx.serviceImpl;
import com.src.xxx.dao.taskMapper;
import com.src.xxx.entity.Pager;
import com.src.xxx.entity.Task;
import com.src.xxx.service.taskService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class taskServiceImpl implements taskService {
private final taskMapper taskMapper;
public taskServiceImpl(taskMapper taskMapper) {
this.taskMapper = taskMapper;
}
@Override
public boolean insert_task(Task task) {
return taskMapper.insert_task(task);
}
@Override
public boolean update_task(Task task) {
return taskMapper.update_task(task);
}
@Override
public Task get_task(String taskId) {
return taskMapper.get_task(taskId);
}
@Override
public Pager<Task> get_task_list(Integer createuserid, Integer page, Integer limit) {
Integer pageStrat = page>1 ? page-1 :0;
Pager<Task> pager = new Pager<Task>();
List<Task> list = taskMapper.get_task_list(createuserid,pageStrat*limit,limit);
pager.setRows(list);
pager.setPage(page);
pager.setSize(limit);
pager.setTotal(taskMapper.count(createuserid,pageStrat*limit,limit));
return pager;
}
@Override
public List<Task> get_task_list2(Integer createuserid) {
//为了测试
return taskMapper.get_task_list(createuserid,1,999);
}
}
tools里面有个DatePattern.java
package com.src.xxx.tools;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;
public class DatePattern {
public static final Pattern REGEX_NORM = Pattern.compile("\\d{4}-\\d{1,2}-\\d{1,2}( \\d{1,2}:\\d{1,2}(:\\d{1,2})?)?");
public static final String NORM_DATE_PATTERN = "yyyy-MM-dd";
public static final String NORM_TIME_PATTERN = "HH:mm:ss";
public static final String NORM_DATETIME_MINUTE_PATTERN = "yyyy-MM-dd HH:mm";
public static final String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String NORM_DATETIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
public static final String CHINESE_DATE_PATTERN = "yyyy年MM月dd日";
public static final String PURE_DATE_PATTERN = "yyyyMMdd";
public static final String PURE_TIME_PATTERN = "HHmmss";
public static final String PURE_DATETIME_PATTERN = "yyyyMMddHHmmss";
public static final String PURE_DATETIME_MS_PATTERN = "yyyyMMddHHmmssSSS";
public static final String HTTP_DATETIME_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
public static final String JDK_DATETIME_PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy";
public static final String UTC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public static final String UTC_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
public static final String UTC_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
public static final String UTC_MS_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public DatePattern() {
}
public static String getnow(){
Date dd=new Date();
//格式化
SimpleDateFormat sim=new SimpleDateFormat(NORM_DATETIME_PATTERN);
return sim.format(dd);
}
}
tools里面的有个ExcelUtil.java
package com.src.xxx.tools;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.src.hospotal.entity.Task;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.List;
public class ExcelUtil {
/**
* 相对路径,文件会生成在与项目平级的目录
*/
private static final String EXCEL_SUFFIX = ".xls";
private static String FILE_PATH(){
String UPLOAD_PATH = "C:/upload/excel/";
if(System.getProperty("os.name").toLowerCase().indexOf("linux")>=0){
UPLOAD_PATH = "/upload/excel/";
}
//获取文件上传的根目录 C:\Users\wanghao/upload/img
return UPLOAD_PATH;
}
/**
* 在指定位置生成excel文件
*
* @param fileName 文件路径
* @param clazz 导出数据类型
* @param list 导出数据
* @param <T> 可以不加这个也行,但是会有警告,看起来不舒服
*/
public static <T> void writeToExcel(String fileName, Class<T> clazz, List<T> list) {
ExcelWriter excelWriter = EasyExcel.write(fileName).build();
WriteSheet sheet = EasyExcel.writerSheet().head(clazz).build();
excelWriter.write(list, sheet);
excelWriter.finish();
}
/**
* 生成excel文件
*
* @param fileName
* @param clazz
* @param list
* @param <T>
* @return
*/
public static <T> File create_Excel(String fileName, Class<T> clazz, List<T> list) {
System.out.println(generateFilePath());
if (!generateFilePath()) {
return null;
}
String fileFullName = FILE_PATH() + fileName + EXCEL_SUFFIX;
//System.out.println(fileFullName);
ExcelUtil.writeToExcel(fileFullName, clazz, list);
return new File(fileFullName);
}
/**
* 删除文件
*
* @param file
* @return
*/
public boolean deleteFile(File file) {
if (file != null) {
return file.delete();
}
return true;
}
private static boolean generateFilePath() {
File path = new File(FILE_PATH());
//System.out.println(path);
if (path.exists()) {
return true;
} else {
return path.mkdirs();
}
}
public static void main(String[] args) {
List<Task> list = new ArrayList<Task>();
for (int i = 0; i < 10; i++) {
Task data = new Task();
data.setMsg("字符串" + i);
data.setUpdatetime(new Date().toString());
list.add(data);
}
System.out.println(list);
File f = create_Excel("11", Task.class, list);
System.out.println(f);
}
}
控制器
taskController.java
package com.src.xxx.controller;
import com.src.xxx.entity.*;
import com.src.xxx.service.taskService;
import com.src.xxx.task.AsyncTaskManager;
import com.src.xxx.tools.CodeUtil;
import com.src.xxx.tools.DatePattern;
import com.src.xxx.tools.Result;
import com.src.xxx.tools.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.System;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import static com.src.hospotal.tools.ExcelUtil.create_Excel;
@RestController
@RequestMapping("/api/task")
@Api(tags="异步任务")
public class taskController {
private final taskService taskService;
//注入异步任务管理器
private final AsyncTaskManager asyncTaskManager;
@Autowired
public taskController(taskService taskService, AsyncTaskManager asyncTaskManager) {
this.taskService = taskService;
this.asyncTaskManager = asyncTaskManager;
}
@ApiOperation(value="导出任务记录报表")
@GetMapping("/get_task_excel")
public Result get_task_excel(@RequestParam(required = false) Integer createuserid, HttpServletRequest request) {
//调用任务管理器中的submit去提交一个异步任务
String taskId = UUID.randomUUID().toString();
Task task = new Task();
task.setTaskId(taskId);
task.setStatus(0);
task.setMsg("开始执行任务!");
task.setInfo("");
task.setInfoType(0);
task.setProgress(0);
task.setCreateuserid(createuserid);
task.setUpdatetime(DatePattern.getnow());
if(taskService.insert_task(task)){
Task task1 = asyncTaskManager.submit(() -> {
System.out.println("_____start_____");
System.out.println(taskId);
Task asynctask = taskService.get_task(taskId);
//获取数据列表,
List<Task> lists = taskService.get_task_list2(createuserid);
asynctask.setMsg("正在生成文件!");
asynctask.setProgress(30);
taskService.update_task(asynctask);
File f = create_Excel(taskId, Blood.class, lists);
if(f != null){
asynctask.setInfo(f.getName());
asynctask.setStatus(2);
asynctask.setMsg("文件已生成!");
}else{
asynctask.setStatus(1);
asynctask.setMsg("文件生成失败!");
}
asynctask.setProgress(100);
taskService.update_task(asynctask);
System.out.println("_____end_____");
}, taskId);
if(task1 == null){
return R.error("异步任务创建失败!");
}
return R.success(task1);
};
return R.error("异步任务创建失败!");
}
@ApiOperation(value="获取任务列表")
@GetMapping("/get_task_list")
public Result<Pager<Task>> get_task_list(@RequestParam Integer page,
@RequestParam Integer size,
HttpServletRequest request){
Integer createuserid = 1;
Pager<Task> res = taskService.get_task_list(page,size,createuserid);
return R.success(res);
}
// @RequestMapping(value = "/start_task", method = RequestMethod.GET)
public Result startAsyncTask() {
//调用任务管理器中的submit去提交一个异步任务
String taskId = UUID.randomUUID().toString();
Task task = new Task();
task.setTaskId(taskId);
task.setStatus(0);
task.setMsg("开始执行任务!");
task.setInfo("");
task.setInfoType(0);
task.setProgress(0);
task.setCreateuserid(1);
task.setUpdatetime(DatePattern.getnow());
if(taskService.insert_task(task)){
Task task1 = asyncTaskManager.submit(() -> {
System.out.println("_____start_____");
try {
//模拟异步,睡眠6秒
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(taskId);
System.out.println("_____end_____");
}, taskId);
if(task1 == null){
return R.error("异步任务创建失败!");
}
return R.success(task1);
};
return R.error("异步任务创建失败!");
}
@RequestMapping(value = "/get_task", method = RequestMethod.GET)
public Result getTaskStatus(
@RequestParam("taskId") String taskId) {
return R.success(asyncTaskManager.getTaskInfo(taskId));
}
@GetMapping("/{filename}")
@ApiOperation(value="读取excel")
public void getexcel(@PathVariable(name = "filename") String filename, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
BufferedInputStream bis = null;
OutputStream out = null;
try
{
InputStream inputStream = CodeUtil.getExcel(filename);
out = response.getOutputStream();
bis = new BufferedInputStream(inputStream);
byte[] buff = new byte[1024 * 2];
int count = -1;
out = response.getOutputStream(); //直接下载导出
while ((count = bis.read(buff)) != -1)
{
out.write(buff, 0, count);
}
out.flush();
out.close();
} catch (IOException e)
{
} finally
{
if (out != null)
{
try
{
out.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
}
}
tools里面的Result.java就是一个

package com.src.xxx.tools;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value="通用返回结构体",description = "http请求返回的最外层对象")
public class Result<T> {
@ApiModelProperty(value="错误码0成功1失败401不是超级管理员",name="code")
private Integer code;
@ApiModelProperty(value="提示信息",name="msg")
private String msg;
@ApiModelProperty(value="具体的内容",name="data")
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
R.java 就是定一个的一个常用方法之类的工具
package com.src.xxx.tools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static org.springframework.web.context.request.RequestContextHolder.getRequestAttributes;
@Slf4j
public class R {
public static Result success(Object object) {
Result result = new Result();
result.setCode(0);
result.setMsg("success");
result.setData(object);
return result;
}
public static Result notadmin() {
Result result = new Result();
result.setCode(401);
result.setMsg("无权限");
return result;
}
public static Result success() {
return success(null);
}
public static Result error(String msg) {
Result result = new Result();
result.setCode(1);
result.setMsg(msg);
return result;
}
}
主要代码如上,其中一些业务逻辑相关的例如登录验证这些都去掉了。临时把导出的数据表改成了task的表。
见笑了。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)