This commit is contained in:
skyyemperor 2022-04-09 21:44:03 +08:00
當前提交 34e481f0a5
共有 26 個檔案被更改,包括 1693 行新增0 行删除

35
.gitignore vendored Normal file
查看文件

@ -0,0 +1,35 @@
src/main/resources/application.yml
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

135
pom.xml Normal file
查看文件

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.weilab</groupId>
<artifactId>biology</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>biology</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.19</version>
</dependency>
</dependencies>
<build>
<finalName>biology</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

查看文件

@ -0,0 +1,13 @@
package com.weilab.biology;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BiologyApplication {
public static void main(String[] args) {
SpringApplication.run(BiologyApplication.class, args);
}
}

查看文件

@ -0,0 +1,33 @@
package com.weilab.biology.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import javax.servlet.DispatcherType;
import java.util.HashMap;
import java.util.Map;
/**
* Created by skyyemperor on 2020-12-27 10:54
* Description :
*/
@Configuration
public class FilterConfig {
@Autowired
private TokenFilter tokenFilter;
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean
public FilterRegistrationBean tokenFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(tokenFilter);
registration.addUrlPatterns("/*");
registration.setName("tokenFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registration;
}
}

查看文件

@ -0,0 +1,33 @@
package com.weilab.biology.config;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setCharacterEncoding("UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Expose-Headers", "*");
response.setHeader("Access-Control-Max-Age", "36000");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(200);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}

查看文件

@ -0,0 +1,124 @@
package com.weilab.biology.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.weilab.biology.core.data.enums.JobStatusEnum;
import com.weilab.biology.core.data.vo.result.CommonError;
import com.weilab.biology.core.data.vo.result.Result;
import com.weilab.biology.core.data.vo.result.error.JobError;
import com.weilab.biology.core.validation.EnumValidation;
import com.weilab.biology.service.JobService;
import com.weilab.biology.util.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("job")
public class JobController {
@Autowired
private JobService jobService;
@Value("${biology.request-path}")
private String requestPath;
@PostMapping("/submit")
public Result submit(@RequestParam(required = false) String dataStr,
@RequestParam(required = false) MultipartFile dataFile,
@RequestParam String param,
@RequestParam String mail,
@RequestParam(defaultValue = "0") Integer type,
@RequestParam(required = false) MultipartFile file1,
@RequestParam(required = false) MultipartFile file2,
@RequestParam(required = false) MultipartFile file3,
@RequestParam(required = false) MultipartFile file4,
@RequestParam(required = false) MultipartFile file5,
@RequestParam(required = false) MultipartFile file6,
@RequestParam(required = false) MultipartFile file7,
@RequestParam(required = false) MultipartFile file8,
@RequestParam(required = false) MultipartFile file9,
@RequestParam(required = false) MultipartFile file10,
@RequestParam(required = false) MultipartFile file11,
@RequestParam(required = false) MultipartFile file12,
@RequestParam(required = false) MultipartFile file13,
@RequestParam(required = false) MultipartFile file14,
@RequestParam(required = false) MultipartFile file15,
@RequestParam(required = false) MultipartFile file16,
@RequestParam(required = false) MultipartFile file17,
@RequestParam(required = false) MultipartFile file18,
@RequestParam(required = false) MultipartFile file19,
@RequestParam(required = false) MultipartFile file20) {
if (dataFile == null && StringUtils.isBlank(dataStr))
return Result.getResult(JobError.PARAM_CAN_NOT_BE_EMPTY);
JSONObject obj = null;
try {
obj = JSON.parseObject(param);
} catch (Exception e) {
return Result.getResult(CommonError.PARAM_WRONG);
}
BufferedInputStream dataStream = null;
if (dataFile != null) {
dataStream = FileUtil.getInputStream(FileUtils.multipartToFile(dataFile));
}
try {
List<MultipartFile> files = Arrays.asList(file1, file2, file3, file4, file5,
file6, file7, file8, file9, file10, file11, file12, file13, file14, file15,
file16, file17, file18, file19, file20);
for (MultipartFile file : files) {
if (file != null) {
String filePath = requestPath + FileUtil.FILE_SEPARATOR + "file" + FileUtil.FILE_SEPARATOR + IdUtil.fastUUID() + "." + FileUtil.extName(file.getOriginalFilename());
FileUtil.writeFromStream(file.getInputStream(), filePath);
obj.put(file.getName(), filePath);
}
}
} catch (IOException e) {
e.printStackTrace();
return Result.getResult(JobError.FILE_READ_FAIL);
}
return jobService.submit(dataStr, dataStream, obj, mail, type);
}
@PostMapping("/status/update")
public Result updateJobStatus(@RequestParam Integer jobId,
@EnumValidation(clazz = JobStatusEnum.class, message = "没有此状态")
@RequestParam Integer status,
@RequestParam(required = false) String result) {
if (status.equals(JobStatusEnum.WAIT.getKey()))
return Result.getResult(CommonError.PARAM_WRONG);
if (status.equals(JobStatusEnum.SUCCESS.getKey())) {
if (StringUtils.isBlank(result))
return Result.getResult(CommonError.PARAM_WRONG);
try {
JSON.parseObject(result);
} catch (Exception e) {
return Result.getResult(CommonError.PARAM_WRONG);
}
}
return jobService.updateJobStatus(jobId, JobStatusEnum.getEnumByKey(status), result);
}
@GetMapping("/info/{jobId}")
public Result getJobInfo(@PathVariable Integer jobId) {
return jobService.getJobInfo(jobId);
}
@GetMapping("/list")
public Result getJobList(@RequestParam(required = false) Integer type) {
return jobService.getJobList(type);
}
}

