Compare commits
12 Commits
niuyu_bran
...
develop
| Author | SHA1 | Date |
|---|---|---|
|
|
2e05194e46 | 4 months ago |
|
|
2034d33d3c | 4 months ago |
|
|
188e1c9f39 | 4 months ago |
|
|
bc8d2b9a67 | 4 months ago |
|
|
603ffbf5c6 | 4 months ago |
|
|
267e7f7b89 | 4 months ago |
|
|
beb41e7eac | 4 months ago |
|
|
c367bf7a4c | 4 months ago |
|
|
a6fc072dc4 | 4 months ago |
|
|
b6ebb5367b | 4 months ago |
|
|
2f7c6c3182 | 4 months ago |
|
|
4af2c14ac3 | 4 months ago |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
java -jar exam-api.jar --spring.config.location=application-local.yml
|
||||
@ -1 +0,0 @@
|
||||
java -jar exam-api.jar --spring.config.location=application-local.yml
|
||||
@ -1,10 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# 依赖于环境的 Maven 主目录路径
|
||||
/mavenHomeManager.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 管理项目的JDK配置及编译输出路径 -->
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,214 @@
|
||||
<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 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.yfhl</groupId>
|
||||
<artifactId>exam-api</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0</version>
|
||||
<name>exam-api</name>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.4.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<fastjson.version>2.0.24</fastjson.version>
|
||||
<oss.version>3.7.0</oss.version>
|
||||
<aliyun.sdk.version>4.1.1</aliyun.sdk.version>
|
||||
<swagger.version>2.9.2</swagger.version>
|
||||
<dozer.version>5.5.1</dozer.version>
|
||||
<apache.commons.version>3.8</apache.commons.version>
|
||||
<mysql.driver.version>8.0.11</mysql.driver.version>
|
||||
<mybatis-plus.version>3.4.1</mybatis-plus.version>
|
||||
<lombok.version>1.18.4</lombok.version>
|
||||
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
|
||||
<alicloud.version>2.1.1.RELEASE</alicloud.version>
|
||||
<poi.version>3.9</poi.version>
|
||||
<log4j2.version>2.17.2</log4j2.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- WEB支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--spring quartz依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-quartz</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.9.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sf.dozer</groupId>
|
||||
<artifactId>dozer</artifactId>
|
||||
<version>${dozer.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.driver.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>swagger-bootstrap-ui</artifactId>
|
||||
<version>1.9.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- poi office -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml-schemas</artifactId>
|
||||
<version>${poi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--JWT-->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>3.7.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Shiro -->
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring-boot-starter</artifactId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>1.2.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.11.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<finalName>${project.name}</finalName>
|
||||
<defaultGoal>compile</defaultGoal>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.1</version>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@ -0,0 +1,53 @@
|
||||
package com.yf.exam;
|
||||
|
||||
import com.yf.exam.core.api.utils.JsonConverter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 云帆在线考试系统
|
||||
* @author bool
|
||||
* @email 18365918@qq.com
|
||||
* @date 2020-03-04 19:41
|
||||
*/
|
||||
@Log4j2
|
||||
@SpringBootApplication
|
||||
public class ExamApplication implements WebMvcConfigurer {
|
||||
|
||||
public static void main(String[] args) throws UnknownHostException {
|
||||
ConfigurableApplicationContext application = SpringApplication.run(ExamApplication.class, args);
|
||||
Environment env = application.getEnvironment();
|
||||
String ip = InetAddress.getLocalHost().getHostAddress();
|
||||
String port = env.getProperty("server.port");
|
||||
String path = env.getProperty("server.servlet.context-path");
|
||||
|
||||
// 未配置默认空白
|
||||
if(path == null){
|
||||
path = "";
|
||||
}
|
||||
|
||||
|
||||
log.info("\n----------------------------------------------------------\n\t" +
|
||||
"云帆考试系统启动成功,访问路径如下:\n\t" +
|
||||
"本地路径: \t\thttp://localhost:" + port + path + "/\n\t" +
|
||||
"网络地址: \thttp://" + ip + ":" + port + path + "/\n\t" +
|
||||
"API文档: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
|
||||
"----------------------------------------------------------");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
//保留原有converter,把新增fastConverter插入集合头,保证优先级
|
||||
converters.add(0, JsonConverter.fastConverter());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.yf.exam.ability;
|
||||
|
||||
|
||||
/**
|
||||
* 通用常量
|
||||
* @author bool
|
||||
*/
|
||||
public class Constant {
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传路径
|
||||
*/
|
||||
public static final String FILE_PREFIX = "/upload/file/";
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.yf.exam.ability.job.enums;
|
||||
|
||||
/**
|
||||
* 任务分组
|
||||
* @author van
|
||||
*/
|
||||
public interface JobGroup {
|
||||
|
||||
/**
|
||||
* 系统任务
|
||||
*/
|
||||
String SYSTEM = "system";
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.yf.exam.ability.job.enums;
|
||||
|
||||
/**
|
||||
* 任务前缀
|
||||
* @author bool
|
||||
*/
|
||||
public interface JobPrefix {
|
||||
|
||||
/**
|
||||
* 强制交卷的
|
||||
*/
|
||||
String BREAK_EXAM = "break_exam_";
|
||||
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
package com.yf.exam.ability.job.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.yf.exam.ability.job.enums.JobGroup;
|
||||
import com.yf.exam.ability.job.service.JobService;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.quartz.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author bool
|
||||
*/
|
||||
@Log4j2
|
||||
@Service
|
||||
public class JobServiceImpl implements JobService {
|
||||
|
||||
/**
|
||||
* Quartz定时任务核心的功能实现类
|
||||
*/
|
||||
private Scheduler scheduler;
|
||||
|
||||
/**
|
||||
* 注入
|
||||
* @param schedulerFactoryBean
|
||||
*/
|
||||
public JobServiceImpl(@Autowired SchedulerFactoryBean schedulerFactoryBean) {
|
||||
scheduler = schedulerFactoryBean.getScheduler();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addCronJob(Class jobClass, String jobName, String cron, String data) {
|
||||
|
||||
|
||||
String jobGroup = JobGroup.SYSTEM;
|
||||
|
||||
// 自动命名
|
||||
if(StringUtils.isEmpty(jobName)){
|
||||
jobName = jobClass.getSimpleName().toUpperCase() + "_"+IdWorker.getIdStr();
|
||||
}
|
||||
|
||||
try {
|
||||
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
|
||||
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
||||
if (jobDetail != null) {
|
||||
log.info("++++++++++任务:{} 已存在", jobName);
|
||||
this.deleteJob(jobName, jobGroup);
|
||||
}
|
||||
|
||||
log.info("++++++++++构建任务:{},{},{},{},{} ", jobClass.toString(), jobName, jobGroup, cron, data);
|
||||
|
||||
//构建job信息
|
||||
jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
|
||||
//用JopDataMap来传递数据
|
||||
jobDetail.getJobDataMap().put(TASK_DATA, data);
|
||||
|
||||
//按新的cronExpression表达式构建一个新的trigger
|
||||
Trigger trigger = null;
|
||||
|
||||
// 有表达式的按表达式
|
||||
if(!StringUtils.isEmpty(cron)){
|
||||
log.info("+++++表达式执行:"+ JSON.toJSONString(jobDetail));
|
||||
//表达式调度构建器
|
||||
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
|
||||
trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
|
||||
}else{
|
||||
// 无表达式则立即执行
|
||||
log.info("+++++立即执行:"+ JSON.toJSONString(jobDetail));
|
||||
trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().build();
|
||||
}
|
||||
|
||||
scheduler.scheduleJob(jobDetail, trigger);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addCronJob(Class jobClass, String jobName, String data) {
|
||||
// 立即执行任务
|
||||
this.addCronJob(jobClass, jobName, null, data);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pauseJob(String jobName, String jobGroup) {
|
||||
try {
|
||||
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
|
||||
scheduler.pauseTrigger(triggerKey);
|
||||
log.info("++++++++++暂停任务:{}", jobName);
|
||||
} catch (SchedulerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resumeJob(String jobName, String jobGroup) {
|
||||
try {
|
||||
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
|
||||
scheduler.resumeTrigger(triggerKey);
|
||||
log.info("++++++++++重启任务:{}", jobName);
|
||||
} catch (SchedulerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteJob(String jobName, String jobGroup) {
|
||||
try {
|
||||
JobKey jobKey = JobKey.jobKey(jobName,jobGroup);
|
||||
scheduler.deleteJob(jobKey);
|
||||
log.info("++++++++++删除任务:{}", jobKey);
|
||||
} catch (SchedulerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.yf.exam.ability.shiro.jwt;
|
||||
|
||||
import lombok.Data;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
|
||||
/**
|
||||
* @author bool
|
||||
*/
|
||||
@Data
|
||||
public class JwtToken implements AuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* JWT的字符token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
|
||||
public JwtToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.yf.exam.ability.upload.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传配置
|
||||
* @author van
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "conf.upload")
|
||||
public class UploadConfig {
|
||||
|
||||
/**
|
||||
* 访问路径
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 物理目录
|
||||
*/
|
||||
private String dir;
|
||||
|
||||
/**
|
||||
* 允许的后缀
|
||||
*/
|
||||
private String [] allowExtensions;
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.yf.exam.ability.upload.dto;
|
||||
|
||||
|
||||
import com.yf.exam.core.api.dto.BaseDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 文件上传请求类
|
||||
* @author
|
||||
* @date 2019-12-26 17:54
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="文件上传参数", description="文件上传参数")
|
||||
public class UploadReqDTO extends BaseDTO {
|
||||
|
||||
@ApiModelProperty(value = "上传文件内容", required=true)
|
||||
private MultipartFile file;
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.yf.exam.ability.upload.dto;
|
||||
|
||||
import com.yf.exam.core.api.dto.BaseDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 上传文件结果
|
||||
* @author bool
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ApiModel(value="文件上传响应", description="文件上传响应")
|
||||
public class UploadRespDTO extends BaseDTO {
|
||||
|
||||
@ApiModelProperty(value = "上传后的完整的URL地址", required=true)
|
||||
private String url;
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.yf.exam.ability.upload.service;
|
||||
|
||||
import com.yf.exam.ability.upload.dto.UploadReqDTO;
|
||||
import com.yf.exam.ability.upload.dto.UploadRespDTO;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 阿里云OSS业务类
|
||||
* @author bool
|
||||
* @date 2019-07-12 16:45
|
||||
*/
|
||||
public interface UploadService {
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
UploadRespDTO upload(UploadReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
void download(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
package com.yf.exam.ability.upload.service.impl;
|
||||
|
||||
import com.yf.exam.ability.Constant;
|
||||
import com.yf.exam.ability.upload.config.UploadConfig;
|
||||
import com.yf.exam.ability.upload.dto.UploadReqDTO;
|
||||
import com.yf.exam.ability.upload.dto.UploadRespDTO;
|
||||
import com.yf.exam.ability.upload.service.UploadService;
|
||||
import com.yf.exam.ability.upload.utils.FileUtils;
|
||||
import com.yf.exam.core.exception.ServiceException;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传业务类
|
||||
* @author bool
|
||||
* @date 2019-07-30 21:02
|
||||
*/
|
||||
@Log4j2
|
||||
@Service
|
||||
public class UploadServiceImpl implements UploadService {
|
||||
|
||||
@Autowired
|
||||
private UploadConfig conf;
|
||||
|
||||
@Override
|
||||
public UploadRespDTO upload(UploadReqDTO reqDTO) {
|
||||
|
||||
|
||||
// 文件内容
|
||||
MultipartFile file = reqDTO.getFile();
|
||||
|
||||
// 验证文件后缀
|
||||
boolean allow = FilenameUtils.isExtension(file.getOriginalFilename(), conf.getAllowExtensions());
|
||||
if(!allow){
|
||||
throw new ServiceException("文件类型不允许上传!");
|
||||
}
|
||||
// 上传文件夹
|
||||
String fileDir = conf.getDir();
|
||||
// 真实物理地址
|
||||
String fullPath;
|
||||
try {
|
||||
|
||||
// 新文件
|
||||
String filePath = FileUtils.processPath(file);
|
||||
// 文件保存地址
|
||||
fullPath = fileDir + filePath;
|
||||
// 创建文件夹
|
||||
FileUtils.checkDir(fullPath);
|
||||
// 上传文件
|
||||
FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(fullPath));
|
||||
|
||||
return this.generateResult(filePath);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new ServiceException("文件上传失败:"+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void download(HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
// 获取真实的文件路径
|
||||
String filePath = this.getRealPath(request.getRequestURI());
|
||||
|
||||
// 处理中文问题
|
||||
try {
|
||||
filePath = URLDecoder.decode(filePath, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
System.out.println("++++完整路径为:"+filePath);
|
||||
|
||||
try {
|
||||
FileUtils.writeRange(request, response, filePath);
|
||||
} catch (IOException e) {
|
||||
response.setStatus(404);
|
||||
log.error("预览文件失败" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造返回
|
||||
* @param fileName
|
||||
* @return
|
||||
*/
|
||||
private UploadRespDTO generateResult(String fileName) {
|
||||
|
||||
//获取加速域名
|
||||
String domain = conf.getUrl();
|
||||
|
||||
// 返回结果
|
||||
return new UploadRespDTO(domain + fileName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取真实物理文件地址
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public String getRealPath(String uri){
|
||||
|
||||
String regx = Constant.FILE_PREFIX+"(.*)";
|
||||
|
||||
// 查找全部变量
|
||||
Pattern pattern = Pattern.compile(regx);
|
||||
Matcher m = pattern.matcher(uri);
|
||||
if (m.find()) {
|
||||
String str = m.group(1);
|
||||
return conf.getDir() + str;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package com.yf.exam.aspect.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlCommandType;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.plugin.Intercepts;
|
||||
import org.apache.ibatis.plugin.Invocation;
|
||||
import org.apache.ibatis.plugin.Plugin;
|
||||
import org.apache.ibatis.plugin.Signature;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 自动给创建时间个更新时间加值
|
||||
* @author bool
|
||||
*/
|
||||
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
|
||||
public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private static final String CREATE_TIME = "createTime";
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private static final String UPDATE_TIME = "updateTime";
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
|
||||
// SQL操作命令
|
||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||
// 获取新增或修改的对象参数
|
||||
Object parameter = invocation.getArgs()[1];
|
||||
// 获取对象中所有的私有成员变量(对应表字段)
|
||||
Field[] declaredFields = parameter.getClass().getDeclaredFields();
|
||||
if (parameter.getClass().getSuperclass() != null) {
|
||||
Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields();
|
||||
declaredFields = ArrayUtils.addAll(declaredFields, superField);
|
||||
}
|
||||
|
||||
String fieldName = null;
|
||||
for (Field field : declaredFields) {
|
||||
fieldName = field.getName();
|
||||
if (Objects.equals(CREATE_TIME, fieldName)) {
|
||||
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
|
||||
field.setAccessible(true);
|
||||
field.set(parameter, new Timestamp(System.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
if (Objects.equals(UPDATE_TIME, fieldName)) {
|
||||
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
|
||||
field.setAccessible(true);
|
||||
field.set(parameter, new Timestamp(System.currentTimeMillis()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin(Object target) {
|
||||
if (target instanceof Executor) {
|
||||
return Plugin.wrap(target, this);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Properties properties) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
package com.yf.exam.aspect.utils;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.yf.exam.core.api.ApiError;
|
||||
import com.yf.exam.core.api.ApiRest;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 注入工具类
|
||||
* @author bool
|
||||
* @date 2019-07-17 09:32
|
||||
*/
|
||||
@Log4j2
|
||||
@Component
|
||||
public class InjectUtils {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 给对象字段赋值
|
||||
*
|
||||
* @param object 赋值的对象
|
||||
* @param value 值
|
||||
* @param fields 字段
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public void setValue(Object object, Object value, String... fields) throws Exception {
|
||||
|
||||
//设置同类的属性
|
||||
for (String fieldName : fields) {
|
||||
|
||||
//获取当前
|
||||
Field field = this.getFiled(object.getClass(), fieldName);
|
||||
if(field == null){
|
||||
continue;
|
||||
}
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(object, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名对应的字段
|
||||
*
|
||||
* @param clazz 目标类
|
||||
* @param fieldName 字段名
|
||||
*/
|
||||
private Field getFiled(Class clazz, String fieldName) {
|
||||
|
||||
System.out.println("注入的类:"+clazz.toString());
|
||||
|
||||
//是否具有包含关系
|
||||
try {
|
||||
//获取当前类的属性
|
||||
return clazz.getDeclaredField(fieldName);
|
||||
}catch (Exception e){
|
||||
|
||||
log.error(clazz.toString() + ": not exist field, try superclass " + fieldName);
|
||||
|
||||
//如果为空且存在父类,则往上找
|
||||
if(clazz.getSuperclass()!=null){
|
||||
return this.getFiled(clazz.getSuperclass(), fieldName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 打印结果返回
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void restError(HttpServletResponse response) {
|
||||
|
||||
try {
|
||||
|
||||
//固定错误
|
||||
ApiRest apiRest = new ApiRest(ApiError.ERROR_10010002);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.getWriter().write(JSON.toJSONString(apiRest));
|
||||
response.getWriter().close();
|
||||
|
||||
}catch (IOException e){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.yf.exam.config;
|
||||
|
||||
import org.springframework.boot.web.servlet.MultipartConfigFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import javax.servlet.MultipartConfigElement;
|
||||
|
||||
/**
|
||||
* 文件上传配置
|
||||
* @author bool
|
||||
* @date 2019-07-29 16:23
|
||||
*/
|
||||
@Configuration
|
||||
public class MultipartConfig {
|
||||
|
||||
@Bean
|
||||
public MultipartConfigElement multipartConfigElement() {
|
||||
MultipartConfigFactory factory = new MultipartConfigFactory();
|
||||
// 单个数据大小
|
||||
factory.setMaxFileSize(DataSize.ofMegabytes(5000L));
|
||||
/// 总上传数据大小
|
||||
factory.setMaxRequestSize(DataSize.ofMegabytes(5000L));
|
||||
return factory.createMultipartConfig();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.yf.exam.config;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 任务调度配置
|
||||
* @author bool
|
||||
*/
|
||||
@Log4j2
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
public class ScheduledConfig implements SchedulingConfigurer, AsyncConfigurer {
|
||||
|
||||
/**
|
||||
* 定时任务使用的线程池
|
||||
* @return
|
||||
*/
|
||||
@Bean(destroyMethod = "shutdown", name = "taskScheduler")
|
||||
public ThreadPoolTaskScheduler taskScheduler(){
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.setPoolSize(10);
|
||||
scheduler.setThreadNamePrefix("task-");
|
||||
scheduler.setAwaitTerminationSeconds(600);
|
||||
scheduler.setWaitForTasksToCompleteOnShutdown(true);
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步任务执行线程池
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = "asyncExecutor")
|
||||
public ThreadPoolTaskExecutor asyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setQueueCapacity(1000);
|
||||
executor.setKeepAliveSeconds(600);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setThreadNamePrefix("taskExecutor-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
|
||||
ThreadPoolTaskScheduler taskScheduler = taskScheduler();
|
||||
scheduledTaskRegistrar.setTaskScheduler(taskScheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
return asyncExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return (throwable, method, objects) -> {
|
||||
log.error("异步任务执行出现异常, message {}, emthod {}, params {}", throwable, method, objects);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.yf.exam.config;
|
||||
|
||||
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.ApiKey;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.service.SecurityScheme;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Swagger配置
|
||||
* @author bool
|
||||
* @date 2020/8/19 20:53
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@EnableSwaggerBootstrapUI
|
||||
@ConfigurationProperties(prefix = "swagger")
|
||||
public class SwaggerConfig {
|
||||
|
||||
|
||||
@Bean
|
||||
public Docket examApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo())
|
||||
.groupName("考试模块接口")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.ant("/exam/api/**"))
|
||||
.build()
|
||||
.securitySchemes(Collections.singletonList(securityScheme()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder().title("考试系统接口")
|
||||
.description("考试系统接口")
|
||||
.contact(new Contact("Van", "https://exam.yfhl.net", "18365918@qq.com"))
|
||||
.version("1.0.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 授权头部
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
SecurityScheme securityScheme() {
|
||||
return new ApiKey("token", "token", "header");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.yf.exam.core.annon;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 数据字典注解
|
||||
* @author bool
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Dict {
|
||||
|
||||
String dicCode();
|
||||
|
||||
String dicText() default "";
|
||||
|
||||
String dictTable() default "";
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.yf.exam.core.api;
|
||||
|
||||
|
||||
import com.yf.exam.core.api.ApiError;
|
||||
import com.yf.exam.core.exception.ServiceException;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 数据结果返回的封装
|
||||
* @author bool
|
||||
* @date 2018/11/20 09:48
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@ApiModel(value="接口响应", description="接口响应")
|
||||
public class ApiRest<T>{
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
@ApiModelProperty(value = "响应消息")
|
||||
private String msg;
|
||||
/**
|
||||
* 响应代码
|
||||
*/
|
||||
@ApiModelProperty(value = "响应代码,0为成功,1为失败", required = true)
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 请求或响应body
|
||||
*/
|
||||
@ApiModelProperty(value = "响应内容")
|
||||
protected T data;
|
||||
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
* @return
|
||||
*/
|
||||
public boolean isSuccess(){
|
||||
return code.equals(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param error
|
||||
*/
|
||||
public ApiRest(ServiceException error){
|
||||
this.code = error.getCode();
|
||||
this.msg = error.getMsg();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param error
|
||||
*/
|
||||
public ApiRest(ApiError error){
|
||||
this.code = error.getCode();
|
||||
this.msg = error.msg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.yf.exam.core.api.dto;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分页查询类
|
||||
* @param <T>
|
||||
* @author bool
|
||||
*/
|
||||
@ApiModel(value="分页参数", description="分页参数")
|
||||
@Data
|
||||
public class PagingReqDTO<T> {
|
||||
|
||||
|
||||
@ApiModelProperty(value = "当前页码", required = true, example = "1")
|
||||
private Integer current;
|
||||
|
||||
@ApiModelProperty(value = "每页数量", required = true, example = "10")
|
||||
private Integer size;
|
||||
|
||||
@ApiModelProperty(value = "查询参数")
|
||||
private T params;
|
||||
|
||||
@ApiModelProperty(value = "排序字符")
|
||||
private String orderBy;
|
||||
|
||||
@JsonIgnore
|
||||
@ApiModelProperty(value = "当前用户的ID")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 转换成MyBatis的简单分页对象
|
||||
* @return
|
||||
*/
|
||||
public Page toPage(){
|
||||
Page page = new Page();
|
||||
page.setCurrent(this.current);
|
||||
page.setSize(this.size);
|
||||
return page;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.yf.exam.core.api.dto;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
/**
|
||||
* 分页响应类
|
||||
* @author bool
|
||||
* @date 2019-07-20 15:17
|
||||
* @param <T>
|
||||
*/
|
||||
public class PagingRespDTO<T> extends Page<T> {
|
||||
|
||||
/**
|
||||
* 获取页面总数量
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public long getPages() {
|
||||
if (this.getSize() == 0L) {
|
||||
return 0L;
|
||||
} else {
|
||||
long pages = this.getTotal() / this.getSize();
|
||||
if (this.getTotal() % this.getSize() != 0L) {
|
||||
++pages;
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.yf.exam.core.enums;
|
||||
|
||||
/**
|
||||
* 开放方式
|
||||
* @author bool
|
||||
*/
|
||||
public interface OpenType {
|
||||
|
||||
/**
|
||||
* 完全开放
|
||||
*/
|
||||
Integer OPEN = 1;
|
||||
|
||||
/**
|
||||
* 部门开放
|
||||
*/
|
||||
Integer DEPT_OPEN = 2;
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.yf.exam.core.exception;
|
||||
|
||||
import com.yf.exam.core.api.ApiError;
|
||||
import com.yf.exam.core.api.ApiRest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ServiceException extends RuntimeException{
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 从结果初始化
|
||||
* @param apiRest
|
||||
*/
|
||||
public ServiceException(ApiRest apiRest){
|
||||
this.code = apiRest.getCode();
|
||||
this.msg = apiRest.getMsg();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从枚举中获取参数
|
||||
* @param apiError
|
||||
*/
|
||||
public ServiceException(ApiError apiError){
|
||||
this.code = apiError.getCode();
|
||||
this.msg = apiError.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常构造
|
||||
* @param msg
|
||||
*/
|
||||
public ServiceException(String msg){
|
||||
this.code = 1;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package com.yf.exam.core.utils;
|
||||
|
||||
import org.dozer.DozerBeanMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 简单封装Dozer, 实现深度转换Bean<->Bean的Mapper.实现:
|
||||
*
|
||||
* 1. 持有Mapper的单例.
|
||||
* 2. 返回值类型转换.
|
||||
* 3. 批量转换Collection中的所有对象.
|
||||
* 4. 区分创建新的B对象与将对象A值复制到已存在的B对象两种函数.
|
||||
*
|
||||
*/
|
||||
public class BeanMapper {
|
||||
|
||||
/**
|
||||
* 持有Dozer单例, 避免重复创建DozerMapper消耗资源.
|
||||
*/
|
||||
private static DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
|
||||
|
||||
/**
|
||||
* 基于Dozer转换对象的类型.
|
||||
*/
|
||||
public static <T> T map(Object source, Class<T> destinationClass) {
|
||||
return dozerBeanMapper.map(source, destinationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于Dozer转换Collection中对象的类型.
|
||||
*/
|
||||
public static <T> List<T> mapList(Iterable<?> sourceList, Class<T> destinationClass) {
|
||||
List<T> destinationList = new ArrayList();
|
||||
for (Object sourceObject : sourceList) {
|
||||
T destinationObject = dozerBeanMapper.map(sourceObject, destinationClass);
|
||||
destinationList.add(destinationObject);
|
||||
}
|
||||
return destinationList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于Dozer将对象A的值拷贝到对象B中.
|
||||
*/
|
||||
public static void copy(Object source, Object destinationObject) {
|
||||
if(source!=null) {
|
||||
dozerBeanMapper.map(source, destinationObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, S> List<T> mapList(Collection<S> source, Function<? super S, ? extends T> mapper) {
|
||||
return source.stream().map(mapper).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.yf.exam.core.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 时间转换quartz表达式
|
||||
* @author bool
|
||||
* @date 2020/11/29 下午3:00
|
||||
*/
|
||||
public class CronUtils {
|
||||
|
||||
/**
|
||||
* 格式化数据
|
||||
*/
|
||||
private static final String DATE_FORMAT = "ss mm HH dd MM ? yyyy";
|
||||
|
||||
/**
|
||||
* 准确的时间点到表达式
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
public static String dateToCron(final Date date){
|
||||
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FORMAT);
|
||||
String formatTimeStr = "";
|
||||
if (date != null) {
|
||||
formatTimeStr = fmt.format(date);
|
||||
}
|
||||
return formatTimeStr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.yf.exam.core.utils;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Spring获取工具
|
||||
*
|
||||
* @author bool
|
||||
* @date 2019-12-09 15:55
|
||||
*/
|
||||
@Component
|
||||
public class SpringUtils implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
applicationContext = context;
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> tClass) {
|
||||
return applicationContext.getBean(tClass);
|
||||
}
|
||||
|
||||
public static <T> T getBean(String name, Class<T> type) {
|
||||
return applicationContext.getBean(name, type);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.yf.exam.core.utils.file;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
|
||||
/**
|
||||
* MD5工具类
|
||||
* ClassName: MD5Util <br/>
|
||||
* date: 2018年1月13日 下午6:54:53 <br/>
|
||||
*
|
||||
* @author Bool
|
||||
* @version
|
||||
*/
|
||||
public class Md5Util {
|
||||
|
||||
|
||||
/**
|
||||
* 简单MD5
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
public static String md5(String str) {
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] array = md.digest(str.getBytes("UTF-8"));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte item : array) {
|
||||
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
|
||||
}
|
||||
return sb.toString();
|
||||
}catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.yf.exam.core.utils.passwd;
|
||||
|
||||
/**
|
||||
* 密码实体
|
||||
* ClassName: PassInfo <br/>
|
||||
* date: 2018年2月13日 下午7:13:50 <br/>
|
||||
*
|
||||
* @author Bool
|
||||
* @version
|
||||
*/
|
||||
public class PassInfo {
|
||||
|
||||
//密码随机串码
|
||||
private String salt;
|
||||
|
||||
//MD5后的密码
|
||||
private String password;
|
||||
|
||||
public PassInfo(String salt, String password) {
|
||||
super();
|
||||
this.salt = salt;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
public void setSalt(String salt) {
|
||||
this.salt = salt;
|
||||
}
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
package com.yf.exam.modules;
|
||||
|
||||
/**
|
||||
* 通用常量
|
||||
* @author bool
|
||||
*/
|
||||
public class Constant {
|
||||
|
||||
/**
|
||||
* 会话
|
||||
*/
|
||||
public static final String TOKEN = "token";
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
package com.yf.exam.modules.exam.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.yf.exam.core.api.ApiRest;
|
||||
import com.yf.exam.core.api.controller.BaseController;
|
||||
import com.yf.exam.core.api.dto.BaseIdReqDTO;
|
||||
import com.yf.exam.core.api.dto.BaseIdsReqDTO;
|
||||
import com.yf.exam.core.api.dto.BaseStateReqDTO;
|
||||
import com.yf.exam.core.api.dto.PagingReqDTO;
|
||||
import com.yf.exam.modules.exam.dto.ExamDTO;
|
||||
import com.yf.exam.modules.exam.dto.request.ExamSaveReqDTO;
|
||||
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
|
||||
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
|
||||
import com.yf.exam.modules.exam.entity.Exam;
|
||||
import com.yf.exam.modules.exam.service.ExamService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 考试控制器
|
||||
* </p>
|
||||
*
|
||||
* @author 聪明笨狗
|
||||
* @since 2020-07-25 16:18
|
||||
*/
|
||||
@Api(tags={"考试"})
|
||||
@RestController
|
||||
@RequestMapping("/exam/api/exam/exam")
|
||||
public class ExamController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ExamService baseService;
|
||||
|
||||
/**
|
||||
* 添加或修改
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@RequiresRoles("sa")
|
||||
@ApiOperation(value = "添加或修改")
|
||||
@RequestMapping(value = "/save", method = { RequestMethod.POST})
|
||||
public ApiRest save(@RequestBody ExamSaveReqDTO reqDTO) {
|
||||
//复制参数
|
||||
baseService.save(reqDTO);
|
||||
return super.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@RequiresRoles("sa")
|
||||
@ApiOperation(value = "批量删除")
|
||||
@RequestMapping(value = "/delete", method = { RequestMethod.POST})
|
||||
public ApiRest edit(@RequestBody BaseIdsReqDTO reqDTO) {
|
||||
//根据ID删除
|
||||
baseService.removeByIds(reqDTO.getIds());
|
||||
return super.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找详情
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "查找详情")
|
||||
@RequestMapping(value = "/detail", method = { RequestMethod.POST})
|
||||
public ApiRest<ExamSaveReqDTO> find(@RequestBody BaseIdReqDTO reqDTO) {
|
||||
ExamSaveReqDTO dto = baseService.findDetail(reqDTO.getId());
|
||||
return super.success(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找详情
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@RequiresRoles("sa")
|
||||
@ApiOperation(value = "查找详情")
|
||||
@RequestMapping(value = "/state", method = { RequestMethod.POST})
|
||||
public ApiRest state(@RequestBody BaseStateReqDTO reqDTO) {
|
||||
|
||||
QueryWrapper<Exam> wrapper = new QueryWrapper<>();
|
||||
wrapper.lambda().in(Exam::getId, reqDTO.getIds());
|
||||
Exam exam = new Exam();
|
||||
exam.setState(reqDTO.getState());
|
||||
exam.setUpdateTime(new Date());
|
||||
|
||||
baseService.update(exam, wrapper);
|
||||
return super.success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分页查找
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@ApiOperation(value = "考试视角")
|
||||
@RequestMapping(value = "/online-paging", method = { RequestMethod.POST})
|
||||
public ApiRest<IPage<ExamOnlineRespDTO>> myPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
|
||||
|
||||
//分页查询并转换
|
||||
IPage<ExamOnlineRespDTO> page = baseService.onlinePaging(reqDTO);
|
||||
return super.success(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查找
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@RequiresRoles("sa")
|
||||
@ApiOperation(value = "分页查找")
|
||||
@RequestMapping(value = "/paging", method = { RequestMethod.POST})
|
||||
public ApiRest<IPage<ExamDTO>> paging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
|
||||
|
||||
//分页查询并转换
|
||||
IPage<ExamDTO> page = baseService.paging(reqDTO);
|
||||
|
||||
return super.success(page);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分页查找
|
||||
* @param reqDTO
|
||||
* @return
|
||||
*/
|
||||
@RequiresRoles("sa")
|
||||
@ApiOperation(value = "待阅试卷")
|
||||
@RequestMapping(value = "/review-paging", method = { RequestMethod.POST})
|
||||
public ApiRest<IPage<ExamReviewRespDTO>> reviewPaging(@RequestBody PagingReqDTO<ExamDTO> reqDTO) {
|
||||
//分页查询并转换
|
||||
IPage<ExamReviewRespDTO> page = baseService.reviewPaging(reqDTO);
|
||||
return super.success(page);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package com.yf.exam.modules.exam.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.yf.exam.modules.paper.enums.ExamState;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 考试数据传输类
|
||||
* </p>
|
||||
*
|
||||
* @author 聪明笨狗
|
||||
* @since 2020-07-25 16:18
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="考试", description="考试")
|
||||
public class ExamDTO implements Serializable {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "ID", required=true)
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty(value = "考试名称", required=true)
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty(value = "考试描述", required=true)
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty(value = "1公开2部门3定员", required=true)
|
||||
private Integer openType;
|
||||
|
||||
@ApiModelProperty(value = "考试状态", required=true)
|
||||
private Integer state;
|
||||
|
||||
@ApiModelProperty(value = "是否限时", required=true)
|
||||
private Boolean timeLimit;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@ApiModelProperty(value = "开始时间", required=true)
|
||||
private Date startTime;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
@ApiModelProperty(value = "结束时间", required=true)
|
||||
private Date endTime;
|
||||
|
||||
@ApiModelProperty(value = "创建时间", required=true)
|
||||
private Date createTime;
|
||||
|
||||
@ApiModelProperty(value = "更新时间", required=true)
|
||||
private Date updateTime;
|
||||
|
||||
@ApiModelProperty(value = "总分数", required=true)
|
||||
private Integer totalScore;
|
||||
|
||||
@ApiModelProperty(value = "总时长(分钟)", required=true)
|
||||
private Integer totalTime;
|
||||
|
||||
@ApiModelProperty(value = "及格分数", required=true)
|
||||
private Integer qualifyScore;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 是否结束
|
||||
* @return
|
||||
*/
|
||||
public Integer getState(){
|
||||
|
||||
if(this.timeLimit!=null && this.timeLimit){
|
||||
|
||||
if(System.currentTimeMillis() < startTime.getTime() ){
|
||||
return ExamState.READY_START;
|
||||
}
|
||||
|
||||
if(System.currentTimeMillis() > endTime.getTime()){
|
||||
return ExamState.OVERDUE;
|
||||
}
|
||||
|
||||
if(System.currentTimeMillis() > startTime.getTime()
|
||||
&& System.currentTimeMillis() < endTime.getTime()
|
||||
&& !ExamState.DISABLED.equals(this.state)){
|
||||
return ExamState.ENABLE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.yf.exam.modules.exam.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 考试部门数据传输类
|
||||
* </p>
|
||||
*
|
||||
* @author 聪明笨狗
|
||||
* @since 2020-09-03 17:24
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="考试部门", description="考试部门")
|
||||
public class ExamDepartDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "ID", required=true)
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty(value = "考试ID", required=true)
|
||||
private String examId;
|
||||
|
||||
@ApiModelProperty(value = "部门ID", required=true)
|
||||
private String departId;
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.yf.exam.modules.exam.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 考试题库数据传输类
|
||||
* </p>
|
||||
*
|
||||
* @author 聪明笨狗
|
||||
* @since 2020-09-05 11:14
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="考试题库", description="考试题库")
|
||||
public class ExamRepoDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "ID", required=true)
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty(value = "考试ID", required=true)
|
||||
private String examId;
|
||||
|
||||
@ApiModelProperty(value = "题库ID", required=true)
|
||||
private String repoId;
|
||||
|
||||
@ApiModelProperty(value = "单选题数量", required=true)
|
||||
private Integer radioCount;
|
||||
|
||||
@ApiModelProperty(value = "单选题分数", required=true)
|
||||
private Integer radioScore;
|
||||
|
||||
@ApiModelProperty(value = "多选题数量", required=true)
|
||||
private Integer multiCount;
|
||||
|
||||
@ApiModelProperty(value = "多选题分数", required=true)
|
||||
private Integer multiScore;
|
||||
|
||||
@ApiModelProperty(value = "判断题数量", required=true)
|
||||
private Integer judgeCount;
|
||||
|
||||
@ApiModelProperty(value = "判断题分数", required=true)
|
||||
private Integer judgeScore;
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.yf.exam.modules.exam.dto.ext;
|
||||
|
||||
import com.yf.exam.modules.exam.dto.ExamRepoDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 考试题库数据传输类
|
||||
* </p>
|
||||
*
|
||||
* @author 聪明笨狗
|
||||
* @since 2020-09-05 11:14
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类")
|
||||
public class ExamRepoExtDTO extends ExamRepoDTO {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "单选题总量", required=true)
|
||||
private Integer totalRadio;
|
||||
|
||||
@ApiModelProperty(value = "多选题总量", required=true)
|
||||
private Integer totalMulti;
|
||||
|
||||
@ApiModelProperty(value = "判断题总量", required=true)
|
||||
private Integer totalJudge;
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.yf.exam.modules.exam.dto.request;
|
||||
|
||||
import com.yf.exam.modules.exam.dto.ExamDTO;
|
||||
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 考试保存请求类
|
||||
* </p>
|
||||
*
|
||||
* @author 聪明笨狗
|
||||
* @since 2020-07-25 16:18
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="考试保存请求类", description="考试保存请求类")
|
||||
public class ExamSaveReqDTO extends ExamDTO {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "题库列表", required=true)
|
||||
private List<ExamRepoExtDTO> repoList;
|
||||
|
||||
@ApiModelProperty(value = "考试部门列表", required=true)
|
||||
private List<String> departIds;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue