在实际项目中需要开发一个处理资源编译请求的接口:接受前台页面上传的ymal文件,后台根据模板生成scala文件,然后打包、构建docker镜像。
文件上传和后台处理过程的实现都不难,只是整个过程是比较耗时的,这里如果使用同步方式,那么上传-->模板解析-->打包-->构建镜像-->返回结果;这个过程,前台的页面都是等待状态的,用户会以为页面卡死了。所以,这里需要做异步处理:
1.上传-->返回正在编译的标志;
2.模板解析-->打包-->构建镜像-->存储编译结果;
此时,当用户上传完文件后,页面立马跳转,模板解析和镜像构建等工作,继续在后台进行,而用户可以不用等待。
由于整个接口服务采用springboot实现,这里简单记录一种springBoot的异步使用方式,
这种方式,是springBoot自身的一种异步方式,使用注解实现,非常方便,我们在想要异步执行的方法上加上@Async注解,在controller上加上@EnableAsync即可。注意这里的异步方法,只能在自身之外调用,在本类调用是无效的。
controller
@RequestMapping(value = "/XXX/xxx")
@RestController
@EnableAsync
public class CompileController {
private static final Logger log = LoggerFactory.getLogger(CompileController.class);
private final static int MZX_SIZE = 51200000;
@Autowired
private CompileRecordRepository recordRepository;
@Autowired
private BackendService backend;
@Value("${build.resource-dir}")
private String resourceDir;
/**
* 编译服务
*
* @param name
* @param version
* @param namespace
* @param file
* @return
*/
@RequestMapping(value = "/compile", method = RequestMethod.POST)
public String compile(String name, String version, String namespace, @RequestParam("yaml") MultipartFile file) {
if (file.isEmpty()) {
return Response.error("编译失败,因为文件是空的.");
}
if (file.getSize() > MZX_SIZE) {
return Response.error("编译失败,文件大小超過限制");
}
String fileName = file.getOriginalFilename().toLowerCase();
log.info("fileName:" + fileName);
if (!fileName.endsWith(".yml")) {
return Response.error("must upload yml format file");
}
String token = TokenUtil.generateToken();
CompileLog compileLog = new CompileLog();
compileLog.setId(token);
compileLog.setName(name);
compileLog.setNamespace(namespace);
compileLog.setToken(token);
compileLog.setVersion(version);
compileLog.setCreateTime(System.currentTimeMillis());
compileLog.setUpdateTime(System.currentTimeMillis());
compileLog.setStatus(0);
recordRepository.save(compileLog);
//读取文件内容并写到本地
String ymlStr = readAndWrite2Local(file);
//后台异步执行
backend.execute(compileLog, ymlStr);
return Response.Builder
.success()
.setMsg("正在编译,可根据token查询编译结果")
.appendData("token", token)
.build();
}
}
serviceImpl
@Component
public class BackendService {
private static final Logger log = LoggerFactory.getLogger(BackendService.class);
@Autowired
CompileRecordRepository recordRepository;
@Value("${build.resource-dir}")
String resourceDir;
private final static String SCALA_FILE = "";
@Async
public String execute(CompileLog compileLog, String ymlStr) {
//模板代码目录
String capTemplateDir = resourceDir + "/cap";
String capInstanceDir = resourceDir + "/capInstance/" + compileLog.getToken();
FileUtil.mkdir(capInstanceDir);
capInstanceDir += "/cap";
//创建实例代码目录
FileUtil.copyDir(capTemplateDir, capInstanceDir);
log.info(Thread.currentThread().getName() + " start rendering...");
TemplateService.templateRender(ymlStr, capInstanceDir + SCALA_FILE);
this.pack(capInstanceDir, compileLog);
this.image(capInstanceDir, compileLog);
log.info(Thread.currentThread().getName() + " update record...");
compileLog.setStatus(CompileStatus.SUCCESS);
compileLog.setUpdateTime(System.currentTimeMillis());
recordRepository.save(compileLog);
log.info(Thread.currentThread().getName() + " execute finished.");
return "执行异步任务完毕";
}
/**
* 打成jar包
*/
private void pack(String capDir, CompileLog record) {
log.info("start package jar...");
}
/**
* 生成镜像
*/
private void image(String capDir, CompileLog record) {
log.info("start build docker image...");
}
}
执行结果,略。。。。。。
很多情况下,异步处理是一种很常见而且高效的方式,springBoot自带的注解方式只用两个注解即可实现,简单易用。当然除此之外还有其他的实现方式,例如可以通过创建线程池来实现。