查看文件

@ -0,0 +1,78 @@
package com.weilab.biology.core.data.dto;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.weilab.biology.core.data.enums.JobStatusEnum;
import com.weilab.biology.core.data.po.Job;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JobDto {
/**
* jobId
*/
private Integer jobId;
/**
* 任务状态
*/
private String status;
/**
* 请求参数
*/
private JSONObject param;
/**
* 运行结果
*/
private JSONObject result;
/**
* 请求时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime requestTime;
/**
* 创建时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 完成时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime completeTime;
/**
* 请求类型
*/
private Integer type;
public static JobDto parseJob(Job job) {
return new JobDto(
job.getJobId(),
JobStatusEnum.getRemark(job.getStatus()),
JSON.parseObject(job.getParam()),
job.getResult() == null ? null : JSON.parseObject(job.getResult()),
job.getRequestTime(),
job.getCreateTime(),
job.getCompleteTime(),
job.getType()
);
}
}

查看文件

@ -0,0 +1,64 @@
package com.weilab.biology.core.data.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.weilab.biology.core.data.enums.JobStatusEnum;
import com.weilab.biology.core.data.po.Job;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* Created by skyyemperor on 2021-09-22
* Description :
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JobLessDto {
/**
* jobId
*/
private Integer jobId;
/**
* 任务状态
*/
private String status;
/**
* 请求时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime requestTime;
/**
* 创建时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 完成时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime completeTime;
/**
* 请求类型
*/
private Integer type;
public static JobLessDto parseJob(Job job) {
return new JobLessDto(
job.getJobId(),
JobStatusEnum.getRemark(job.getStatus()),
job.getRequestTime(),
job.getCreateTime(),
job.getCompleteTime(),
job.getType()
);
}
}

查看文件

@ -0,0 +1,42 @@
package com.weilab.biology.core.data.enums;
import lombok.Getter;
/**
* 校区的枚举类
*/
@Getter
public enum JobStatusEnum {
WAIT(0, "waiting"),
REQED(3, "requested"),
RUNNING(1, "running"),
SUCCESS(2, "success"),
FAIL(-1, "failed"),
TIMEOUT(-2, "timeout"),
;
private final Integer key;
private final String remark;
private JobStatusEnum(Integer key, String remark) {
this.key = key;
this.remark = remark;
}
public static String getRemark(Integer key) {
for (JobStatusEnum enums : JobStatusEnum.values()) {
if (enums.key.equals(key))
return enums.getRemark();
}
return null;
}
public static JobStatusEnum getEnumByKey(Integer key) {
for (JobStatusEnum enums : JobStatusEnum.values()) {
if (enums.key.equals(key))
return enums;
}
return null;
}
}

查看文件

@ -0,0 +1,97 @@
package com.weilab.biology.core.data.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import jnr.ffi.annotations.In;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Created by skyyemperor on 2021-09-19
* Description :
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "job")
public class Job implements Serializable {
/**
* jobId
*/
@TableId(value = "job_id", type = IdType.AUTO)
private Integer jobId;
/**
* 基因序列数据
*/
@TableField(value = "`data`")
private String data;
/**
* 请求参数
*/
@TableField(value = "param")
private String param;
/**
* 联系邮箱
*/
@TableField(value = "mail")
private String mail;
/**
* 运行结果
*/
@TableField(value = "result")
private String result;
/**
* 任务状态0为待运行1为正在运行2为运行成功-1为运行失败
*/
@TableField(value = "`status`")
private Integer status;
/**
* 请求时间
*/
@TableField(value = "request_time")
private LocalDateTime requestTime;
/**
* 创建时间
*/
@TableField(value = "create_time")
private LocalDateTime createTime;
/**
* 完成时间
*/
@TableField(value = "complete_time")
private LocalDateTime completeTime;
/**
* 请求类型
*/
@TableField(value = "type")
private Integer type;
public Job(String data, String param, String mail, Integer status, LocalDateTime requestTime, Integer type) {
this.data = data;
this.param = param;
this.mail = mail;
this.status = status;
this.requestTime = requestTime;
this.type = type;
}
private static final long serialVersionUID = 1L;
}

查看文件

@ -0,0 +1,43 @@
package com.weilab.biology.core.data.vo.result;
/**
* Created by skyyemperor on 2021-01-30
* Description : 通用异常返回
*/
public enum CommonError implements ResultError {
PARAM_WRONG(40000, "参数范围或格式错误"),
NETWORK_WRONG(40001, "网络错误"),
REQUEST_NOT_ALLOW(40002, "当前条件或时间不允许〒▽〒"),
REQUEST_FREQUENTLY(40003, "请求繁忙,请稍后再试"),
CONTENT_NOT_FOUND(40004, "你要找的东西好像走丢啦XX"),
METHOD_NOT_ALLOW(40005, "方法不允许"),
THIS_IS_LAST_PAGE(40006, "这是最后一页,再怎么找也没有啦"),
THIS_IS_FIRST_PAGE(40007, "没有上一页啦"),
PIC_FORMAT_ERROR(40008, "图片格式只能为jpg, jpeg, png, gif, bmp, webp"),
;
private int code;
private String message;
private CommonError(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

查看文件

@ -0,0 +1,96 @@
package com.weilab.biology.core.data.vo.result;
/**
* web层统一返回类型
*/
public class Result {
private int code = 0;
private String message;
private Object data;
public Result() {
}
public Result(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public Result(ResultError resultError) {
this.code = resultError.getCode();
this.message = resultError.getMessage();
}
public static Result getResult(int code, String message) {
return getResult(code, message, null);
}
public static Result getResult(int code, String message, Object data) {
return new Result(code, message, data);
}
public static Result success() {
return success(null);
}
public static Result success(Object data) {
return success("success", data);
}
public static Result success(String message, Object data) {
return new Result(0, message, data);
}
public static Result fail() {
return fail(null);
}
public static Result fail(Object data) {
return fail("请求失败", data);
}
public static Result fail(String message, Object data) {
return new Result(-1, message, data);
}
public static Result getResult(ResultError resultError) {
return getResult(resultError.getCode(), resultError.getMessage());
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Info{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}

查看文件

@ -0,0 +1,13 @@
package com.weilab.biology.core.data.vo.result;
/**
* Created by skyyemperor on 2021-01-30
* Description : 通用异常返回的父接口
*/
public interface ResultError {
int getCode();
String getMessage();
}

查看文件

@ -0,0 +1,40 @@
package com.weilab.biology.core.data.vo.result.error;
import com.weilab.biology.core.data.vo.result.ResultError;
/**
* Created by skyyemperor on 2021-09-19
* Description :
*/
public enum JobError implements ResultError {
PARAM_CAN_NOT_BE_EMPTY(40100, "文本框和文件不能同时为空"),
FILE_READ_FAIL(40101, "文件读取出错"),
STATUS_UPDATE_FAIL(40102,"当前状态下不允许更新为指定状态"),
;
private int code;
private String message;
private JobError(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

查看文件

@ -0,0 +1,52 @@
package com.weilab.biology.core.exception;
import com.weilab.biology.core.data.vo.result.ResultError;
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code;
private String message;
private Object data;
public BaseException(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public BaseException(int code, String message) {
this(code, message, null);
}
public BaseException(ResultError error) {
this(error.getCode(), error.getMessage(), null);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}

查看文件

@ -0,0 +1,119 @@
package com.weilab.biology.core.exception;
import com.weilab.biology.core.data.vo.result.CommonError;
import com.weilab.biology.core.data.vo.result.Result;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import javax.validation.ConstraintViolationException;
import java.util.List;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 自定义异常捕获
*/
@ExceptionHandler(BaseException.class)
public Result handleBaseException(BaseException e) {
return Result.getResult(e.getCode(), e.getMessage(), e.getData());
}
/**
* 服务器错误常见有数据库错误
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
e.printStackTrace();
return Result.fail();
}
/**
* JSON解析异常最常见NullPointerException这里均将其过滤
*/
@ExceptionHandler({NullPointerException.class})
public Result handleJSONException(Exception e){
return Result.getResult(CommonError.REQUEST_NOT_ALLOW);
}
/**
* 参数校验错误异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result validationBodyException(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
String message = CommonError.PARAM_WRONG.getMessage();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
if (errors.size() > 0) {
FieldError fieldError = (FieldError) errors.get(0);
message = fieldError.getDefaultMessage();
}
}
return Result.getResult(CommonError.PARAM_WRONG.getCode(), message);
}
/**
* 参数类型转换错误
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public Result methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
return Result.getResult(CommonError.PARAM_WRONG.getCode(),
e.getName() + "参数类型转换错误");
}
/**
* 参数转换JSON出错
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result httpMessageNotReadableException(HttpMessageNotReadableException e) {
return Result.getResult(CommonError.PARAM_WRONG);
}
/**
* 请求方法不允许
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result methodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return Result.getResult(CommonError.METHOD_NOT_ALLOW.getCode(),
e.getMethod() + "方法不允许");
}
/**
* 缺少请求参数
*/
@ExceptionHandler({MissingServletRequestParameterException.class, MissingServletRequestPartException.class})
public Result missingParameterException(Exception e) {
String message = CommonError.PARAM_WRONG.getMessage();
if (e instanceof MissingServletRequestParameterException) {
message = ((MissingServletRequestParameterException) e).getParameterName() + "不能为空";
} else if (e instanceof MissingServletRequestPartException) {
message = ((MissingServletRequestPartException) e).getRequestPartName() + "不能为空";
}
return Result.getResult(CommonError.PARAM_WRONG.getCode(), message);
}
/**
* 请求参数格式错误
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result ConstraintViolationException(ConstraintViolationException e) {
if (e.getConstraintViolations().size() > 0) {
return Result.getResult(CommonError.PARAM_WRONG.getCode(),
e.getConstraintViolations().iterator().next().getMessageTemplate());
}
return Result.getResult(CommonError.PARAM_WRONG);
}
}

查看文件

@ -0,0 +1,13 @@
package com.weilab.biology.core.exception;
import com.weilab.biology.core.data.vo.result.CommonError;
/**
* Created by skyyemperor on 2021-04-30
* Description : 网络异常
*/
public class NetworkException extends BaseException {
public NetworkException() {
super(CommonError.NETWORK_WRONG);
}
}

查看文件

@ -0,0 +1,60 @@
package com.weilab.biology.core.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumValidation.List.class)
@Constraint(validatedBy = {EnumValidator.class})
public @interface EnumValidation {
String message() default "{*.validation.constraint.Enum.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* the enum's class-type
*
* @return Class
*/
Class<?> clazz();
/**
* the method's name ,which used to validate the enum's value
*
* @return method's name
*/
String method() default "getKey";
/**
* 是否允许为空
*
* @return true or false
*/
boolean allowNull() default true;
/**
* Defines several {@link EnumValidation} annotations on the same element.
*
* @see EnumValidation
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@interface List {
EnumValidation[] value();
}
}

查看文件

@ -0,0 +1,63 @@
package com.weilab.biology.core.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Method;
/**
* Controller入参对象中属性枚举项校验
*/
public class EnumValidator implements ConstraintValidator<EnumValidation, Object> {
private EnumValidation annotation;
/**
* Initializes the validator in preparation for
* {@link #isValid(Object, ConstraintValidatorContext)} calls.
* The constraint annotation for a given constraint declaration
* is passed.
* <p>
* This method is guaranteed to be called before any use of this instance for
* validation.
* <p>
* The default implementation is a no-op.
*
* @param constraintAnnotation annotation instance for a given constraint declaration
*/
@Override
public void initialize(EnumValidation constraintAnnotation) {
this.annotation = constraintAnnotation;
}
/**
* Implements the validation logic.
* The state of {@code value} must not be altered.
* <p>
* This method can be accessed concurrently, thread-safety must be ensured
* by the implementation.
*
* @param value object to validate
* @param context context in which the constraint is evaluated
* @return {@code false} if {@code value} does not pass the constraint
*/
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return annotation.allowNull();
}
Object[] objects = annotation.clazz().getEnumConstants();
try {
Method method = annotation.clazz().getMethod(annotation.method());
for (Object o : objects) {
if (value.equals(method.invoke(o))) {
return true;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
}

查看文件

@ -0,0 +1,26 @@
package com.weilab.biology.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.weilab.biology.core.data.dto.JobLessDto;
import com.weilab.biology.core.data.po.Job;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.python.modules.itertools.count;
import java.util.List;
/**
* Created by skyyemperor on 2021-09-19
* Description :
*/
@Mapper
public interface JobMapper extends BaseMapper<Job> {
List<Job> selectRunningJobs();
Job selectNextWaitingJob();
List<Job> selectJobList(@Param("type") Integer type,
@Param("count") Integer count);
}

查看文件

@ -0,0 +1,242 @@
package com.weilab.biology.service;
import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.weilab.biology.core.data.dto.JobDto;
import com.weilab.biology.core.data.dto.JobLessDto;
import com.weilab.biology.core.data.enums.JobStatusEnum;
import com.weilab.biology.core.data.po.Job;
import com.weilab.biology.core.data.vo.result.CommonError;
import com.weilab.biology.core.data.vo.result.Result;
import com.weilab.biology.core.data.vo.result.error.JobError;
import com.weilab.biology.mapper.JobMapper;
import com.weilab.biology.util.FileUtils;
import com.weilab.biology.util.MailUtil;
import com.weilab.biology.util.TaskExecutorUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.BufferedInputStream;
import java.io.File;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Created by skyyemperor on 2021-09-19
* Description :
*/
@Service
@Slf4j
public class JobService {
@Value("${biology.python-cmd}")
private String pythonCmd;
@Value("${biology.request-path}")
private String requestPath;
@Value("${biology.result-path}")
private String resultDataPath;
@Value("${biology.log-path}")
private String logPath;
@Value("${biology.concurrent-num}")
private Integer concurrentNum;
@Autowired
private JobMapper jobMapper;
@Autowired
private MailUtil mailUtil;
@Autowired
private TaskExecutorUtil<?> taskExecutorUtil;
private static final String SUBJECT = "【DeepBIO Result Notice】";
private static final String SUCCESS_EMAIL_CONTENT = "Your request has been completed, click http://server.wei-group.net/front/biology/#/resultMail?jobId=%s to check the detail information";
private static final String FAIL_EMAIL_CONTENT = "We are very sorry, but some errors occurred in the task you submitted, click http://server.wei-group.net/front/biology/#/resultMail?jobId=%s to check the detail information";
private static final String TIMEOUT_EMAIL_CONTENT = "We are very sorry, but the task you submitted was overtime, click http://server.wei-group.net/front/biology/#/resultMail?jobId=%s to check the detail information";
private static final String RECEIVED_EMAIL_CONTENT = "Your request has been received, click http://server.wei-group.net/front/biology/#/resultMail?jobId=%s to check the detail information";
private static final String START_RUNNING_EMAIL_CONTENT = "Your task has started running, click http://server.wei-group.net/front/biology/#/resultMail?jobId=%s to check the detail information";
/**
* 提交job
*
* @param dataStr 文本内容
* @param param 其他参数(json格式)
* @param mail 邮箱
* @return
* @throws Exception
*/
@Transactional
public Result submit(String dataStr, BufferedInputStream dataStream, JSONObject param, String mail, Integer type) {
Job job = new Job("", JSON.toJSONString(param), mail, JobStatusEnum.WAIT.getKey(), LocalDateTime.now(), type);
jobMapper.insert(job);
sendEmail(job.getJobId(), JobStatusEnum.WAIT, mail);
try {
//将请求数据写入本地文件之后向python传递文件路径参数
String dataPath = String.format(requestPath + File.separator + "job-%d-dataStr.txt", job.getJobId());
if (dataStream != null) {
FileUtil.writeFromStream(dataStream, dataPath);
} else {
FileUtils.writeStringToFile(dataPath, dataStr);
}
//将jobId和dataPath参数添加至param中
param.put("jobId", job.getJobId());
param.put("requestDataPath", dataPath);
param.put("resultDataPath", resultDataPath);
//更新数据库param字段
job.setParam(JSON.toJSONString(param));
jobMapper.updateById(job);
runNextJob();
} catch (Exception e) {
e.printStackTrace();
updateJobStatus(job.getJobId(), JobStatusEnum.FAIL);
}
return getJobInfo(job.getJobId());
}
public Result getJobInfo(Integer jobId) {
Job job = jobMapper.selectById(jobId);
if (job == null)
return Result.getResult(CommonError.CONTENT_NOT_FOUND);
return Result.success(JobDto.parseJob(job));
}
public Result updateJobStatus(Integer jobId, JobStatusEnum status) {
return updateJobStatus(jobId, status, null);
}
public Result updateJobStatus(Integer jobId, JobStatusEnum status, String result) {
System.out.println(status.getRemark());
Job job = jobMapper.selectById(jobId);
if (job == null)
return Result.getResult(CommonError.CONTENT_NOT_FOUND);
switch (status) {
case WAIT:
break;
case SUCCESS:
job.setCompleteTime(LocalDateTime.now());
job.setResult(result);
break;
case FAIL:
case TIMEOUT:
if (!job.getStatus().equals(JobStatusEnum.RUNNING.getKey())
&& !job.getStatus().equals(JobStatusEnum.REQED.getKey()))
return Result.getResult(JobError.STATUS_UPDATE_FAIL);
job.setCompleteTime(LocalDateTime.now());
break;
case RUNNING:
if (!job.getStatus().equals(JobStatusEnum.REQED.getKey()))
return Result.getResult(JobError.STATUS_UPDATE_FAIL);
job.setCreateTime(LocalDateTime.now());
break;
}
job.setStatus(status.getKey());
jobMapper.updateById(job);
sendEmail(jobId, status, job.getMail());
runNextJob();
return getJobInfo(jobId);
}
public Result getJobList(Integer type) {
List<Job> jobs = jobMapper.selectJobList(type, 200);
return Result.success(jobs.stream().map(JobLessDto::parseJob).collect(Collectors.toList()));
}
/**
* 运行下一个job
*/
private synchronized void runNextJob() {
Job nextJob = null;
try {
//并发数小于concurrentNum运行该job
if (jobMapper.selectRunningJobs().size() < concurrentNum) {
if ((nextJob = jobMapper.selectNextWaitingJob()) != null) {
//更新job状态
nextJob.setStatus(JobStatusEnum.REQED.getKey());
jobMapper.updateById(nextJob);
waitRunning(nextJob.getJobId());
String logFilePath = String.format(logPath + File.separator + "task-log-%s.txt", nextJob.getJobId());
String cmd = String.format("%s -setting '%s' >> %s 2>&1", pythonCmd, nextJob.getParam(), logFilePath);
log.info("执行命令: " + cmd);
String[] cmds = new String[]{"/bin/sh", "-c", cmd};
Runtime.getRuntime().exec(cmds);
}
}
} catch (Exception e) {
e.printStackTrace();
if (nextJob != null) {
nextJob.setStatus(JobStatusEnum.FAIL.getKey());
jobMapper.updateById(nextJob);
}
}
}
/**
* 等待运行超时时间60秒
*/
private void waitRunning(Integer jobId) {
//等待60秒检查是否已运行
taskExecutorUtil.schedule(() -> {
Job job = jobMapper.selectById(jobId);
if (job.getStatus().equals(JobStatusEnum.REQED.getKey())) {
updateJobStatus(jobId, JobStatusEnum.FAIL);
}
}, 60, TimeUnit.SECONDS);
//等待4小时查看是否执行完成
taskExecutorUtil.schedule(() -> {
Job job = jobMapper.selectById(jobId);
if (job.getStatus().equals(JobStatusEnum.RUNNING.getKey())) {
updateJobStatus(jobId, JobStatusEnum.TIMEOUT);
}
}, 4, TimeUnit.HOURS);
}
private void sendEmail(Integer jobId, JobStatusEnum status, String mail) {
String content = null;
switch (status) {
case WAIT:
content = String.format(RECEIVED_EMAIL_CONTENT, jobId);
break;
case SUCCESS:
content = String.format(SUCCESS_EMAIL_CONTENT, jobId);
break;
case FAIL:
content = String.format(FAIL_EMAIL_CONTENT, jobId);
break;
case TIMEOUT:
content = String.format(TIMEOUT_EMAIL_CONTENT, jobId);
break;
case RUNNING:
content = String.format(START_RUNNING_EMAIL_CONTENT, jobId);
break;
}
if (content != null)
mailUtil.send(mail, SUBJECT, content);
}
}

查看文件

@ -0,0 +1,118 @@
package com.weilab.biology.util;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
/**
* 文件读取工具类
*/
public class FileUtils {
/**
* MultipartFile 转换成File
*
* @param multfile 原文件类型
* @return File
*/
public static File multipartToFile(MultipartFile multfile) {
File file = null;
try {
file = File.createTempFile("prefix", "_" + multfile.getOriginalFilename());
multfile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
/**
* 读取文件内容作为字符串返回
*/
public static String readFileAsString(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException(filePath);
}
return readFileAsString(file);
}
/**
* 读取文件内容作为字符串返回
*/
public static String readFileAsString(File file) throws IOException {
StringBuilder sb = new StringBuilder((int) (file.length()));
// 创建字节输入流
FileInputStream fis = new FileInputStream(file);
// 创建一个长度为10240的Buffer
byte[] bbuf = new byte[10240];
// 用于保存实际读取的字节数
int hasRead = 0;
while ((hasRead = fis.read(bbuf)) > 0) {
sb.append(new String(bbuf, 0, hasRead));
}
fis.close();
return sb.toString();
}
/**
* 根据文件路径读取byte[] 数组
*/
public static byte[] readFileAsBytes(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException(filePath);
} else {
return readFileAsBytes(file);
}
}
/**
* 根据文件读取byte[]数组
*/
public static byte[] readFileAsBytes(File file) throws IOException {
if (file == null) {
throw new FileNotFoundException();
} else {
ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
short bufSize = 1024;
byte[] buffer = new byte[bufSize];
int len1;
while (-1 != (len1 = in.read(buffer, 0, bufSize))) {
bos.write(buffer, 0, len1);
}
return bos.toByteArray();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException var14) {
var14.printStackTrace();
}
bos.close();
}
}
}
public static void writeStringToFile(String filePath, String content) throws IOException {
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filePath));
bufferedOutputStream.write(content.getBytes());
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
public static void writeByteToFile(String filePath, byte[] content) throws IOException {
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filePath));
bufferedOutputStream.write(content);
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}

查看文件

@ -0,0 +1,39 @@
package com.weilab.biology.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
@Slf4j
public class MailUtil {
@Autowired
private JavaMailSenderImpl mailSender;
@Value("${spring.mail.username}")
private String sender;
@Autowired
private TaskExecutorUtil<?> taskExecutorUtil;
public void send(String receiver, String subject, String content) {
taskExecutorUtil.run(() -> {
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject(subject);//设置标题
message.setText(content);//设置内容
message.setTo(receiver);
message.setFrom(sender);
mailSender.send(message);
log.info("Mail已发送: " + message);
});
}
}

查看文件

@ -0,0 +1,43 @@
package com.weilab.biology.util;
import org.springframework.stereotype.Component;
import java.util.concurrent.*;
@Component
public class TaskExecutorUtil<C> {
public TaskExecutorUtil() {
}
private static final ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
};
private static final ThreadPoolExecutor cachePool = new ThreadPoolExecutor(
2, 40,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
factory
);
private static final ScheduledExecutorService scheduledThreadPool =
Executors.newScheduledThreadPool(5);
public void run(Runnable r) {
cachePool.execute(r);
}
public Future<C> submit(Callable<C> c) {
return cachePool.submit(c);
}
public void schedule(Runnable runnable, long delay, TimeUnit timeUnit) {
scheduledThreadPool.schedule(runnable, delay, timeUnit);
}
}

查看文件

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.weilab.biology.mapper.JobMapper">
<resultMap id="BaseResultMap" type="com.weilab.biology.core.data.po.Job">
<id column="job_id" jdbcType="INTEGER" property="jobId"/>
<result column="data" jdbcType="LONGVARCHAR" property="data"/>
<result column="param" jdbcType="VARCHAR" property="param"/>
<result column="mail" jdbcType="VARCHAR" property="mail"/>
<result column="result" jdbcType="LONGVARCHAR" property="result"/>
<result column="status" jdbcType="TINYINT" property="status"/>
<result column="request_time" jdbcType="TIMESTAMP" property="requestTime"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="complete_time" jdbcType="TIMESTAMP" property="completeTime"/>
<result column="type" jdbcType="INTEGER" property="type"/>
</resultMap>
<sql id="JobSql">
SELECT job_id,
`data`,
param,
mail,
result,
`status`,
request_time,
create_time,
complete_time,
type
FROM job
</sql>
<sql id="JobLessSql">
SELECT job_id,
`status`,
request_time,
create_time,
complete_time,
type
FROM job
</sql>
<select id="selectRunningJobs" resultMap="BaseResultMap">
<include refid="JobSql"/>
WHERE status = '${@com.weilab.biology.core.data.enums.JobStatusEnum@RUNNING.getKey()}'
OR status = '${@com.weilab.biology.core.data.enums.JobStatusEnum@REQED.getKey()}'
</select>
<select id="selectNextWaitingJob" resultMap="BaseResultMap">
<include refid="JobSql"/>
WHERE status = '${@com.weilab.biology.core.data.enums.JobStatusEnum@WAIT.getKey()}'
ORDER BY request_time
LIMIT 1
</select>
<select id="selectJobList" resultMap="BaseResultMap">
<include refid="JobLessSql"/>
<where>
<if test="type != null">
AND type = #{type}
</if>
</where>
ORDER BY job_id DESC
LIMIT #{count}
</select>
</mapper>

查看文件

@ -0,0 +1,13 @@
package com.weilab.biology;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BiologyApplicationTests {
@Test
void contextLoads() {
}
}