代码初始化
This commit is contained in:
parent
991c6d988e
commit
e043412446
211
exam-admin/pom.xml
Normal file
211
exam-admin/pom.xml
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.example</groupId>
|
||||||
|
<artifactId>exam-admin</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.1.4.RELEASE</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<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>
|
||||||
44
exam-admin/src/main/java/com/yf/exam/ExamApplication.java
Normal file
44
exam-admin/src/main/java/com/yf/exam/ExamApplication.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package com.yf.exam;
|
||||||
|
|
||||||
|
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.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云帆在线考试系统
|
||||||
|
* @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" +
|
||||||
|
"----------------------------------------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
exam-admin/src/main/java/com/yf/exam/ability/Constant.java
Normal file
15
exam-admin/src/main/java/com/yf/exam/ability/Constant.java
Normal file
@ -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,53 @@
|
|||||||
|
package com.yf.exam.ability.job.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务业务类,用于动态处理任务信息
|
||||||
|
* @author bool
|
||||||
|
* @date 2020/11/29 下午2:17
|
||||||
|
*/
|
||||||
|
public interface JobService {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务数据
|
||||||
|
*/
|
||||||
|
String TASK_DATA = "taskData";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加定时任务
|
||||||
|
* @param jobClass
|
||||||
|
* @param jobName
|
||||||
|
* @param cron
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
void addCronJob(Class jobClass, String jobName, String cron, String data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加立即执行的任务
|
||||||
|
* @param jobClass
|
||||||
|
* @param jobName
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
void addCronJob(Class jobClass, String jobName, String data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停任务
|
||||||
|
* @param jobName
|
||||||
|
* @param jobGroup
|
||||||
|
*/
|
||||||
|
void pauseJob(String jobName, String jobGroup);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复任务
|
||||||
|
* @param triggerName
|
||||||
|
* @param triggerGroup
|
||||||
|
*/
|
||||||
|
void resumeJob(String triggerName, String triggerGroup);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除job
|
||||||
|
* @param jobName
|
||||||
|
* @param jobGroup
|
||||||
|
*/
|
||||||
|
void deleteJob(String jobName, String jobGroup);
|
||||||
|
}
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
package com.yf.exam.ability.job.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
|
import com.yf.exam.ability.job.enums.JobGroup;
|
||||||
|
import com.yf.exam.ability.job.service.JobService;
|
||||||
|
import com.yf.exam.core.utils.jackson.JsonHelper;
|
||||||
|
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("+++++表达式执行:"+ JsonHelper.toJson(jobDetail));
|
||||||
|
//表达式调度构建器
|
||||||
|
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
|
||||||
|
trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
|
||||||
|
}else{
|
||||||
|
// 无表达式则立即执行
|
||||||
|
log.info("+++++立即执行:"+ JsonHelper.toJson(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,29 @@
|
|||||||
|
package com.yf.exam.ability.shiro;
|
||||||
|
|
||||||
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
|
import org.apache.shiro.web.filter.InvalidRequestFilter;
|
||||||
|
import org.apache.shiro.web.filter.mgt.DefaultFilter;
|
||||||
|
import org.apache.shiro.web.filter.mgt.FilterChainManager;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义过滤器,用于处理中文URL问题
|
||||||
|
* 如:下载文件中包含中文会返回400错误,https://youdomain.com/upload/file/云帆考试系统用户手册.pdf
|
||||||
|
* @author van
|
||||||
|
*/
|
||||||
|
public class CNFilterFactoryBean extends ShiroFilterFactoryBean {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FilterChainManager createFilterChainManager() {
|
||||||
|
FilterChainManager manager = super.createFilterChainManager();
|
||||||
|
// URL携带中文400,servletPath中文校验bug
|
||||||
|
Map<String, Filter> filterMap = manager.getFilters();
|
||||||
|
Filter invalidRequestFilter = filterMap.get(DefaultFilter.invalidRequest.name());
|
||||||
|
if (invalidRequestFilter instanceof InvalidRequestFilter) {
|
||||||
|
((InvalidRequestFilter) invalidRequestFilter).setBlockNonAscii(false);
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,131 @@
|
|||||||
|
package com.yf.exam.ability.shiro;
|
||||||
|
|
||||||
|
|
||||||
|
import com.yf.exam.ability.shiro.jwt.JwtToken;
|
||||||
|
import com.yf.exam.ability.shiro.jwt.JwtUtils;
|
||||||
|
import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
|
||||||
|
import com.yf.exam.modules.sys.user.service.SysUserRoleService;
|
||||||
|
import com.yf.exam.modules.sys.user.service.SysUserService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.apache.shiro.authc.SimpleAuthenticationInfo;
|
||||||
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
|
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||||
|
import org.apache.shiro.realm.AuthorizingRealm;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录鉴权和获取用户授权
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ShiroRealm extends AuthorizingRealm {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private SysUserService sysUserService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private SysUserRoleService sysUserRoleService;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
return token instanceof JwtToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详细授权认证
|
||||||
|
* @param principals
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||||
|
|
||||||
|
String userId = null;
|
||||||
|
if (principals != null) {
|
||||||
|
SysUserLoginDTO user = (SysUserLoginDTO) principals.getPrimaryPrincipal();
|
||||||
|
userId = user.getId();
|
||||||
|
}
|
||||||
|
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||||
|
|
||||||
|
// 查找用户角色
|
||||||
|
List<String> roles = sysUserRoleService.listRoles(userId);
|
||||||
|
info.setRoles(new HashSet<>(roles));
|
||||||
|
|
||||||
|
log.info("++++++++++校验详细权限完成");
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验用户的账号密码是否正确
|
||||||
|
* @param auth
|
||||||
|
* @return
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
|
||||||
|
String token = (String) auth.getCredentials();
|
||||||
|
if (token == null) {
|
||||||
|
throw new AuthenticationException("token为空!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验token有效性
|
||||||
|
SysUserLoginDTO user = this.checkToken(token);
|
||||||
|
return new SimpleAuthenticationInfo(user, token, getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验Token的有效性
|
||||||
|
* @param token
|
||||||
|
* @return
|
||||||
|
* @throws AuthenticationException
|
||||||
|
*/
|
||||||
|
public SysUserLoginDTO checkToken(String token) throws AuthenticationException {
|
||||||
|
|
||||||
|
// 查询用户信息
|
||||||
|
log.debug("++++++++++校验用户token: "+ token);
|
||||||
|
|
||||||
|
// 从token中获取用户名
|
||||||
|
String username = JwtUtils.getUsername(token);
|
||||||
|
log.debug("++++++++++用户名: "+ username);
|
||||||
|
|
||||||
|
if (username == null) {
|
||||||
|
throw new AuthenticationException("无效的token");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找登录用户对象
|
||||||
|
SysUserLoginDTO user = sysUserService.token(token);
|
||||||
|
|
||||||
|
// 校验token是否失效
|
||||||
|
if (!JwtUtils.verify(token, username)) {
|
||||||
|
throw new AuthenticationException("登陆失效,请重试登陆!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除当前用户的权限认证缓存
|
||||||
|
* @param principals
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearCache(PrincipalCollection principals) {
|
||||||
|
super.clearCache(principals);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package com.yf.exam.ability.shiro.aop;
|
||||||
|
|
||||||
|
|
||||||
|
import com.yf.exam.ability.shiro.jwt.JwtToken;
|
||||||
|
import com.yf.exam.aspect.utils.InjectUtils;
|
||||||
|
import com.yf.exam.modules.Constant;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鉴权登录拦截器
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class JwtFilter extends BasicHttpAuthenticationFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行登录认证
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @param mappedValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||||
|
try {
|
||||||
|
executeLogin(request, response);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 写出统一错误信息
|
||||||
|
InjectUtils.restError((HttpServletResponse) response);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
|
||||||
|
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||||
|
String token = httpServletRequest.getHeader(Constant.TOKEN);
|
||||||
|
|
||||||
|
JwtToken jwtToken = new JwtToken(token);
|
||||||
|
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
|
||||||
|
getSubject(request, response).login(jwtToken);
|
||||||
|
// 如果没有抛出异常则代表登入成功,返回true
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,99 @@
|
|||||||
|
package com.yf.exam.ability.shiro.jwt;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.JWTVerifier;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.auth0.jwt.exceptions.JWTDecodeException;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import com.yf.exam.core.utils.file.Md5Util;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT工具类
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
public class JwtUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期24小时
|
||||||
|
*/
|
||||||
|
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验是否正确
|
||||||
|
* @param token
|
||||||
|
* @param username
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean verify(String token, String username) {
|
||||||
|
try {
|
||||||
|
// 根据密码生成JWT效验器
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username));
|
||||||
|
JWTVerifier verifier = JWT.require(algorithm)
|
||||||
|
.withClaim("username", username)
|
||||||
|
.build();
|
||||||
|
// 效验TOKEN
|
||||||
|
verifier.verify(token);
|
||||||
|
return true;
|
||||||
|
} catch (Exception exception) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Token中解密获得用户名
|
||||||
|
* @param token
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getUsername(String token) {
|
||||||
|
try {
|
||||||
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
|
return jwt.getClaim("username").asString();
|
||||||
|
} catch (JWTDecodeException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JWT Token字符串
|
||||||
|
* @param username
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String sign(String username) {
|
||||||
|
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
|
||||||
|
Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username));
|
||||||
|
// 附带username信息
|
||||||
|
return JWT.create()
|
||||||
|
.withClaim("username", username)
|
||||||
|
.withExpiresAt(date).sign(algorithm);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名和秘钥,生成一个新的秘钥,用于JWT加强一些安全性
|
||||||
|
* @param userName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String encryptSecret(String userName){
|
||||||
|
|
||||||
|
// 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录
|
||||||
|
// 可自行修改此规则
|
||||||
|
Calendar cl = Calendar.getInstance();
|
||||||
|
cl.setTimeInMillis(System.currentTimeMillis());
|
||||||
|
StringBuffer sb = new StringBuffer(userName)
|
||||||
|
.append("&")
|
||||||
|
.append(cl.get(Calendar.MONTH));
|
||||||
|
|
||||||
|
// 获取MD5
|
||||||
|
String secret = Md5Util.md5(sb.toString());
|
||||||
|
|
||||||
|
return Md5Util.md5(userName + "&" + secret);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,57 @@
|
|||||||
|
package com.yf.exam.ability.upload.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.yf.exam.ability.Constant;
|
||||||
|
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.core.api.ApiRest;
|
||||||
|
import com.yf.exam.core.api.controller.BaseController;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地文件上传下载请求类
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Log4j2
|
||||||
|
@Api(tags = {"文件上传"})
|
||||||
|
@RestController
|
||||||
|
public class UploadController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UploadService uploadService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@PostMapping("/common/api/file/upload")
|
||||||
|
@ApiOperation(value = "文件上传", notes = "此接口较为特殊,参数都通过表单方式提交,而非JSON")
|
||||||
|
public ApiRest<UploadRespDTO> upload(@ModelAttribute UploadReqDTO reqDTO) {
|
||||||
|
// 上传并返回URL
|
||||||
|
UploadRespDTO respDTO = uploadService.upload(reqDTO);
|
||||||
|
return super.success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 独立文件下载
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
@GetMapping(Constant.FILE_PREFIX+"**")
|
||||||
|
@ApiOperation(value = "文件下载", notes = "文件下载")
|
||||||
|
public void download(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
uploadService.download(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,172 @@
|
|||||||
|
package com.yf.exam.ability.upload.utils;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
|
import com.yf.exam.core.utils.DateUtils;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件工具类
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后缀分割符号
|
||||||
|
*/
|
||||||
|
private static final String SUFFIX_SPLIT = ".";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持以断点的方式输出文件,提供文件在线预览和视频在线播放
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @param filePath
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void writeRange(HttpServletRequest request,
|
||||||
|
HttpServletResponse response, String filePath) throws IOException {
|
||||||
|
|
||||||
|
// 读取文件
|
||||||
|
File file = new File(filePath);
|
||||||
|
|
||||||
|
//只读模式
|
||||||
|
RandomAccessFile randomFile = new RandomAccessFile(file, "r");
|
||||||
|
long contentLength = randomFile.length();
|
||||||
|
String range = request.getHeader("Range");
|
||||||
|
int start = 0, end = 0;
|
||||||
|
if (range != null && range.startsWith("bytes=")) {
|
||||||
|
String[] values = range.split("=")[1].split("-");
|
||||||
|
start = Integer.parseInt(values[0]);
|
||||||
|
if (values.length > 1) {
|
||||||
|
end = Integer.parseInt(values[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int requestSize;
|
||||||
|
if (end != 0 && end > start) {
|
||||||
|
requestSize = end - start + 1;
|
||||||
|
} else {
|
||||||
|
requestSize = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[128];
|
||||||
|
response.setContentType(MediaUtils.getContentType(filePath));
|
||||||
|
response.setHeader("Accept-Ranges", "bytes");
|
||||||
|
response.setHeader("ETag", file.getName());
|
||||||
|
response.setHeader("Last-Modified", new Date().toString());
|
||||||
|
//第一次请求只返回content length来让客户端请求多次实际数据
|
||||||
|
if (range == null) {
|
||||||
|
response.setHeader("Content-length", contentLength + "");
|
||||||
|
} else {
|
||||||
|
//以后的多次以断点续传的方式来返回视频数据
|
||||||
|
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
|
long requestStart = 0, requestEnd = 0;
|
||||||
|
String[] ranges = range.split("=");
|
||||||
|
if (ranges.length > 1) {
|
||||||
|
String[] rangeData = ranges[1].split("-");
|
||||||
|
requestStart = Integer.parseInt(rangeData[0]);
|
||||||
|
if (rangeData.length > 1) {
|
||||||
|
requestEnd = Integer.parseInt(rangeData[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long length;
|
||||||
|
if (requestEnd > 0) {
|
||||||
|
length = requestEnd - requestStart + 1;
|
||||||
|
response.setHeader("Content-length", "" + length);
|
||||||
|
response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
|
||||||
|
} else {
|
||||||
|
length = contentLength - requestStart;
|
||||||
|
response.setHeader("Content-length", "" + length);
|
||||||
|
response.setHeader("Content-Range", "bytes " + requestStart + "-" + (contentLength - 1) + "/" + contentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServletOutputStream out = response.getOutputStream();
|
||||||
|
int needSize = requestSize;
|
||||||
|
randomFile.seek(start);
|
||||||
|
while (needSize > 0) {
|
||||||
|
int len = randomFile.read(buffer);
|
||||||
|
if (needSize < buffer.length) {
|
||||||
|
out.write(buffer, 0, needSize);
|
||||||
|
} else {
|
||||||
|
out.write(buffer, 0, len);
|
||||||
|
if (len < buffer.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needSize -= buffer.length;
|
||||||
|
}
|
||||||
|
randomFile.close();
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名文件
|
||||||
|
* @param fileName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String renameFile(String fileName) {
|
||||||
|
|
||||||
|
//没有后缀名不处理
|
||||||
|
if (!fileName.contains(SUFFIX_SPLIT)) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
//文件后缀
|
||||||
|
String extension = FilenameUtils.getExtension(fileName);
|
||||||
|
|
||||||
|
//以系统时间命名
|
||||||
|
return IdWorker.getIdStr() + "."+ extension;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理新的文件路径,为上传文件预设目录,如:2021/01/01/xxx.jpg,要注意的是,前面没有斜杠
|
||||||
|
* @param file 文件
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String processPath(MultipartFile file){
|
||||||
|
|
||||||
|
// 创建OSSClient实例。
|
||||||
|
String fileName = file.getOriginalFilename();
|
||||||
|
|
||||||
|
// 需要重命名
|
||||||
|
fileName = renameFile(fileName);
|
||||||
|
|
||||||
|
//获得上传的文件夹
|
||||||
|
String dir = DateUtils.formatDate(new Date(), "yyyy/MM/dd/");
|
||||||
|
|
||||||
|
return new StringBuffer(dir).append(fileName).toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件夹是否存在,不存在则创建
|
||||||
|
* @param fileName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static void checkDir(String fileName){
|
||||||
|
int index = fileName.lastIndexOf("/");
|
||||||
|
if(index == -1){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = new File(fileName.substring(0,index));
|
||||||
|
if(!file.exists()){
|
||||||
|
file.mkdirs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.yf.exam.ability.upload.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 媒体工具,判断媒体类型
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-11-14 16:21
|
||||||
|
*/
|
||||||
|
public class MediaUtils {
|
||||||
|
|
||||||
|
public static final Map<String, String> MEDIA_MAP = new HashMap(){
|
||||||
|
{
|
||||||
|
|
||||||
|
//本来是pdf的
|
||||||
|
put(".pdf", "application/pdf");
|
||||||
|
|
||||||
|
//视频
|
||||||
|
put(".mp4", "video,video/mp4");
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得文件类型
|
||||||
|
* @param filePath
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getContentType(String filePath){
|
||||||
|
|
||||||
|
if(!StringUtils.isBlank(filePath)
|
||||||
|
&& filePath.indexOf(".")!=-1) {
|
||||||
|
|
||||||
|
// 后缀转换成小写
|
||||||
|
String suffix = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
|
||||||
|
|
||||||
|
if (MEDIA_MAP.containsKey(suffix)) {
|
||||||
|
return MEDIA_MAP.get(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
317
exam-admin/src/main/java/com/yf/exam/aspect/DictAspect.java
Normal file
317
exam-admin/src/main/java/com/yf/exam/aspect/DictAspect.java
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
package com.yf.exam.aspect;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.yf.exam.core.annon.Dict;
|
||||||
|
import com.yf.exam.core.api.ApiRest;
|
||||||
|
import com.yf.exam.core.utils.Reflections;
|
||||||
|
import com.yf.exam.modules.sys.system.service.SysDictService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典AOP类,处理数据字典值
|
||||||
|
*
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class DictAspect {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysDictService sysDictService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切入Controller执行
|
||||||
|
* @param pjp
|
||||||
|
* @return
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Around("execution(public * com.yf.exam..*.*Controller.*(..))")
|
||||||
|
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||||
|
return this.translate(pjp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进行翻译并返回,调用前必须实现:BaseDictService
|
||||||
|
*
|
||||||
|
* @param pjp
|
||||||
|
* @return
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public Object translate(ProceedingJoinPoint pjp) throws Throwable {
|
||||||
|
// 处理字典
|
||||||
|
return this.parseAllDictText(pjp.proceed());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换全部数据字典
|
||||||
|
*
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
private Object parseAllDictText(Object result) {
|
||||||
|
|
||||||
|
// 非ApiRest类型不处理
|
||||||
|
if (result instanceof ApiRest) {
|
||||||
|
parseFullDictText(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换所有类型的数据字典、包含子列表
|
||||||
|
*
|
||||||
|
* @param result
|
||||||
|
*/
|
||||||
|
private void parseFullDictText(Object result) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Object rest = ((ApiRest) result).getData();
|
||||||
|
|
||||||
|
// 不处理普通数据类型
|
||||||
|
if (rest == null || this.isBaseType(rest.getClass())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页的
|
||||||
|
if (rest instanceof IPage) {
|
||||||
|
List<Object> items = new ArrayList<>(16);
|
||||||
|
for (Object record : ((IPage) rest).getRecords()) {
|
||||||
|
Object item = this.parseObject(record);
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
((IPage) rest).setRecords(items);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据列表的
|
||||||
|
if (rest instanceof List) {
|
||||||
|
List<Object> items = new ArrayList<>();
|
||||||
|
for (Object record : ((List) rest)) {
|
||||||
|
Object item = this.parseObject(record);
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
// 重新回写值
|
||||||
|
((ApiRest) result).setData(items);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理单对象
|
||||||
|
Object item = this.parseObject(((ApiRest) result).getData());
|
||||||
|
((ApiRest) result).setData(item);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理数据字典值
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Object parseObject(Object record) {
|
||||||
|
|
||||||
|
if (record == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不处理普通数据类型
|
||||||
|
if (this.isBaseType(record.getClass())) {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换JSON字符
|
||||||
|
String json = JSON.toJSONString(record);
|
||||||
|
JSONObject item = JSONObject.parseObject(json);
|
||||||
|
|
||||||
|
for (Field field : Reflections.getAllFields(record)) {
|
||||||
|
|
||||||
|
// 如果是List类型
|
||||||
|
if (List.class.isAssignableFrom(field.getType())) {
|
||||||
|
try {
|
||||||
|
List list = this.processList(field, item.getObject(field.getName(), List.class));
|
||||||
|
item.put(field.getName(), list);
|
||||||
|
continue;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理普通字段
|
||||||
|
if (field.getAnnotation(Dict.class) != null) {
|
||||||
|
String code = field.getAnnotation(Dict.class).dicCode();
|
||||||
|
String text = field.getAnnotation(Dict.class).dicText();
|
||||||
|
String table = field.getAnnotation(Dict.class).dictTable();
|
||||||
|
String key = String.valueOf(item.get(field.getName()));
|
||||||
|
|
||||||
|
//翻译字典值对应的txt
|
||||||
|
String textValue = this.translateDictValue(code, text, table, key);
|
||||||
|
if (StringUtils.isEmpty(textValue)) {
|
||||||
|
textValue = "";
|
||||||
|
}
|
||||||
|
item.put(field.getName() + "_dictText", textValue);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//日期格式转换
|
||||||
|
if ("java.util.Date".equals(field.getType().getName()) && item.get(field.getName()) != null) {
|
||||||
|
|
||||||
|
// 获取注解
|
||||||
|
JsonFormat ann = field.getAnnotation(JsonFormat.class);
|
||||||
|
// 格式化方式
|
||||||
|
SimpleDateFormat fmt;
|
||||||
|
|
||||||
|
// 使用注解指定的
|
||||||
|
if (ann != null && !StringUtils.isEmpty(ann.pattern())) {
|
||||||
|
fmt = new SimpleDateFormat(ann.pattern());
|
||||||
|
} else {
|
||||||
|
// 默认时间样式
|
||||||
|
fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
item.put(field.getName(), fmt.format(new Date((Long) item.get(field.getName()))));
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得类型为List的值
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private List<Object> processList(Field field, List list) {
|
||||||
|
|
||||||
|
// 空判断
|
||||||
|
if (list == null || list.size() == 0) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获得List属性的真实类
|
||||||
|
Type genericType = field.getGenericType();
|
||||||
|
Class<?> actualType = null;
|
||||||
|
if (genericType instanceof ParameterizedType) {
|
||||||
|
// 尝试获取数据类型
|
||||||
|
ParameterizedType pt = (ParameterizedType) genericType;
|
||||||
|
try {
|
||||||
|
actualType = (Class) pt.getActualTypeArguments()[0];
|
||||||
|
}catch (Exception e){
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 常规列表无需处理
|
||||||
|
if (isBaseType(actualType)) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回列表
|
||||||
|
List<Object> result = new ArrayList<>(16);
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
// 创建实例-->赋值-->字典处理
|
||||||
|
Object data = list.get(i);
|
||||||
|
try {
|
||||||
|
data = JSON.parseObject(JSON.toJSONString(data), actualType);
|
||||||
|
}catch (Exception e){
|
||||||
|
// 转换出错不处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理后的数据
|
||||||
|
Object pds = this.parseObject(data);
|
||||||
|
result.add(pds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译实现
|
||||||
|
*
|
||||||
|
* @param code
|
||||||
|
* @param text
|
||||||
|
* @param table
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String translateDictValue(String code, String text, String table, String key) {
|
||||||
|
if (StringUtils.isEmpty(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 翻译值
|
||||||
|
String dictText = null;
|
||||||
|
if (!StringUtils.isEmpty(table)) {
|
||||||
|
dictText = sysDictService.findDict(table, text, code, key.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.isEmpty(dictText)) {
|
||||||
|
return dictText;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否基本类型
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isBaseType(Class clazz) {
|
||||||
|
|
||||||
|
|
||||||
|
// 基础数据类型
|
||||||
|
if (clazz.equals(java.lang.Integer.class) ||
|
||||||
|
clazz.equals(java.lang.Byte.class) ||
|
||||||
|
clazz.equals(java.lang.Long.class) ||
|
||||||
|
clazz.equals(java.lang.Double.class) ||
|
||||||
|
clazz.equals(java.lang.Float.class) ||
|
||||||
|
clazz.equals(java.lang.Character.class) ||
|
||||||
|
clazz.equals(java.lang.Short.class) ||
|
||||||
|
clazz.equals(java.lang.Boolean.class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// String类型
|
||||||
|
if (clazz.equals(java.lang.String.class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数字
|
||||||
|
if (clazz.equals(java.lang.Number.class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
package com.yf.exam.aspect.mybatis;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
|
||||||
|
import com.yf.exam.modules.sys.user.dto.response.SysUserLoginDTO;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import net.sf.jsqlparser.parser.CCJSqlParserManager;
|
||||||
|
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||||
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||||
|
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 org.apache.ibatis.reflection.DefaultReflectorFactory;
|
||||||
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询拦截器,用于拦截处理通用的信息、如用户ID、多租户信息等;
|
||||||
|
* 特别注意:此处继承了PaginationInterceptor分页,分页必须在拦截数据后执行,否则容易出现分页不准确,分页计数大于实际数量等问题
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Log4j2
|
||||||
|
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),})
|
||||||
|
public class QueryInterceptor extends PaginationInterceptor implements Interceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户ID
|
||||||
|
*/
|
||||||
|
private static final String USER_FILTER = "{{userId}}";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object intercept(Invocation invocation) throws Throwable {
|
||||||
|
|
||||||
|
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
|
||||||
|
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
|
||||||
|
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||||
|
|
||||||
|
//sql语句类型
|
||||||
|
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
||||||
|
|
||||||
|
// 只过滤查询的
|
||||||
|
if (SqlCommandType.SELECT == sqlCommandType) {
|
||||||
|
// 获得原始SQL
|
||||||
|
String sql = statementHandler.getBoundSql().getSql();
|
||||||
|
|
||||||
|
// 不处理
|
||||||
|
if(!sql.contains(USER_FILTER)){
|
||||||
|
return super.intercept(invocation);
|
||||||
|
}
|
||||||
|
// 处理SQL语句
|
||||||
|
String outSql = this.parseSql(sql);
|
||||||
|
// 设置SQL
|
||||||
|
metaObject.setValue("delegate.boundSql.sql", outSql);
|
||||||
|
// 再分页
|
||||||
|
return super.intercept(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return invocation.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object plugin(Object target) {
|
||||||
|
return Plugin.wrap(target, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private SysUserLoginDTO getLoginUser() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SecurityUtils.getSubject().getPrincipal() != null ? (SysUserLoginDTO) SecurityUtils.getSubject().getPrincipal() : null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换用户ID
|
||||||
|
* @param sql
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String processUserId(String sql) {
|
||||||
|
|
||||||
|
// 当前用户
|
||||||
|
SysUserLoginDTO user = this.getLoginUser();
|
||||||
|
String userId = user.getId();
|
||||||
|
if(StringUtils.isNotBlank(userId)){
|
||||||
|
return sql.replace(USER_FILTER, userId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理注入用户信息
|
||||||
|
* @param src
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String parseSql(String src) {
|
||||||
|
|
||||||
|
CCJSqlParserManager parserManager = new CCJSqlParserManager();
|
||||||
|
try {
|
||||||
|
|
||||||
|
Select select = (Select) parserManager.parse(new StringReader(src));
|
||||||
|
PlainSelect selectBody = (PlainSelect) select.getSelectBody();
|
||||||
|
|
||||||
|
// 过滤客户
|
||||||
|
String sql = selectBody.toString();
|
||||||
|
|
||||||
|
// 过滤用户ID
|
||||||
|
sql = this.processUserId(sql);
|
||||||
|
|
||||||
|
// 获得SQL
|
||||||
|
return sql;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -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.yf.exam.core.api.ApiError;
|
||||||
|
import com.yf.exam.core.api.ApiRest;
|
||||||
|
import com.yf.exam.core.utils.jackson.JsonHelper;
|
||||||
|
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(JsonHelper.toJson(apiRest));
|
||||||
|
response.getWriter().close();
|
||||||
|
|
||||||
|
}catch (IOException e){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
exam-admin/src/main/java/com/yf/exam/config/CorsConfig.java
Normal file
35
exam-admin/src/main/java/com/yf/exam/config/CorsConfig.java
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package com.yf.exam.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关全局设置,允许跨域
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-08-13 17:28
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean corsFilter() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
config.addAllowedOrigin(CorsConfiguration.ALL);
|
||||||
|
config.addAllowedHeader(CorsConfiguration.ALL);
|
||||||
|
config.addAllowedMethod(CorsConfiguration.ALL);
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
|
||||||
|
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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,37 @@
|
|||||||
|
package com.yf.exam.config;
|
||||||
|
|
||||||
|
import com.yf.exam.aspect.mybatis.QueryInterceptor;
|
||||||
|
import com.yf.exam.aspect.mybatis.UpdateInterceptor;
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mybatis过滤器配置
|
||||||
|
* 注意:必须按顺序进行配置,否则容易出现业务异常
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@MapperScan("com.yf.exam.modules.**.mapper")
|
||||||
|
public class MybatisConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据查询过滤器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public QueryInterceptor queryInterceptor() {
|
||||||
|
QueryInterceptor query = new QueryInterceptor();
|
||||||
|
query.setLimit(-1L);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入数据过滤器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public UpdateInterceptor updateInterceptor() {
|
||||||
|
return new UpdateInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
127
exam-admin/src/main/java/com/yf/exam/config/ShiroConfig.java
Normal file
127
exam-admin/src/main/java/com/yf/exam/config/ShiroConfig.java
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package com.yf.exam.config;
|
||||||
|
|
||||||
|
import com.yf.exam.ability.shiro.CNFilterFactoryBean;
|
||||||
|
import com.yf.exam.ability.shiro.ShiroRealm;
|
||||||
|
import com.yf.exam.ability.shiro.aop.JwtFilter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||||
|
import org.apache.shiro.mgt.DefaultSubjectDAO;
|
||||||
|
import org.apache.shiro.mgt.SecurityManager;
|
||||||
|
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||||
|
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||||
|
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||||
|
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||||
|
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shiro配置类
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class ShiroConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter Chain定义说明
|
||||||
|
*
|
||||||
|
* 1、一个URL可以配置多个Filter,使用逗号分隔
|
||||||
|
* 2、当设置多个过滤器时,全部验证通过,才视为通过
|
||||||
|
* 3、部分过滤器可指定参数,如perms,roles
|
||||||
|
*/
|
||||||
|
@Bean("shiroFilterFactoryBean")
|
||||||
|
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
|
||||||
|
ShiroFilterFactoryBean shiroFilterFactoryBean = new CNFilterFactoryBean();
|
||||||
|
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||||
|
// 拦截器
|
||||||
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// 需要排除的一些接口
|
||||||
|
map.put("/exam/api/sys/user/login", "anon");
|
||||||
|
map.put("/exam/api/sys/user/reg", "anon");
|
||||||
|
map.put("/exam/api/sys/user/quick-reg", "anon");
|
||||||
|
|
||||||
|
// 获取网站基本信息
|
||||||
|
map.put("/exam/api/sys/config/detail", "anon");
|
||||||
|
|
||||||
|
// 文件读取
|
||||||
|
map.put("/upload/file/**", "anon");
|
||||||
|
|
||||||
|
map.put("/", "anon");
|
||||||
|
map.put("/v2/**", "anon");
|
||||||
|
map.put("/doc.html", "anon");
|
||||||
|
map.put("/**/*.js", "anon");
|
||||||
|
map.put("/**/*.css", "anon");
|
||||||
|
map.put("/**/*.html", "anon");
|
||||||
|
map.put("/**/*.svg", "anon");
|
||||||
|
map.put("/**/*.pdf", "anon");
|
||||||
|
map.put("/**/*.jpg", "anon");
|
||||||
|
map.put("/**/*.png", "anon");
|
||||||
|
map.put("/**/*.ico", "anon");
|
||||||
|
|
||||||
|
// 字体
|
||||||
|
map.put("/**/*.ttf", "anon");
|
||||||
|
map.put("/**/*.woff", "anon");
|
||||||
|
map.put("/**/*.woff2", "anon");
|
||||||
|
map.put("/druid/**", "anon");
|
||||||
|
map.put("/swagger-ui.html", "anon");
|
||||||
|
map.put("/swagger**/**", "anon");
|
||||||
|
map.put("/webjars/**", "anon");
|
||||||
|
|
||||||
|
// 添加自己的过滤器并且取名为jwt
|
||||||
|
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
|
||||||
|
filterMap.put("jwt", new JwtFilter());
|
||||||
|
shiroFilterFactoryBean.setFilters(filterMap);
|
||||||
|
map.put("/**", "jwt");
|
||||||
|
|
||||||
|
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
|
||||||
|
return shiroFilterFactoryBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("securityManager")
|
||||||
|
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
|
||||||
|
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||||
|
securityManager.setRealm(myRealm);
|
||||||
|
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
|
||||||
|
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
|
||||||
|
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
|
||||||
|
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
|
||||||
|
securityManager.setSubjectDAO(subjectDAO);
|
||||||
|
return securityManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下面的代码是添加注解支持
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@DependsOn("lifecycleBeanPostProcessor")
|
||||||
|
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
|
||||||
|
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
|
||||||
|
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
|
||||||
|
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
|
||||||
|
defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
|
||||||
|
return defaultAdvisorAutoProxyCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
||||||
|
return new LifecycleBeanPostProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
|
||||||
|
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
|
||||||
|
advisor.setSecurityManager(securityManager);
|
||||||
|
return advisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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.config.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON序列化配置
|
||||||
|
* @author van
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class JacksonConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SimpleModule xssEscapeModule() {
|
||||||
|
SimpleModule module = new SimpleModule();
|
||||||
|
// 注册反序列化器(处理输入)
|
||||||
|
module.addDeserializer(String.class, new XssEscapeJsonDeserializer());
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.yf.exam.config.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import org.owasp.encoder.Encode;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class XssEscapeJsonDeserializer extends JsonDeserializer<String> {
|
||||||
|
@Override
|
||||||
|
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
String value = p.getText(); // 获取原始字符串
|
||||||
|
return Encode.forHtml(value); // 转义HTML特殊字符
|
||||||
|
}
|
||||||
|
}
|
||||||
21
exam-admin/src/main/java/com/yf/exam/core/annon/Dict.java
Normal file
21
exam-admin/src/main/java/com/yf/exam/core/annon/Dict.java
Normal file
@ -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 "";
|
||||||
|
}
|
||||||
67
exam-admin/src/main/java/com/yf/exam/core/api/ApiError.java
Normal file
67
exam-admin/src/main/java/com/yf/exam/core/api/ApiError.java
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package com.yf.exam.core.api;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局错误码定义,用于定义接口的响应数据,
|
||||||
|
* 枚举名称全部使用代码命名,在系统中调用,免去取名难的问题。
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-06-14 21:15
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ApiError implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用错误,接口参数不全
|
||||||
|
*/
|
||||||
|
ERROR_10010001("参数不全或类型错误!"),
|
||||||
|
ERROR_10010002("您还未登录,请先登录!"),
|
||||||
|
ERROR_10010003("数据不存在!"),
|
||||||
|
ERROR_10010012("图形验证码错误!"),
|
||||||
|
ERROR_10010013("短信验证码错误!"),
|
||||||
|
ERROR_10010014("不允许重复评论!"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试相关错误
|
||||||
|
*/
|
||||||
|
ERROR_20010001("试题被删除,无法继续考试!"),
|
||||||
|
ERROR_20010002("您有正在进行的考试!"),
|
||||||
|
|
||||||
|
|
||||||
|
ERROR_90010001("账号不存在,请确认!"),
|
||||||
|
ERROR_90010002("账号或密码错误!"),
|
||||||
|
ERROR_90010003("至少要包含一个角色!"),
|
||||||
|
ERROR_90010004("管理员账号无法修改!"),
|
||||||
|
ERROR_90010005("账号被禁用,请联系管理员!"),
|
||||||
|
ERROR_90010006("活动用户不足,无法开启竞拍!"),
|
||||||
|
ERROR_90010007("旧密码不正确,请确认!"),
|
||||||
|
|
||||||
|
|
||||||
|
ERROR_60000001("数据不存在!");
|
||||||
|
|
||||||
|
public String msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成Markdown格式文档,用于更新文档用的
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
for (ApiError e : ApiError.values()) {
|
||||||
|
System.out.println("'"+e.name().replace("ERROR_", "")+"':'"+e.msg+"',");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取错误码
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Integer getCode(){
|
||||||
|
return Integer.parseInt(this.name().replace("ERROR_", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
64
exam-admin/src/main/java/com/yf/exam/core/api/ApiRest.java
Normal file
64
exam-admin/src/main/java/com/yf/exam/core/api/ApiRest.java
Normal file
@ -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,154 @@
|
|||||||
|
package com.yf.exam.core.api.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.yf.exam.core.api.ApiError;
|
||||||
|
import com.yf.exam.core.api.ApiRest;
|
||||||
|
import com.yf.exam.core.exception.ServiceException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础控制器
|
||||||
|
* @author Dav
|
||||||
|
*/
|
||||||
|
public class BaseController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功默认消息
|
||||||
|
*/
|
||||||
|
private static final Integer CODE_SUCCESS = 0;
|
||||||
|
private static final String MSG_SUCCESS = "操作成功!";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败默认消息
|
||||||
|
*/
|
||||||
|
private static final Integer CODE_FAILURE = 1;
|
||||||
|
private static final String MSG_FAILURE = "请求失败!";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成消息构造
|
||||||
|
* @param code
|
||||||
|
* @param message
|
||||||
|
* @param data
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> message(Integer code, String message, T data){
|
||||||
|
ApiRest<T> response = new ApiRest<>();
|
||||||
|
response.setCode(code);
|
||||||
|
response.setMsg(message);
|
||||||
|
if(data!=null) {
|
||||||
|
response.setData(data);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求成功空数据
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> success(){
|
||||||
|
return message(0, "请求成功!", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求成功,通用代码
|
||||||
|
* @param message
|
||||||
|
* @param data
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> success(String message, T data){
|
||||||
|
return message(CODE_SUCCESS, message, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求成功,仅内容
|
||||||
|
* @param data
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> success(T data){
|
||||||
|
return message(CODE_SUCCESS, MSG_SUCCESS, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,完整构造
|
||||||
|
* @param code
|
||||||
|
* @param message
|
||||||
|
* @param data
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(Integer code, String message, T data){
|
||||||
|
return message(code, message, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,消息和内容
|
||||||
|
* @param message
|
||||||
|
* @param data
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(String message, T data){
|
||||||
|
return message(CODE_FAILURE, message, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,消息
|
||||||
|
* @param message
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(String message){
|
||||||
|
return message(CODE_FAILURE, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,仅内容
|
||||||
|
* @param data
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(T data){
|
||||||
|
return message(CODE_FAILURE, MSG_FAILURE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,仅内容
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(){
|
||||||
|
return message(CODE_FAILURE, MSG_FAILURE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,仅内容
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(ApiError error, T data){
|
||||||
|
return message(error.getCode(), error.msg, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求失败,仅内容
|
||||||
|
* @param ex
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected <T> ApiRest<T> failure(ServiceException ex){
|
||||||
|
ApiRest<T> apiRest = message(ex.getCode(), ex.getMsg(), null);
|
||||||
|
return apiRest;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.yf.exam.core.api.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求和响应的基础类,用于处理序列化
|
||||||
|
* @author dav
|
||||||
|
* @date 2019/3/16 15:56
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BaseDTO implements Serializable {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package com.yf.exam.core.api.dto;
|
||||||
|
|
||||||
|
import com.yf.exam.core.api.dto.BaseDTO;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 主键通用请求类,用于根据ID查询
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2019-04-20 12:15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="主键通用请求类", description="主键通用请求类")
|
||||||
|
public class BaseIdReqDTO extends BaseDTO {
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "主键ID", required=true)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.yf.exam.core.api.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 主键通用响应类,用于添加后返回内容
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2019-04-20 12:15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="主键通用响应类", description="主键通用响应类")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BaseIdRespDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "主键ID", required=true)
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.yf.exam.core.api.dto;
|
||||||
|
|
||||||
|
import com.yf.exam.core.api.dto.BaseDTO;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用ID列表类操作,用于批量删除、修改状态等
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-08-01 19:07
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="删除参数", description="删除参数")
|
||||||
|
public class BaseIdsReqDTO extends BaseDTO {
|
||||||
|
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "要删除的ID列表", required = true)
|
||||||
|
private List<String> ids;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.yf.exam.core.api.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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 通用状态请求类,用于修改状态什么的
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2019-04-20 12:15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="通用状态请求类", description="通用状态请求类")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BaseStateReqDTO extends BaseDTO {
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "要修改对象的ID列表", required=true)
|
||||||
|
private List<String> ids;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true)
|
||||||
|
private Integer state;
|
||||||
|
}
|
||||||
@ -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,48 @@
|
|||||||
|
package com.yf.exam.core.api.utils;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.alibaba.fastjson.support.config.FastJsonConfig;
|
||||||
|
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON数据转换器,用于转换返回消息的格式
|
||||||
|
* @author dav
|
||||||
|
* @date 2018/9/11 19:30
|
||||||
|
*/
|
||||||
|
public class JsonConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FastJson消息转换器
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static HttpMessageConverter fastConverter() {
|
||||||
|
// 定义一个convert转换消息的对象
|
||||||
|
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
|
||||||
|
// 添加FastJson的配置信息
|
||||||
|
FastJsonConfig fastJsonConfig = new FastJsonConfig();
|
||||||
|
// 默认转换器
|
||||||
|
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
|
||||||
|
SerializerFeature.WriteNullNumberAsZero,
|
||||||
|
SerializerFeature.MapSortField,
|
||||||
|
SerializerFeature.WriteNullStringAsEmpty,
|
||||||
|
SerializerFeature.DisableCircularReferenceDetect,
|
||||||
|
SerializerFeature.WriteDateUseDateFormat,
|
||||||
|
SerializerFeature.WriteNullListAsEmpty);
|
||||||
|
fastJsonConfig.setCharset(Charset.forName("UTF-8"));
|
||||||
|
// 处理中文乱码问题
|
||||||
|
List<MediaType> fastMediaTypes = new ArrayList<>();
|
||||||
|
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
|
||||||
|
fastConverter.setSupportedMediaTypes(fastMediaTypes);
|
||||||
|
// 在convert中添加配置信息
|
||||||
|
fastConverter.setFastJsonConfig(fastJsonConfig);
|
||||||
|
|
||||||
|
return fastConverter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package com.yf.exam.core.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的状态枚举信息
|
||||||
|
*
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-09-17 17:57
|
||||||
|
*/
|
||||||
|
public interface CommonState {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通状态,正常的
|
||||||
|
*/
|
||||||
|
Integer NORMAL = 0;
|
||||||
|
/**
|
||||||
|
* 非正常状态,禁用,下架等
|
||||||
|
*/
|
||||||
|
Integer ABNORMAL = 1;
|
||||||
|
}
|
||||||
@ -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,46 @@
|
|||||||
|
package com.yf.exam.core.exception;
|
||||||
|
|
||||||
|
import com.yf.exam.core.api.ApiRest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一异常处理类
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-06-21 19:27
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class ServiceExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
|
||||||
|
* @param binder
|
||||||
|
*/
|
||||||
|
@InitBinder
|
||||||
|
public void initWebBinder(WebDataBinder binder){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
|
||||||
|
* @param model
|
||||||
|
*/
|
||||||
|
@ModelAttribute
|
||||||
|
public void addAttribute(Model model) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获ServiceException
|
||||||
|
* @param e
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({com.yf.exam.core.exception.ServiceException.class})
|
||||||
|
@ResponseStatus(HttpStatus.OK)
|
||||||
|
public ApiRest serviceExceptionHandler(ServiceException e) {
|
||||||
|
return new ApiRest(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
exam-admin/src/main/java/com/yf/exam/core/utils/DateUtils.java
Normal file
103
exam-admin/src/main/java/com/yf/exam/core/utils/DateUtils.java
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package com.yf.exam.core.utils;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期处理工具类
|
||||||
|
* ClassName: DateUtils <br/>
|
||||||
|
* date: 2018年12月13日 下午6:34:02 <br/>
|
||||||
|
*
|
||||||
|
* @author Bool
|
||||||
|
* @version
|
||||||
|
*/
|
||||||
|
public class DateUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数 <br/>
|
||||||
|
* @author Bool
|
||||||
|
* @param userCreateTime
|
||||||
|
* @return
|
||||||
|
* @since JDK 1.6
|
||||||
|
*/
|
||||||
|
public static int calcExpDays(Date userCreateTime){
|
||||||
|
|
||||||
|
Calendar start = Calendar.getInstance();
|
||||||
|
start.setTime(userCreateTime);
|
||||||
|
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
now.setTime(new Date());
|
||||||
|
|
||||||
|
long l = now.getTimeInMillis() - start.getTimeInMillis();
|
||||||
|
int days = new Long(l / (1000 * 60 * 60 * 24)).intValue();
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* dateNow:获取当前时间的字符串格式,根据传入的格式化来展示. <br/>
|
||||||
|
* @author Bool
|
||||||
|
* @param format 日期格式化
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String dateNow(String format) {
|
||||||
|
SimpleDateFormat fmt = new SimpleDateFormat(format);
|
||||||
|
Calendar c = new GregorianCalendar();
|
||||||
|
return fmt.format(c.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* formatDate:格式化日期,返回指定的格式 <br/>
|
||||||
|
* @author Bool
|
||||||
|
* @param time
|
||||||
|
* @param format
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String formatDate(Date time, String format) {
|
||||||
|
SimpleDateFormat fmt = new SimpleDateFormat(format);
|
||||||
|
return fmt.format(time.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parseDate:将字符串转换成日期,使用:yyyy-MM-dd HH:mm:ss 来格式化
|
||||||
|
* @author Bool
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Date parseDate(String date) {
|
||||||
|
return parseDate(date, "yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* parseDate:将字符串转换成日期,使用指定格式化来格式化
|
||||||
|
* @author Bool
|
||||||
|
* @param date
|
||||||
|
* @param pattern
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Date parseDate(String date, String pattern) {
|
||||||
|
|
||||||
|
if (pattern==null) {
|
||||||
|
pattern = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleDateFormat fmt = new SimpleDateFormat(pattern);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
return fmt.parse(date);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
65
exam-admin/src/main/java/com/yf/exam/core/utils/IpUtils.java
Normal file
65
exam-admin/src/main/java/com/yf/exam/core/utils/IpUtils.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package com.yf.exam.core.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP获取工具类,用户获取网络请求过来的真实IP
|
||||||
|
* ClassName: IpUtils <br/>
|
||||||
|
* date: 2018年2月13日 下午7:27:52 <br/>
|
||||||
|
*
|
||||||
|
* @author Bool
|
||||||
|
* @version
|
||||||
|
*/
|
||||||
|
public class IpUtils {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* getClientIp:通过请求获取客户端的真实IP地址
|
||||||
|
* @author Bool
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String extractClientIp(HttpServletRequest request) {
|
||||||
|
|
||||||
|
String ip = null;
|
||||||
|
|
||||||
|
//X-Forwarded-For:Squid 服务代理
|
||||||
|
String ipAddresses = request.getHeader("X-Forwarded-For");
|
||||||
|
|
||||||
|
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
|
||||||
|
//Proxy-Client-IP:apache 服务代理
|
||||||
|
ipAddresses = request.getHeader("Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
|
||||||
|
//WL-Proxy-Client-IP:weblogic 服务代理
|
||||||
|
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
|
||||||
|
//HTTP_CLIENT_IP:有些代理服务器
|
||||||
|
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
|
||||||
|
//X-Real-IP:nginx服务代理
|
||||||
|
ipAddresses = request.getHeader("X-Real-IP");
|
||||||
|
}
|
||||||
|
|
||||||
|
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
|
||||||
|
if (ipAddresses != null && ipAddresses.length() != 0) {
|
||||||
|
ip = ipAddresses.split(",")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
//还是不能获取到,最后再通过request.getRemoteAddr();获取
|
||||||
|
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
324
exam-admin/src/main/java/com/yf/exam/core/utils/Reflections.java
Normal file
324
exam-admin/src/main/java/com/yf/exam/core/utils/Reflections.java
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2005-2012 springside.org.cn
|
||||||
|
*/
|
||||||
|
package com.yf.exam.core.utils;
|
||||||
|
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反射工具类.
|
||||||
|
* 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
|
||||||
|
* @author calvin
|
||||||
|
* @version 2016-01-15
|
||||||
|
*/
|
||||||
|
@Log4j2
|
||||||
|
public class Reflections {
|
||||||
|
|
||||||
|
private static final String SETTER_PREFIX = "set";
|
||||||
|
|
||||||
|
private static final String GETTER_PREFIX = "get";
|
||||||
|
|
||||||
|
private static final String CGLIB_CLASS_SEPARATOR = "$$";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类的所有属性,包括父类
|
||||||
|
*
|
||||||
|
* @param object
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Field[] getAllFields(Object object) {
|
||||||
|
Class<?> clazz = object.getClass();
|
||||||
|
List<Field> fieldList = new ArrayList<>();
|
||||||
|
while (clazz != null) {
|
||||||
|
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
Field[] fields = new Field[fieldList.size()];
|
||||||
|
fieldList.toArray(fields);
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用Getter方法.
|
||||||
|
* 支持多级,如:对象名.对象名.方法
|
||||||
|
*/
|
||||||
|
public static Object invokeGetter(Object obj, String propertyName) {
|
||||||
|
Object object = obj;
|
||||||
|
for (String name : StringUtils.split(propertyName, ".")){
|
||||||
|
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
|
||||||
|
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用Setter方法, 仅匹配方法名。
|
||||||
|
* 支持多级,如:对象名.对象名.方法
|
||||||
|
*/
|
||||||
|
public static void invokeSetter(Object obj, String propertyName, Object value) {
|
||||||
|
Object object = obj;
|
||||||
|
String[] names = StringUtils.split(propertyName, ".");
|
||||||
|
for (int i=0; i<names.length; i++){
|
||||||
|
if(i<names.length-1){
|
||||||
|
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
|
||||||
|
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
|
||||||
|
}else{
|
||||||
|
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
|
||||||
|
invokeMethodByName(object, setterMethodName, new Object[] { value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
|
||||||
|
*/
|
||||||
|
public static Object getFieldValue(final Object obj, final String fieldName) {
|
||||||
|
Field field = getAccessibleField(obj, fieldName);
|
||||||
|
|
||||||
|
if (field == null) {
|
||||||
|
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object result = null;
|
||||||
|
try {
|
||||||
|
result = field.get(obj);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
log.error("不可能抛出的异常{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
|
||||||
|
*/
|
||||||
|
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
|
||||||
|
Field field = getAccessibleField(obj, fieldName);
|
||||||
|
|
||||||
|
if (field == null) {
|
||||||
|
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
field.set(obj, value);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
log.error("不可能抛出的异常:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接调用对象方法, 无视private/protected修饰符.
|
||||||
|
* 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
|
||||||
|
* 同时匹配方法名+参数类型,
|
||||||
|
*/
|
||||||
|
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
|
||||||
|
final Object[] args) {
|
||||||
|
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
|
||||||
|
if (method == null) {
|
||||||
|
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return method.invoke(obj, args);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw convertReflectionExceptionToUnchecked(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接调用对象方法, 无视private/protected修饰符,
|
||||||
|
* 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
|
||||||
|
* 只匹配函数名,如果有多个同名函数调用第一个。
|
||||||
|
*/
|
||||||
|
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
|
||||||
|
Method method = getAccessibleMethodByName(obj, methodName);
|
||||||
|
if (method == null) {
|
||||||
|
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return method.invoke(obj, args);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw convertReflectionExceptionToUnchecked(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
|
||||||
|
*
|
||||||
|
* 如向上转型到Object仍无法找到, 返回null.
|
||||||
|
*/
|
||||||
|
public static Field getAccessibleField(final Object obj, final String fieldName) {
|
||||||
|
Validate.notNull(obj, "object can't be null");
|
||||||
|
Validate.notBlank(fieldName, "fieldName can't be blank");
|
||||||
|
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
|
||||||
|
try {
|
||||||
|
Field field = superClass.getDeclaredField(fieldName);
|
||||||
|
makeAccessible(field);
|
||||||
|
return field;
|
||||||
|
} catch (NoSuchFieldException e) {//NOSONAR
|
||||||
|
// Field不在当前类定义,继续向上转型
|
||||||
|
continue;// new add
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
|
||||||
|
* 如向上转型到Object仍无法找到, 返回null.
|
||||||
|
* 匹配函数名+参数类型。
|
||||||
|
*
|
||||||
|
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
|
||||||
|
*/
|
||||||
|
public static Method getAccessibleMethod(final Object obj, final String methodName,
|
||||||
|
final Class<?>... parameterTypes) {
|
||||||
|
Validate.notNull(obj, "object can't be null");
|
||||||
|
Validate.notBlank(methodName, "methodName can't be blank");
|
||||||
|
|
||||||
|
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
|
||||||
|
try {
|
||||||
|
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
|
||||||
|
makeAccessible(method);
|
||||||
|
return method;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Method不在当前类定义,继续向上转型
|
||||||
|
continue;// new add
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
|
||||||
|
* 如向上转型到Object仍无法找到, 返回null.
|
||||||
|
* 只匹配函数名。
|
||||||
|
*
|
||||||
|
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
|
||||||
|
*/
|
||||||
|
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
|
||||||
|
Validate.notNull(obj, "object can't be null");
|
||||||
|
Validate.notBlank(methodName, "methodName can't be blank");
|
||||||
|
|
||||||
|
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
|
||||||
|
Method[] methods = searchType.getDeclaredMethods();
|
||||||
|
for (Method method : methods) {
|
||||||
|
if (method.getName().equals(methodName)) {
|
||||||
|
makeAccessible(method);
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
|
||||||
|
*/
|
||||||
|
public static void makeAccessible(Method method) {
|
||||||
|
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
|
||||||
|
&& !method.isAccessible()) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
|
||||||
|
*/
|
||||||
|
public static void makeAccessible(Field field) {
|
||||||
|
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
|
||||||
|
.isFinal(field.getModifiers())) && !field.isAccessible()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
|
||||||
|
* 如无法找到, 返回Object.class.
|
||||||
|
* eg.
|
||||||
|
* public UserDao extends HibernateDao<User>
|
||||||
|
*
|
||||||
|
* @param clazz The class to introspect
|
||||||
|
* @return the first generic declaration, or Object.class if cannot be determined
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> Class<T> getClassGenricType(final Class clazz) {
|
||||||
|
return getClassGenricType(clazz, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
|
||||||
|
* 如无法找到, 返回Object.class.
|
||||||
|
*
|
||||||
|
* 如public UserDao extends HibernateDao<User,Long>
|
||||||
|
*
|
||||||
|
* @param clazz clazz The class to introspect
|
||||||
|
* @param index the Index of the generic ddeclaration,start from 0.
|
||||||
|
* @return the index generic declaration, or Object.class if cannot be determined
|
||||||
|
*/
|
||||||
|
public static Class getClassGenricType(final Class clazz, final int index) {
|
||||||
|
|
||||||
|
Type genType = clazz.getGenericSuperclass();
|
||||||
|
|
||||||
|
if (!(genType instanceof ParameterizedType)) {
|
||||||
|
log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
|
||||||
|
return Object.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
|
||||||
|
|
||||||
|
if (index >= params.length || index < 0) {
|
||||||
|
log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
|
||||||
|
+ params.length);
|
||||||
|
return Object.class;
|
||||||
|
}
|
||||||
|
if (!(params[index] instanceof Class)) {
|
||||||
|
log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
|
||||||
|
return Object.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Class) params[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> getUserClass(Object instance) {
|
||||||
|
Assert.notNull(instance, "Instance must not be null");
|
||||||
|
Class clazz = instance.getClass();
|
||||||
|
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
|
||||||
|
Class<?> superClass = clazz.getSuperclass();
|
||||||
|
if (superClass != null && !Object.class.equals(superClass)) {
|
||||||
|
return superClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将反射时的checked exception转换为unchecked exception.
|
||||||
|
*/
|
||||||
|
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
|
||||||
|
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|
||||||
|
|| e instanceof NoSuchMethodException) {
|
||||||
|
return new IllegalArgumentException(e);
|
||||||
|
} else if (e instanceof InvocationTargetException) {
|
||||||
|
return new RuntimeException(((InvocationTargetException) e).getTargetException());
|
||||||
|
} else if (e instanceof RuntimeException) {
|
||||||
|
return (RuntimeException) e;
|
||||||
|
}
|
||||||
|
return new RuntimeException("Unexpected Checked Exception.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,39 @@
|
|||||||
|
package com.yf.exam.core.utils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串常用工具类
|
||||||
|
* @author bool
|
||||||
|
* @date 2019-05-15 11:40
|
||||||
|
*/
|
||||||
|
public class StringUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为空字符
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isBlank(String str){
|
||||||
|
return str==null || "".equals(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将MAP转换成一个xml格式,格式为<xml><key>value</key>...</xml>
|
||||||
|
* @param params
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String mapToXml(Map<String, String> params){
|
||||||
|
StringBuffer sb = new StringBuffer("<xml>");
|
||||||
|
for(String key:params.keySet()){
|
||||||
|
sb.append("<")
|
||||||
|
.append(key).append(">")
|
||||||
|
.append(params.get(key))
|
||||||
|
.append("</").append(key).append(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("</xml>");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,402 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
|
||||||
|
*/
|
||||||
|
package com.yf.exam.core.utils.excel;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.yf.exam.core.utils.Reflections;
|
||||||
|
import com.yf.exam.core.utils.excel.annotation.ExcelField;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.CellStyle;
|
||||||
|
import org.apache.poi.ss.usermodel.Comment;
|
||||||
|
import org.apache.poi.ss.usermodel.DataFormat;
|
||||||
|
import org.apache.poi.ss.usermodel.Font;
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出Excel文件(导出“XLSX”格式,支持大数据量导出 @see org.apache.poi.ss.SpreadsheetVersion)
|
||||||
|
* @author jeeplus
|
||||||
|
* @version 2016-04-21
|
||||||
|
*/
|
||||||
|
public class ExportExcel {
|
||||||
|
|
||||||
|
private static Logger log = LoggerFactory.getLogger(ExportExcel.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作薄对象
|
||||||
|
*/
|
||||||
|
private SXSSFWorkbook wb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作表对象
|
||||||
|
*/
|
||||||
|
private Sheet sheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 样式列表
|
||||||
|
*/
|
||||||
|
private Map<String, CellStyle> styles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前行号
|
||||||
|
*/
|
||||||
|
private int rownum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注解列表(Object[]{ ExcelField, Field/Method })
|
||||||
|
*/
|
||||||
|
List<Object[]> annotationList = Lists.newArrayList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param title 表格标题,传“空值”,表示无标题
|
||||||
|
* @param cls 实体对象,通过annotation.ExportField获取标题
|
||||||
|
*/
|
||||||
|
public ExportExcel(String title, Class<?> cls){
|
||||||
|
this(title, cls, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param title 表格标题,传“空值”,表示无标题
|
||||||
|
* @param cls 实体对象,通过annotation.ExportField获取标题
|
||||||
|
* @param type 导出类型(1:导出数据;2:导出模板)
|
||||||
|
* @param groups 导入分组
|
||||||
|
*/
|
||||||
|
public ExportExcel(String title, Class<?> cls, int type, int... groups){
|
||||||
|
// Get annotation field
|
||||||
|
Field[] fs = cls.getDeclaredFields();
|
||||||
|
for (Field f : fs){
|
||||||
|
ExcelField ef = f.getAnnotation(ExcelField.class);
|
||||||
|
if (ef != null && (ef.type()==0 || ef.type()==type)){
|
||||||
|
if (groups!=null && groups.length>0){
|
||||||
|
boolean inGroup = false;
|
||||||
|
for (int g : groups){
|
||||||
|
if (inGroup){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int efg : ef.groups()){
|
||||||
|
if (g == efg){
|
||||||
|
inGroup = true;
|
||||||
|
annotationList.add(new Object[]{ef, f});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
annotationList.add(new Object[]{ef, f});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get annotation method
|
||||||
|
Method[] ms = cls.getDeclaredMethods();
|
||||||
|
for (Method m : ms){
|
||||||
|
ExcelField ef = m.getAnnotation(ExcelField.class);
|
||||||
|
if (ef != null && (ef.type()==0 || ef.type()==type)){
|
||||||
|
if (groups!=null && groups.length>0){
|
||||||
|
boolean inGroup = false;
|
||||||
|
for (int g : groups){
|
||||||
|
if (inGroup){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int efg : ef.groups()){
|
||||||
|
if (g == efg){
|
||||||
|
inGroup = true;
|
||||||
|
annotationList.add(new Object[]{ef, m});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
annotationList.add(new Object[]{ef, m});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Field sorting
|
||||||
|
Collections.sort(annotationList, new Comparator<Object[]>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Object[] o1, Object[] o2) {
|
||||||
|
return new Integer(((ExcelField)o1[0]).sort()).compareTo(
|
||||||
|
new Integer(((ExcelField)o2[0]).sort()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Initialize
|
||||||
|
List<String> headerList = Lists.newArrayList();
|
||||||
|
for (Object[] os : annotationList){
|
||||||
|
String t = ((ExcelField)os[0]).title();
|
||||||
|
// 如果是导出,则去掉注释
|
||||||
|
if (type==1){
|
||||||
|
String[] ss = StringUtils.split(t, "**", 2);
|
||||||
|
if (ss.length==2){
|
||||||
|
t = ss[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headerList.add(t);
|
||||||
|
}
|
||||||
|
initialize(title, headerList);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 初始化函数
|
||||||
|
* @param title 表格标题,传“空值”,表示无标题
|
||||||
|
* @param headerList 表头列表
|
||||||
|
*/
|
||||||
|
private void initialize(String title, List<String> headerList) {
|
||||||
|
this.wb = new SXSSFWorkbook(500);
|
||||||
|
this.sheet = wb.createSheet("Export");
|
||||||
|
this.styles = createStyles(wb);
|
||||||
|
// Create title
|
||||||
|
if (StringUtils.isNotBlank(title)){
|
||||||
|
Row titleRow = sheet.createRow(rownum++);
|
||||||
|
titleRow.setHeightInPoints(30);
|
||||||
|
Cell titleCell = titleRow.createCell(0);
|
||||||
|
titleCell.setCellStyle(styles.get("title"));
|
||||||
|
titleCell.setCellValue(title);
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(),
|
||||||
|
titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1));
|
||||||
|
}
|
||||||
|
// Create header
|
||||||
|
if (headerList == null){
|
||||||
|
throw new RuntimeException("headerList not null!");
|
||||||
|
}
|
||||||
|
Row headerRow = sheet.createRow(rownum++);
|
||||||
|
headerRow.setHeightInPoints(16);
|
||||||
|
for (int i = 0; i < headerList.size(); i++) {
|
||||||
|
Cell cell = headerRow.createCell(i);
|
||||||
|
cell.setCellStyle(styles.get("header"));
|
||||||
|
String[] ss = StringUtils.split(headerList.get(i), "**", 2);
|
||||||
|
if (ss.length==2){
|
||||||
|
cell.setCellValue(ss[0]);
|
||||||
|
Comment comment = this.sheet.createDrawingPatriarch().createCellComment(
|
||||||
|
new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6));
|
||||||
|
comment.setString(new XSSFRichTextString(ss[1]));
|
||||||
|
cell.setCellComment(comment);
|
||||||
|
}else{
|
||||||
|
cell.setCellValue(headerList.get(i));
|
||||||
|
}
|
||||||
|
sheet.autoSizeColumn(i);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < headerList.size(); i++) {
|
||||||
|
int colWidth = sheet.getColumnWidth(i)*2;
|
||||||
|
sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth);
|
||||||
|
}
|
||||||
|
log.debug("Initialize success.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建表格样式
|
||||||
|
* @param wb 工作薄对象
|
||||||
|
* @return 样式列表
|
||||||
|
*/
|
||||||
|
private Map<String, CellStyle> createStyles(Workbook wb) {
|
||||||
|
Map<String, CellStyle> styles = new HashMap<>(16);
|
||||||
|
|
||||||
|
CellStyle style = wb.createCellStyle();
|
||||||
|
style.setAlignment(CellStyle.ALIGN_CENTER);
|
||||||
|
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
|
||||||
|
Font titleFont = wb.createFont();
|
||||||
|
titleFont.setFontName("Arial");
|
||||||
|
titleFont.setFontHeightInPoints((short) 16);
|
||||||
|
titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
|
||||||
|
style.setFont(titleFont);
|
||||||
|
styles.put("title", style);
|
||||||
|
|
||||||
|
style = wb.createCellStyle();
|
||||||
|
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
|
||||||
|
style.setBorderRight(CellStyle.BORDER_THIN);
|
||||||
|
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||||
|
style.setBorderLeft(CellStyle.BORDER_THIN);
|
||||||
|
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||||
|
style.setBorderTop(CellStyle.BORDER_THIN);
|
||||||
|
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||||
|
style.setBorderBottom(CellStyle.BORDER_THIN);
|
||||||
|
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||||
|
Font dataFont = wb.createFont();
|
||||||
|
dataFont.setFontName("Arial");
|
||||||
|
dataFont.setFontHeightInPoints((short) 10);
|
||||||
|
style.setFont(dataFont);
|
||||||
|
styles.put("data", style);
|
||||||
|
|
||||||
|
style = wb.createCellStyle();
|
||||||
|
style.cloneStyleFrom(styles.get("data"));
|
||||||
|
style.setAlignment(CellStyle.ALIGN_LEFT);
|
||||||
|
styles.put("data1", style);
|
||||||
|
|
||||||
|
style = wb.createCellStyle();
|
||||||
|
style.cloneStyleFrom(styles.get("data"));
|
||||||
|
style.setAlignment(CellStyle.ALIGN_CENTER);
|
||||||
|
styles.put("data2", style);
|
||||||
|
|
||||||
|
style = wb.createCellStyle();
|
||||||
|
style.cloneStyleFrom(styles.get("data"));
|
||||||
|
style.setAlignment(CellStyle.ALIGN_RIGHT);
|
||||||
|
styles.put("data3", style);
|
||||||
|
|
||||||
|
style = wb.createCellStyle();
|
||||||
|
style.cloneStyleFrom(styles.get("data"));
|
||||||
|
// style.setWrapText(true);
|
||||||
|
style.setAlignment(CellStyle.ALIGN_CENTER);
|
||||||
|
style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
||||||
|
style.setFillPattern(CellStyle.SOLID_FOREGROUND);
|
||||||
|
Font headerFont = wb.createFont();
|
||||||
|
headerFont.setFontName("Arial");
|
||||||
|
headerFont.setFontHeightInPoints((short) 10);
|
||||||
|
headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
|
||||||
|
headerFont.setColor(IndexedColors.WHITE.getIndex());
|
||||||
|
style.setFont(headerFont);
|
||||||
|
styles.put("header", style);
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一行
|
||||||
|
* @return 行对象
|
||||||
|
*/
|
||||||
|
public Row addRow(){
|
||||||
|
return sheet.createRow(rownum++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个单元格
|
||||||
|
* @param row 添加的行
|
||||||
|
* @param column 添加列号
|
||||||
|
* @param val 添加值
|
||||||
|
* @return 单元格对象
|
||||||
|
*/
|
||||||
|
public Cell addCell(Row row, int column, Object val){
|
||||||
|
return this.addCell(row, column, val, 0, Class.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个单元格
|
||||||
|
* @param row 添加的行
|
||||||
|
* @param column 添加列号
|
||||||
|
* @param val 添加值
|
||||||
|
* @param align 对齐方式(1:靠左;2:居中;3:靠右)
|
||||||
|
* @return 单元格对象
|
||||||
|
*/
|
||||||
|
public Cell addCell(Row row, int column, Object val, int align, Class<?> fieldType){
|
||||||
|
Cell cell = row.createCell(column);
|
||||||
|
CellStyle style = styles.get("data"+(align>=1&&align<=3?align:""));
|
||||||
|
try {
|
||||||
|
if (val == null){
|
||||||
|
cell.setCellValue("");
|
||||||
|
} else if (val instanceof String) {
|
||||||
|
cell.setCellValue((String) val);
|
||||||
|
} else if (val instanceof Integer) {
|
||||||
|
cell.setCellValue((Integer) val);
|
||||||
|
} else if (val instanceof Long) {
|
||||||
|
cell.setCellValue((Long) val);
|
||||||
|
} else if (val instanceof Double) {
|
||||||
|
cell.setCellValue((Double) val);
|
||||||
|
} else if (val instanceof Float) {
|
||||||
|
cell.setCellValue((Float) val);
|
||||||
|
} else if (val instanceof Date) {
|
||||||
|
DataFormat format = wb.createDataFormat();
|
||||||
|
style.setDataFormat(format.getFormat("yyyy-MM-dd"));
|
||||||
|
cell.setCellValue((Date) val);
|
||||||
|
} else {
|
||||||
|
if (fieldType != Class.class){
|
||||||
|
cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val));
|
||||||
|
}else{
|
||||||
|
cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
|
||||||
|
"fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString());
|
||||||
|
cell.setCellValue(val.toString());
|
||||||
|
}
|
||||||
|
cell.setCellStyle(style);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加数据(通过annotation.ExportField添加数据)
|
||||||
|
* @return list 数据列表
|
||||||
|
*/
|
||||||
|
public <E> ExportExcel setDataList(List<E> list){
|
||||||
|
for (E e : list){
|
||||||
|
int colunm = 0;
|
||||||
|
Row row = this.addRow();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Object[] os : annotationList){
|
||||||
|
ExcelField ef = (ExcelField)os[0];
|
||||||
|
Object val = null;
|
||||||
|
try{
|
||||||
|
if (StringUtils.isNotBlank(ef.value())){
|
||||||
|
val = Reflections.invokeGetter(e, ef.value());
|
||||||
|
}else{
|
||||||
|
if (os[1] instanceof Field){
|
||||||
|
val = Reflections.invokeGetter(e, ((Field)os[1]).getName());
|
||||||
|
}else if (os[1] instanceof Method){
|
||||||
|
val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(Exception ex) {
|
||||||
|
log.info(ex.toString());
|
||||||
|
val = "";
|
||||||
|
}
|
||||||
|
this.addCell(row, colunm++, val, ef.align(), ef.fieldType());
|
||||||
|
sb.append(val + ", ");
|
||||||
|
}
|
||||||
|
log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出数据流
|
||||||
|
* @param os 输出数据流
|
||||||
|
*/
|
||||||
|
public ExportExcel write(OutputStream os) throws IOException{
|
||||||
|
wb.write(os);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出到客户端
|
||||||
|
* @param fileName 输出文件名
|
||||||
|
*/
|
||||||
|
public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{
|
||||||
|
response.reset();
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
response.setContentType("application/octet-stream; charset=utf-8");
|
||||||
|
response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName, "utf-8"));
|
||||||
|
write(response.getOutputStream());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理临时文件
|
||||||
|
*/
|
||||||
|
public ExportExcel dispose(){
|
||||||
|
wb.dispose();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,303 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
|
||||||
|
*/
|
||||||
|
package com.yf.exam.core.utils.excel;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.yf.exam.core.utils.Reflections;
|
||||||
|
import com.yf.exam.core.utils.excel.annotation.ExcelField;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入Excel文件(支持“XLS”和“XLSX”格式)
|
||||||
|
* @author jeeplus
|
||||||
|
* @version 2016-03-10
|
||||||
|
*/
|
||||||
|
public class ImportExcel {
|
||||||
|
|
||||||
|
private static Logger log = LoggerFactory.getLogger(ImportExcel.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作薄对象
|
||||||
|
*/
|
||||||
|
private Workbook wb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作表对象
|
||||||
|
*/
|
||||||
|
private Sheet sheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标题行号
|
||||||
|
*/
|
||||||
|
private int headerNum;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param multipartFile 导入文件对象
|
||||||
|
* @param headerNum 标题行号,数据行号=标题行号+1
|
||||||
|
* @param sheetIndex 工作表编号
|
||||||
|
* @throws InvalidFormatException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex)
|
||||||
|
throws InvalidFormatException, IOException {
|
||||||
|
this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param is 导入文件对象
|
||||||
|
* @param headerNum 标题行号,数据行号=标题行号+1
|
||||||
|
* @param sheetIndex 工作表编号
|
||||||
|
* @throws InvalidFormatException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex)
|
||||||
|
throws IOException {
|
||||||
|
if (StringUtils.isBlank(fileName)){
|
||||||
|
throw new RuntimeException("导入文档为空!");
|
||||||
|
}else if(fileName.toLowerCase().endsWith("xls")){
|
||||||
|
this.wb = new HSSFWorkbook(is);
|
||||||
|
}else if(fileName.toLowerCase().endsWith("xlsx")){
|
||||||
|
this.wb = new XSSFWorkbook(is);
|
||||||
|
}else{
|
||||||
|
throw new RuntimeException("文档格式不正确!");
|
||||||
|
}
|
||||||
|
if (this.wb.getNumberOfSheets()<sheetIndex){
|
||||||
|
throw new RuntimeException("文档中没有工作表!");
|
||||||
|
}
|
||||||
|
this.sheet = this.wb.getSheetAt(sheetIndex);
|
||||||
|
this.headerNum = headerNum;
|
||||||
|
log.debug("Initialize success.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取行对象
|
||||||
|
* @param rownum
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Row getRow(int rownum){
|
||||||
|
return this.sheet.getRow(rownum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据行号
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getDataRowNum(){
|
||||||
|
return headerNum+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最后一个数据行号
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getLastDataRowNum(){
|
||||||
|
return this.sheet.getLastRowNum()+headerNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单元格值
|
||||||
|
* @param row 获取的行
|
||||||
|
* @param column 获取单元格列号
|
||||||
|
* @return 单元格值
|
||||||
|
*/
|
||||||
|
public Object getCellValue(Row row, int column) {
|
||||||
|
Object val = "";
|
||||||
|
try {
|
||||||
|
Cell cell = row.getCell(column);
|
||||||
|
if (cell != null) {
|
||||||
|
if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
|
||||||
|
// 当excel 中的数据为数值或日期是需要特殊处理
|
||||||
|
if (HSSFDateUtil.isCellDateFormatted(cell)) {
|
||||||
|
double d = cell.getNumericCellValue();
|
||||||
|
Date date = HSSFDateUtil.getJavaDate(d);
|
||||||
|
SimpleDateFormat dformat = new SimpleDateFormat(
|
||||||
|
"yyyy-MM-dd");
|
||||||
|
val = dformat.format(date);
|
||||||
|
} else {
|
||||||
|
NumberFormat nf = NumberFormat.getInstance();
|
||||||
|
nf.setGroupingUsed(false);// true时的格式:1,234,567,890
|
||||||
|
val = nf.format(cell.getNumericCellValue());// 数值类型的数据为double,所以需要转换一下
|
||||||
|
}
|
||||||
|
} else if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
|
||||||
|
val = cell.getStringCellValue();
|
||||||
|
} else if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
|
||||||
|
val = cell.getCellFormula();
|
||||||
|
} else if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
|
||||||
|
val = cell.getBooleanCellValue();
|
||||||
|
} else if (cell.getCellType() == Cell.CELL_TYPE_ERROR) {
|
||||||
|
val = cell.getErrorCellValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导入数据列表
|
||||||
|
* @param cls 导入对象类型
|
||||||
|
* @param groups 导入分组
|
||||||
|
*/
|
||||||
|
public <E> List<E> getDataList(Class<E> cls, int... groups) throws InstantiationException, IllegalAccessException{
|
||||||
|
List<Object[]> annotationList = Lists.newArrayList();
|
||||||
|
// Get annotation field
|
||||||
|
Field[] fs = cls.getDeclaredFields();
|
||||||
|
for (Field f : fs){
|
||||||
|
ExcelField ef = f.getAnnotation(ExcelField.class);
|
||||||
|
if (ef != null && (ef.type()==0 || ef.type()==2)){
|
||||||
|
if (groups!=null && groups.length>0){
|
||||||
|
boolean inGroup = false;
|
||||||
|
for (int g : groups){
|
||||||
|
if (inGroup){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int efg : ef.groups()){
|
||||||
|
if (g == efg){
|
||||||
|
inGroup = true;
|
||||||
|
annotationList.add(new Object[]{ef, f});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
annotationList.add(new Object[]{ef, f});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get annotation method
|
||||||
|
Method[] ms = cls.getDeclaredMethods();
|
||||||
|
for (Method m : ms){
|
||||||
|
ExcelField ef = m.getAnnotation(ExcelField.class);
|
||||||
|
if (ef != null && (ef.type()==0 || ef.type()==2)){
|
||||||
|
if (groups!=null && groups.length>0){
|
||||||
|
boolean inGroup = false;
|
||||||
|
for (int g : groups){
|
||||||
|
if (inGroup){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (int efg : ef.groups()){
|
||||||
|
if (g == efg){
|
||||||
|
inGroup = true;
|
||||||
|
annotationList.add(new Object[]{ef, m});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
annotationList.add(new Object[]{ef, m});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Field sorting
|
||||||
|
Collections.sort(annotationList, new Comparator<Object[]>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Object[] o1, Object[] o2) {
|
||||||
|
return new Integer(((ExcelField)o1[0]).sort()).compareTo(
|
||||||
|
new Integer(((ExcelField)o2[0]).sort()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Get excel data
|
||||||
|
List<E> dataList = Lists.newArrayList();
|
||||||
|
for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) {
|
||||||
|
E e = (E)cls.newInstance();
|
||||||
|
int column = 0;
|
||||||
|
Row row = this.getRow(i);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Object[] os : annotationList){
|
||||||
|
Object val = this.getCellValue(row, column++);
|
||||||
|
if (val != null){
|
||||||
|
ExcelField ef = (ExcelField)os[0];
|
||||||
|
// Get param type and type cast
|
||||||
|
Class<?> valType = Class.class;
|
||||||
|
if (os[1] instanceof Field){
|
||||||
|
valType = ((Field)os[1]).getType();
|
||||||
|
}else if (os[1] instanceof Method){
|
||||||
|
Method method = ((Method)os[1]);
|
||||||
|
if ("get".equals(method.getName().substring(0, 3))){
|
||||||
|
valType = method.getReturnType();
|
||||||
|
}else if("set".equals(method.getName().substring(0, 3))){
|
||||||
|
valType = ((Method)os[1]).getParameterTypes()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//log.debug("Import value type: ["+i+","+column+"] " + valType);
|
||||||
|
try {
|
||||||
|
//如果导入的java对象,需要在这里自己进行变换。
|
||||||
|
if (valType == String.class){
|
||||||
|
String s = String.valueOf(val.toString());
|
||||||
|
if(StringUtils.endsWith(s, ".0")){
|
||||||
|
val = StringUtils.substringBefore(s, ".0");
|
||||||
|
}else{
|
||||||
|
val = String.valueOf(val.toString());
|
||||||
|
}
|
||||||
|
}else if (valType == Integer.class){
|
||||||
|
val = Double.valueOf(val.toString()).intValue();
|
||||||
|
}else if (valType == Long.class){
|
||||||
|
val = Double.valueOf(val.toString()).longValue();
|
||||||
|
}else if (valType == Double.class){
|
||||||
|
val = Double.valueOf(val.toString());
|
||||||
|
}else if (valType == Float.class){
|
||||||
|
val = Float.valueOf(val.toString());
|
||||||
|
}else if (valType == Date.class){
|
||||||
|
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
val=sdf.parse(val.toString());
|
||||||
|
}else{
|
||||||
|
if (ef.fieldType() != Class.class){
|
||||||
|
val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString());
|
||||||
|
}else{
|
||||||
|
val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
|
||||||
|
"fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.info("Get cell value ["+i+","+column+"] error: " + ex.toString());
|
||||||
|
val = null;
|
||||||
|
}
|
||||||
|
// set entity value
|
||||||
|
if (os[1] instanceof Field){
|
||||||
|
Reflections.invokeSetter(e, ((Field)os[1]).getName(), val);
|
||||||
|
}else if (os[1] instanceof Method){
|
||||||
|
String mthodName = ((Method)os[1]).getName();
|
||||||
|
if ("get".equals(mthodName.substring(0, 3))){
|
||||||
|
mthodName = "set"+StringUtils.substringAfter(mthodName, "get");
|
||||||
|
}
|
||||||
|
Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(val+", ");
|
||||||
|
}
|
||||||
|
dataList.add(e);
|
||||||
|
log.debug("Read success: ["+i+"] "+sb.toString());
|
||||||
|
}
|
||||||
|
return dataList;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
|
||||||
|
*/
|
||||||
|
package com.yf.exam.core.utils.excel.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel注解定义
|
||||||
|
* @author jeeplus
|
||||||
|
* @version 2016-03-10
|
||||||
|
*/
|
||||||
|
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ExcelField {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段名(默认调用当前字段的“get”方法,如指定导出字段为对象,请填写“对象名.对象属性”,例:“area.name”、“office.name”)
|
||||||
|
*/
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段标题(需要添加批注请用“**”分隔,标题**批注,仅对导出模板有效)
|
||||||
|
*/
|
||||||
|
String title();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段类型(0:导出导入;1:仅导出;2:仅导入)
|
||||||
|
*/
|
||||||
|
int type() default 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段对齐方式(0:自动;1:靠左;2:居中;3:靠右)
|
||||||
|
*/
|
||||||
|
int align() default 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字段字段排序(升序)
|
||||||
|
*/
|
||||||
|
int sort() default 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果是字典类型,请设置字典的type值
|
||||||
|
*/
|
||||||
|
String dictType() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反射类型
|
||||||
|
*/
|
||||||
|
Class<?> fieldType() default Class.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段归属组(根据分组导出导入)
|
||||||
|
*/
|
||||||
|
int[] groups() default {};
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
|
||||||
|
*/
|
||||||
|
package com.yf.exam.core.utils.excel.fieldtype;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.yf.exam.core.utils.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段类型转换
|
||||||
|
* @author jeeplus
|
||||||
|
* @version 2016-5-29
|
||||||
|
*/
|
||||||
|
public class ListType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对象值(导入)
|
||||||
|
*/
|
||||||
|
public static Object getValue(String val) {
|
||||||
|
List<String> list = Lists.newArrayList();
|
||||||
|
if(!StringUtils.isBlank(val)) {
|
||||||
|
for (String s : val.split(",")) {
|
||||||
|
list.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置对象值(导出)
|
||||||
|
*/
|
||||||
|
public static String setValue(Object val) {
|
||||||
|
if (val != null){
|
||||||
|
List<String> list = (List<String>)val;
|
||||||
|
StringBuffer sb = null;
|
||||||
|
for (String item: list){
|
||||||
|
if(StringUtils.isBlank(item)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(sb == null){
|
||||||
|
sb = new StringBuffer(item);
|
||||||
|
}else{
|
||||||
|
sb.append(",").append(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sb!=null) {
|
||||||
|
return sb.toString().replace("[]", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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,118 @@
|
|||||||
|
package com.yf.exam.core.utils.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||||
|
import com.yf.exam.core.exception.ServiceException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON工具类
|
||||||
|
* @author van
|
||||||
|
*/
|
||||||
|
public class JsonHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为字符串
|
||||||
|
* @param obj
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String toJson(Object obj) {
|
||||||
|
|
||||||
|
ObjectMapper mapper = getMapper();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return mapper.writeValueAsString(obj);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符转换为java对象
|
||||||
|
* @param json
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject(String json, Class<T> clazz) {
|
||||||
|
ObjectMapper mapper = getMapper();
|
||||||
|
try {
|
||||||
|
return mapper.readValue(json, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将对象转换为另外一个对象
|
||||||
|
* @param object
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject(Object object, Class<T> clazz) {
|
||||||
|
ObjectMapper mapper = getMapper();
|
||||||
|
try {
|
||||||
|
return mapper.readValue(toJson(object), clazz);
|
||||||
|
} catch (InvalidFormatException e){
|
||||||
|
throw new ServiceException("数据格式存在错误!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复杂对象的转换
|
||||||
|
* @param object
|
||||||
|
* @param typeReference
|
||||||
|
* @return
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject(Object object, TypeReference<T> typeReference) {
|
||||||
|
ObjectMapper mapper = getMapper();
|
||||||
|
try {
|
||||||
|
return mapper.readValue(toJson(object), typeReference);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复杂对象的转换
|
||||||
|
* @param json
|
||||||
|
* @param typeReference
|
||||||
|
* @return
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject(String json, TypeReference<T> typeReference) {
|
||||||
|
ObjectMapper mapper = getMapper();
|
||||||
|
try {
|
||||||
|
return mapper.readValue(json, typeReference);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取转换配置
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static ObjectMapper getMapper(){
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.setDateFormat(new MyDateFormat());
|
||||||
|
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
|
||||||
|
|
||||||
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
|
||||||
|
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||||
|
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package com.yf.exam.core.utils.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON时间格式类,用于兼容不同类型日期
|
||||||
|
* @author van
|
||||||
|
*/
|
||||||
|
public class MyDateFormat extends SimpleDateFormat {
|
||||||
|
|
||||||
|
private StdDateFormat stdDateFormat = new StdDateFormat();
|
||||||
|
|
||||||
|
public MyDateFormat() {
|
||||||
|
// 设置默认的日期格式
|
||||||
|
super("yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date parse(String dateStr) throws ParseException {
|
||||||
|
if (dateStr != null && !dateStr.contains("T")) {
|
||||||
|
return super.parse(dateStr);
|
||||||
|
}
|
||||||
|
return stdDateFormat.parse(dateStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
MyDateFormat other = (MyDateFormat)super.clone();
|
||||||
|
other.stdDateFormat = new StdDateFormat();
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.yf.exam.core.utils.passwd;
|
||||||
|
|
||||||
|
|
||||||
|
import com.yf.exam.core.utils.file.Md5Util;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的密码处理类,用于生成密码和校验密码
|
||||||
|
* ClassName: PassGenerator <br/>
|
||||||
|
* date: 2017年12月13日 下午7:13:03 <br/>
|
||||||
|
*
|
||||||
|
* @author Bool
|
||||||
|
* @version
|
||||||
|
*/
|
||||||
|
public class PassHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checkPass:校验密码是否一致
|
||||||
|
* @author Bool
|
||||||
|
* @param inputPass 用户传入的密码
|
||||||
|
* @param salt 数据库保存的密码随机码
|
||||||
|
* @param pass 数据库保存的密码MD5
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean checkPass(String inputPass , String salt , String pass){
|
||||||
|
String pwdMd5 = Md5Util.md5(inputPass);
|
||||||
|
return Md5Util.md5(pwdMd5 + salt).equals(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* buildPassword:用于用户注册时产生一个密码
|
||||||
|
* @author Bool
|
||||||
|
* @param inputPass 输入的密码
|
||||||
|
* @return PassInfo 返回一个密码对象,记得保存
|
||||||
|
*/
|
||||||
|
public static PassInfo buildPassword(String inputPass) {
|
||||||
|
|
||||||
|
//产生一个6位数的随机码
|
||||||
|
String salt = RandomStringUtils.randomAlphabetic(6);
|
||||||
|
//加密后的密码
|
||||||
|
String encryptPassword = Md5Util.md5(Md5Util.md5(inputPass)+salt);
|
||||||
|
//返回对象
|
||||||
|
return new PassInfo(salt,encryptPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
PassInfo info = buildPassword("190601");
|
||||||
|
|
||||||
|
System.out.println(info.getPassword());
|
||||||
|
System.out.println(info.getSalt());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
14
exam-admin/src/main/java/com/yf/exam/modules/Constant.java
Normal file
14
exam-admin/src/main/java/com/yf/exam/modules/Constant.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.yf.exam.modules.exam.dto.response;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.exam.dto.ExamDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试分页响应类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-07-25 16:18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类")
|
||||||
|
public class ExamOnlineRespDTO extends ExamDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.yf.exam.modules.exam.dto.response;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.exam.dto.ExamDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试分页响应类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-07-25 16:18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类")
|
||||||
|
public class ExamReviewRespDTO extends ExamDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "考试人数", required=true)
|
||||||
|
private Integer examUser;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "待阅试卷", required=true)
|
||||||
|
private Integer unreadPaper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package com.yf.exam.modules.exam.entity;
|
||||||
|
|
||||||
|
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 com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试实体类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-07-25 16:18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("el_exam")
|
||||||
|
public class Exam extends Model<Exam> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试名称
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试描述
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1公开2部门3定员
|
||||||
|
*/
|
||||||
|
@TableField("open_type")
|
||||||
|
private Integer openType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试状态
|
||||||
|
*/
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否限时
|
||||||
|
*/
|
||||||
|
@TableField("time_limit")
|
||||||
|
private Boolean timeLimit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间
|
||||||
|
*/
|
||||||
|
@TableField("start_time")
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束时间
|
||||||
|
*/
|
||||||
|
@TableField("end_time")
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField("create_time")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField("update_time")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总分数
|
||||||
|
*/
|
||||||
|
@TableField("total_score")
|
||||||
|
private Integer totalScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总时长(分钟)
|
||||||
|
*/
|
||||||
|
@TableField("total_time")
|
||||||
|
private Integer totalTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 及格分数
|
||||||
|
*/
|
||||||
|
@TableField("qualify_score")
|
||||||
|
private Integer qualifyScore;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.yf.exam.modules.exam.entity;
|
||||||
|
|
||||||
|
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 com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试部门实体类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-03 17:24
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("el_exam_depart")
|
||||||
|
public class ExamDepart extends Model<ExamDepart> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试ID
|
||||||
|
*/
|
||||||
|
@TableField("exam_id")
|
||||||
|
private String examId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID
|
||||||
|
*/
|
||||||
|
@TableField("depart_id")
|
||||||
|
private String departId;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
package com.yf.exam.modules.exam.entity;
|
||||||
|
|
||||||
|
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 com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试题库实体类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-05 11:14
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("el_exam_repo")
|
||||||
|
public class ExamRepo extends Model<ExamRepo> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试ID
|
||||||
|
*/
|
||||||
|
@TableField("exam_id")
|
||||||
|
private String examId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题库ID
|
||||||
|
*/
|
||||||
|
@TableField("repo_id")
|
||||||
|
private String repoId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单选题数量
|
||||||
|
*/
|
||||||
|
@TableField("radio_count")
|
||||||
|
private Integer radioCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单选题分数
|
||||||
|
*/
|
||||||
|
@TableField("radio_score")
|
||||||
|
private Integer radioScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多选题数量
|
||||||
|
*/
|
||||||
|
@TableField("multi_count")
|
||||||
|
private Integer multiCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多选题分数
|
||||||
|
*/
|
||||||
|
@TableField("multi_score")
|
||||||
|
private Integer multiScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断题数量
|
||||||
|
*/
|
||||||
|
@TableField("judge_count")
|
||||||
|
private Integer judgeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断题分数
|
||||||
|
*/
|
||||||
|
@TableField("judge_score")
|
||||||
|
private Integer judgeScore;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.yf.exam.modules.exam.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yf.exam.modules.exam.entity.ExamDepart;
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试部门Mapper
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-03 17:24
|
||||||
|
*/
|
||||||
|
public interface ExamDepartMapper extends BaseMapper<ExamDepart> {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.yf.exam.modules.exam.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.yf.exam.modules.exam.dto.ExamDTO;
|
||||||
|
import com.yf.exam.modules.exam.dto.response.ExamReviewRespDTO;
|
||||||
|
import com.yf.exam.modules.exam.dto.response.ExamOnlineRespDTO;
|
||||||
|
import com.yf.exam.modules.exam.entity.Exam;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试Mapper
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-07-25 16:18
|
||||||
|
*/
|
||||||
|
public interface ExamMapper extends BaseMapper<Exam> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找分页内容
|
||||||
|
* @param page
|
||||||
|
* @param query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ExamDTO> paging(Page page, @Param("query") ExamDTO query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找分页内容
|
||||||
|
* @param page
|
||||||
|
* @param query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ExamReviewRespDTO> reviewPaging(Page page, @Param("query") ExamDTO query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在线考试分页响应类-考生视角
|
||||||
|
* @param page
|
||||||
|
* @param query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ExamOnlineRespDTO> online(Page page, @Param("query") ExamDTO query);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.yf.exam.modules.exam.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
|
||||||
|
import com.yf.exam.modules.exam.entity.ExamRepo;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试题库Mapper
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-05 11:14
|
||||||
|
*/
|
||||||
|
public interface ExamRepoMapper extends BaseMapper<ExamRepo> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找考试题库列表
|
||||||
|
* @param examId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ExamRepoExtDTO> listByExam(@Param("examId") String examId);
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.yf.exam.modules.exam.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.yf.exam.modules.exam.entity.ExamDepart;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试部门业务类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-03 17:24
|
||||||
|
*/
|
||||||
|
public interface ExamDepartService extends IService<ExamDepart> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存全部
|
||||||
|
* @param examId
|
||||||
|
* @param departs
|
||||||
|
*/
|
||||||
|
void saveAll(String examId, List<String> departs);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据考试查找对应的部门
|
||||||
|
* @param examId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<String> listByExam(String examId);
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.yf.exam.modules.exam.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
|
||||||
|
import com.yf.exam.modules.exam.entity.ExamRepo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试题库业务类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-05 11:14
|
||||||
|
*/
|
||||||
|
public interface ExamRepoService extends IService<ExamRepo> {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存全部
|
||||||
|
* @param examId
|
||||||
|
* @param list
|
||||||
|
*/
|
||||||
|
void saveAll(String examId, List<ExamRepoExtDTO> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找考试题库列表
|
||||||
|
* @param examId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ExamRepoExtDTO> listByExam(String examId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理脏数据
|
||||||
|
* @param examId
|
||||||
|
*/
|
||||||
|
void clear(String examId);
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package com.yf.exam.modules.exam.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试业务类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-07-25 16:18
|
||||||
|
*/
|
||||||
|
public interface ExamService extends IService<Exam> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存考试信息
|
||||||
|
* @param reqDTO
|
||||||
|
*/
|
||||||
|
void save(ExamSaveReqDTO reqDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找考试详情
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ExamSaveReqDTO findDetail(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找考试详情--简要信息
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ExamDTO findById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询数据
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在线考试分页响应类-考生视角
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待阅试卷列表
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO);
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
package com.yf.exam.modules.exam.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yf.exam.core.exception.ServiceException;
|
||||||
|
import com.yf.exam.modules.exam.entity.ExamDepart;
|
||||||
|
import com.yf.exam.modules.exam.mapper.ExamDepartMapper;
|
||||||
|
import com.yf.exam.modules.exam.service.ExamDepartService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试部门业务实现类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-03 17:24
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ExamDepartServiceImpl extends ServiceImpl<ExamDepartMapper, ExamDepart> implements ExamDepartService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveAll(String examId, List<String> departs) {
|
||||||
|
|
||||||
|
// 先删除
|
||||||
|
QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.lambda().eq(ExamDepart::getExamId, examId);
|
||||||
|
this.remove(wrapper);
|
||||||
|
|
||||||
|
// 再增加
|
||||||
|
if(CollectionUtils.isEmpty(departs)){
|
||||||
|
throw new ServiceException(1, "请至少选择选择一个部门!!");
|
||||||
|
}
|
||||||
|
List<ExamDepart> list = new ArrayList<>();
|
||||||
|
|
||||||
|
for(String id: departs){
|
||||||
|
ExamDepart depart = new ExamDepart();
|
||||||
|
depart.setDepartId(id);
|
||||||
|
depart.setExamId(examId);
|
||||||
|
list.add(depart);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveBatch(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> listByExam(String examId) {
|
||||||
|
// 先删除
|
||||||
|
QueryWrapper<ExamDepart> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.lambda().eq(ExamDepart::getExamId, examId);
|
||||||
|
List<ExamDepart> list = this.list(wrapper);
|
||||||
|
List<String> ids = new ArrayList<>();
|
||||||
|
if(!CollectionUtils.isEmpty(list)){
|
||||||
|
for(ExamDepart item: list){
|
||||||
|
ids.add(item.getDepartId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.yf.exam.modules.exam.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yf.exam.core.exception.ServiceException;
|
||||||
|
import com.yf.exam.core.utils.BeanMapper;
|
||||||
|
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
|
||||||
|
import com.yf.exam.modules.exam.entity.ExamRepo;
|
||||||
|
import com.yf.exam.modules.exam.mapper.ExamRepoMapper;
|
||||||
|
import com.yf.exam.modules.exam.service.ExamRepoService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试题库业务实现类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-09-05 11:14
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ExamRepoServiceImpl extends ServiceImpl<ExamRepoMapper, ExamRepo> implements ExamRepoService {
|
||||||
|
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@Override
|
||||||
|
public void saveAll(String examId, List<ExamRepoExtDTO> list) {
|
||||||
|
|
||||||
|
// 先删除
|
||||||
|
QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.lambda().eq(ExamRepo::getExamId, examId);
|
||||||
|
this.remove(wrapper);
|
||||||
|
|
||||||
|
// 再增加
|
||||||
|
if(CollectionUtils.isEmpty(list)){
|
||||||
|
throw new ServiceException(1, "必须选择题库!");
|
||||||
|
}
|
||||||
|
List<ExamRepo> repos = BeanMapper.mapList(list, ExamRepo.class);
|
||||||
|
for(ExamRepo item: repos){
|
||||||
|
item.setExamId(examId);
|
||||||
|
item.setId(IdWorker.getIdStr());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveBatch(repos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExamRepoExtDTO> listByExam(String examId) {
|
||||||
|
return baseMapper.listByExam(examId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear(String examId) {
|
||||||
|
|
||||||
|
// 先删除
|
||||||
|
QueryWrapper<ExamRepo> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.lambda().eq(ExamRepo::getExamId, examId);
|
||||||
|
this.remove(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
package com.yf.exam.modules.exam.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yf.exam.core.api.dto.PagingReqDTO;
|
||||||
|
import com.yf.exam.core.enums.OpenType;
|
||||||
|
import com.yf.exam.core.exception.ServiceException;
|
||||||
|
import com.yf.exam.core.utils.BeanMapper;
|
||||||
|
import com.yf.exam.modules.exam.dto.ExamDTO;
|
||||||
|
import com.yf.exam.modules.exam.dto.ExamRepoDTO;
|
||||||
|
import com.yf.exam.modules.exam.dto.ext.ExamRepoExtDTO;
|
||||||
|
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.mapper.ExamMapper;
|
||||||
|
import com.yf.exam.modules.exam.service.ExamDepartService;
|
||||||
|
import com.yf.exam.modules.exam.service.ExamRepoService;
|
||||||
|
import com.yf.exam.modules.exam.service.ExamService;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 考试业务实现类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-07-25 16:18
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements ExamService {
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ExamRepoService examRepoService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ExamDepartService examDepartService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(ExamSaveReqDTO reqDTO) {
|
||||||
|
|
||||||
|
// ID
|
||||||
|
String id = reqDTO.getId();
|
||||||
|
|
||||||
|
if(StringUtils.isBlank(id)){
|
||||||
|
id = IdWorker.getIdStr();
|
||||||
|
}
|
||||||
|
|
||||||
|
//复制参数
|
||||||
|
Exam entity = new Exam();
|
||||||
|
|
||||||
|
// 计算分值
|
||||||
|
this.calcScore(reqDTO);
|
||||||
|
|
||||||
|
|
||||||
|
// 复制基本数据
|
||||||
|
BeanMapper.copy(reqDTO, entity);
|
||||||
|
entity.setId(id);
|
||||||
|
|
||||||
|
// 修复状态
|
||||||
|
if (reqDTO.getTimeLimit()!=null
|
||||||
|
&& !reqDTO.getTimeLimit()
|
||||||
|
&& reqDTO.getState()!=null
|
||||||
|
&& reqDTO.getState() == 2) {
|
||||||
|
entity.setState(0);
|
||||||
|
} else {
|
||||||
|
entity.setState(reqDTO.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 题库组卷
|
||||||
|
try {
|
||||||
|
examRepoService.saveAll(id, reqDTO.getRepoList());
|
||||||
|
}catch (DuplicateKeyException e){
|
||||||
|
throw new ServiceException(1, "不能选择重复的题库!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 开放的部门
|
||||||
|
if(OpenType.DEPT_OPEN.equals(reqDTO.getOpenType())){
|
||||||
|
examDepartService.saveAll(id, reqDTO.getDepartIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveOrUpdate(entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExamSaveReqDTO findDetail(String id) {
|
||||||
|
ExamSaveReqDTO respDTO = new ExamSaveReqDTO();
|
||||||
|
Exam exam = this.getById(id);
|
||||||
|
BeanMapper.copy(exam, respDTO);
|
||||||
|
|
||||||
|
// 考试部门
|
||||||
|
List<String> departIds = examDepartService.listByExam(id);
|
||||||
|
respDTO.setDepartIds(departIds);
|
||||||
|
|
||||||
|
// 题库
|
||||||
|
List<ExamRepoExtDTO> repos = examRepoService.listByExam(id);
|
||||||
|
respDTO.setRepoList(repos);
|
||||||
|
|
||||||
|
return respDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExamDTO findById(String id) {
|
||||||
|
ExamDTO respDTO = new ExamDTO();
|
||||||
|
Exam exam = this.getById(id);
|
||||||
|
BeanMapper.copy(exam, respDTO);
|
||||||
|
return respDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<ExamDTO> paging(PagingReqDTO<ExamDTO> reqDTO) {
|
||||||
|
|
||||||
|
//创建分页对象
|
||||||
|
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
|
||||||
|
|
||||||
|
//转换结果
|
||||||
|
IPage<ExamDTO> pageData = baseMapper.paging(page, reqDTO.getParams());
|
||||||
|
return pageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<ExamOnlineRespDTO> onlinePaging(PagingReqDTO<ExamDTO> reqDTO) {
|
||||||
|
|
||||||
|
|
||||||
|
// 创建分页对象
|
||||||
|
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
|
||||||
|
|
||||||
|
// 查找分页
|
||||||
|
IPage<ExamOnlineRespDTO> pageData = baseMapper.online(page, reqDTO.getParams());
|
||||||
|
|
||||||
|
return pageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<ExamReviewRespDTO> reviewPaging(PagingReqDTO<ExamDTO> reqDTO) {
|
||||||
|
// 创建分页对象
|
||||||
|
Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
|
||||||
|
|
||||||
|
// 查找分页
|
||||||
|
IPage<ExamReviewRespDTO> pageData = baseMapper.reviewPaging(page, reqDTO.getParams());
|
||||||
|
|
||||||
|
return pageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算分值
|
||||||
|
* @param reqDTO
|
||||||
|
*/
|
||||||
|
private void calcScore(ExamSaveReqDTO reqDTO){
|
||||||
|
|
||||||
|
// 主观题分数
|
||||||
|
int objScore = 0;
|
||||||
|
|
||||||
|
// 题库组卷
|
||||||
|
List<ExamRepoExtDTO> repoList = reqDTO.getRepoList();
|
||||||
|
|
||||||
|
for(ExamRepoDTO item: repoList){
|
||||||
|
if(item.getRadioCount()!=null
|
||||||
|
&& item.getRadioCount()>0
|
||||||
|
&& item.getRadioScore()!=null
|
||||||
|
&& item.getRadioScore()>0){
|
||||||
|
objScore+=item.getRadioCount()*item.getRadioScore();
|
||||||
|
}
|
||||||
|
if(item.getMultiCount()!=null
|
||||||
|
&& item.getMultiCount()>0
|
||||||
|
&& item.getMultiScore()!=null
|
||||||
|
&& item.getMultiScore()>0){
|
||||||
|
objScore+=item.getMultiCount()*item.getMultiScore();
|
||||||
|
}
|
||||||
|
if(item.getJudgeCount()!=null
|
||||||
|
&& item.getJudgeCount()>0
|
||||||
|
&& item.getJudgeScore()!=null
|
||||||
|
&& item.getJudgeScore()>0){
|
||||||
|
objScore+=item.getJudgeCount()*item.getJudgeScore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
reqDTO.setTotalScore(objScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
package com.yf.exam.modules.paper.controller;
|
||||||
|
|
||||||
|
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.BaseIdRespDTO;
|
||||||
|
import com.yf.exam.core.api.dto.BaseIdsReqDTO;
|
||||||
|
import com.yf.exam.core.api.dto.PagingReqDTO;
|
||||||
|
import com.yf.exam.core.utils.BeanMapper;
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.request.PaperAnswerDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.request.PaperCreateReqDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.request.PaperListReqDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.request.PaperQuQueryDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.response.ExamDetailRespDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.response.ExamResultRespDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.response.PaperListRespDTO;
|
||||||
|
import com.yf.exam.modules.paper.entity.Paper;
|
||||||
|
import com.yf.exam.modules.paper.service.PaperService;
|
||||||
|
import com.yf.exam.modules.user.UserUtils;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷控制器
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 16:33
|
||||||
|
*/
|
||||||
|
@Api(tags={"试卷"})
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/exam/api/paper/paper")
|
||||||
|
public class PaperController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PaperService baseService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查找
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "分页查找")
|
||||||
|
@RequestMapping(value = "/paging", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<IPage<PaperListRespDTO>> paging(@RequestBody PagingReqDTO<PaperListReqDTO> reqDTO) {
|
||||||
|
//分页查询并转换
|
||||||
|
IPage<PaperListRespDTO> page = baseService.paging(reqDTO);
|
||||||
|
return super.success(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建试卷
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "创建试卷")
|
||||||
|
@RequestMapping(value = "/create-paper", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<BaseIdRespDTO> save(@RequestBody PaperCreateReqDTO reqDTO) {
|
||||||
|
//复制参数
|
||||||
|
String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId());
|
||||||
|
return super.success(new BaseIdRespDTO(paperId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "试卷详情")
|
||||||
|
@RequestMapping(value = "/paper-detail", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<ExamDetailRespDTO> paperDetail(@RequestBody BaseIdReqDTO reqDTO) {
|
||||||
|
//根据ID删除
|
||||||
|
ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId());
|
||||||
|
return super.success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "试题详情")
|
||||||
|
@RequestMapping(value = "/qu-detail", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<PaperQuDetailDTO> quDetail(@RequestBody PaperQuQueryDTO reqDTO) {
|
||||||
|
//根据ID删除
|
||||||
|
PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId());
|
||||||
|
return super.success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充答案
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "填充答案")
|
||||||
|
@RequestMapping(value = "/fill-answer", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<PaperQuDetailDTO> fillAnswer(@RequestBody PaperAnswerDTO reqDTO) {
|
||||||
|
//根据ID删除
|
||||||
|
baseService.fillAnswer(reqDTO);
|
||||||
|
return super.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交卷操作
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "交卷操作")
|
||||||
|
@RequestMapping(value = "/hand-exam", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<PaperQuDetailDTO> handleExam(@RequestBody BaseIdReqDTO reqDTO) {
|
||||||
|
//根据ID删除
|
||||||
|
baseService.handExam(reqDTO.getId());
|
||||||
|
return super.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除
|
||||||
|
* @param reqDTO
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "试卷详情")
|
||||||
|
@RequestMapping(value = "/paper-result", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<ExamResultRespDTO> paperResult(@RequestBody BaseIdReqDTO reqDTO) {
|
||||||
|
//根据ID删除
|
||||||
|
ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId());
|
||||||
|
return super.success(respDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测用户有没有中断的考试
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "检测进行中的考试")
|
||||||
|
@RequestMapping(value = "/check-process", method = { RequestMethod.POST})
|
||||||
|
public ApiRest<PaperDTO> checkProcess() {
|
||||||
|
//复制参数
|
||||||
|
PaperDTO dto = baseService.checkProcess(UserUtils.getUserId());
|
||||||
|
return super.success(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷", description="试卷")
|
||||||
|
public class PaperDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷ID", required=true)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "用户ID", required=true)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "部门ID", required=true)
|
||||||
|
private String departId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "规则ID", required=true)
|
||||||
|
private String examId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "考试标题", required=true)
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "考试时长", required=true)
|
||||||
|
private Integer totalTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "用户时长", required=true)
|
||||||
|
private Integer userTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷总分", required=true)
|
||||||
|
private Integer totalScore;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "及格分", required=true)
|
||||||
|
private Integer qualifyScore;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "客观分", required=true)
|
||||||
|
private Integer objScore;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "主观分", required=true)
|
||||||
|
private Integer subjScore;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "用户得分", required=true)
|
||||||
|
private Integer userScore;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否包含简答题", required=true)
|
||||||
|
private Boolean hasSaq;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷状态", required=true)
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "创建时间", required=true)
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "更新时间", required=true)
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "截止时间")
|
||||||
|
private Date limitTime;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷考题备选答案请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案")
|
||||||
|
public class PaperQuAnswerDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "自增ID", required=true)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷ID", required=true)
|
||||||
|
private String paperId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "回答项ID", required=true)
|
||||||
|
private String answerId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "题目ID", required=true)
|
||||||
|
private String quId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否正确项", required=true)
|
||||||
|
private Boolean isRight;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否选中", required=true)
|
||||||
|
private Boolean checked;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "排序", required=true)
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "选项标签", required=true)
|
||||||
|
private String abc;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷考题请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷考题", description="试卷考题")
|
||||||
|
public class PaperQuDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "ID", required=true)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷ID", required=true)
|
||||||
|
private String paperId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "题目ID", required=true)
|
||||||
|
private String quId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "题目类型", required=true)
|
||||||
|
private Integer quType;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否已答", required=true)
|
||||||
|
private Boolean answered;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "主观答案", required=true)
|
||||||
|
private String answer;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "问题排序", required=true)
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单题分分值", required=true)
|
||||||
|
private Integer score;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "实际得分(主观题)", required=true)
|
||||||
|
private Integer actualScore;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "是否答对", required=true)
|
||||||
|
private Boolean isRight;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.ext;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperQuAnswerDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷考题备选答案请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案")
|
||||||
|
public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试题图片", required=true)
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "答案内容", required=true)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.ext;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperQuDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷考题请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷题目详情类", description="试卷题目详情类")
|
||||||
|
public class PaperQuDetailDTO extends PaperQuDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "图片", required=true)
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "题目内容", required=true)
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "答案内容", required=true)
|
||||||
|
List<PaperQuAnswerExtDTO> answerList;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类")
|
||||||
|
public class PaperAnswerDTO extends PaperQuQueryDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "回答列表", required=true)
|
||||||
|
private List<String> answers;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "主观答案", required=true)
|
||||||
|
private String answer;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.request;
|
||||||
|
|
||||||
|
import com.yf.exam.core.api.dto.BaseDTO;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷创建请求类", description="试卷创建请求类")
|
||||||
|
public class PaperCreateReqDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "考试ID", required=true)
|
||||||
|
private String examId;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷", description="试卷")
|
||||||
|
public class PaperListReqDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "用户ID", required=true)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "部门ID", required=true)
|
||||||
|
private String departId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "规则ID", required=true)
|
||||||
|
private String examId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "用户昵称", required=true)
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷状态", required=true)
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.request;
|
||||||
|
|
||||||
|
import com.yf.exam.core.api.dto.BaseDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bool
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类")
|
||||||
|
public class PaperQuQueryDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "试卷ID", required=true)
|
||||||
|
private String paperId;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "题目ID", required=true)
|
||||||
|
private String quId;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.response;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperQuDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="考试详情", description="考试详情")
|
||||||
|
public class ExamDetailRespDTO extends PaperDTO {
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "单选题列表", required=true)
|
||||||
|
private List<PaperQuDTO> radioList;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "多选题列表", required=true)
|
||||||
|
private List<PaperQuDTO> multiList;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "判断题", required=true)
|
||||||
|
private List<PaperQuDTO> judgeList;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "剩余结束秒数", required=true)
|
||||||
|
public Long getLeftSeconds(){
|
||||||
|
|
||||||
|
// 结束时间
|
||||||
|
Calendar cl = Calendar.getInstance();
|
||||||
|
cl.setTime(this.getCreateTime());
|
||||||
|
cl.add(Calendar.MINUTE, getTotalTime());
|
||||||
|
|
||||||
|
return (cl.getTimeInMillis() - System.currentTimeMillis()) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.response;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperDTO;
|
||||||
|
import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="考试结果展示响应类", description="考试结果展示响应类")
|
||||||
|
public class ExamResultRespDTO extends PaperDTO {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "问题列表", required=true)
|
||||||
|
private List<PaperQuDetailDTO> quList;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.yf.exam.modules.paper.dto.response;
|
||||||
|
|
||||||
|
import com.yf.exam.modules.paper.dto.PaperDTO;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷请求类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value="试卷列表响应类", description="试卷列表响应类")
|
||||||
|
public class PaperListRespDTO extends PaperDTO {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "人员", required=true)
|
||||||
|
private String realName;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,125 @@
|
|||||||
|
package com.yf.exam.modules.paper.entity;
|
||||||
|
|
||||||
|
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 com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷实体类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("el_paper")
|
||||||
|
public class Paper extends Model<Paper> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 试卷ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@TableField("user_id")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID
|
||||||
|
*/
|
||||||
|
@TableField("depart_id")
|
||||||
|
private String departId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则ID
|
||||||
|
*/
|
||||||
|
@TableField("exam_id")
|
||||||
|
private String examId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考试时长
|
||||||
|
*/
|
||||||
|
@TableField("total_time")
|
||||||
|
private Integer totalTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户时长
|
||||||
|
*/
|
||||||
|
@TableField("user_time")
|
||||||
|
private Integer userTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 试卷总分
|
||||||
|
*/
|
||||||
|
@TableField("total_score")
|
||||||
|
private Integer totalScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 及格分
|
||||||
|
*/
|
||||||
|
@TableField("qualify_score")
|
||||||
|
private Integer qualifyScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客观分
|
||||||
|
*/
|
||||||
|
@TableField("obj_score")
|
||||||
|
private Integer objScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主观分
|
||||||
|
*/
|
||||||
|
@TableField("subj_score")
|
||||||
|
private Integer subjScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户得分
|
||||||
|
*/
|
||||||
|
@TableField("user_score")
|
||||||
|
private Integer userScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否包含简答题
|
||||||
|
*/
|
||||||
|
@TableField("has_saq")
|
||||||
|
private Boolean hasSaq;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 试卷状态
|
||||||
|
*/
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField("create_time")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField("update_time")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截止时间
|
||||||
|
*/
|
||||||
|
@TableField("limit_time")
|
||||||
|
private Date limitTime;
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package com.yf.exam.modules.paper.entity;
|
||||||
|
|
||||||
|
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 com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 试卷考题实体类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author 聪明笨狗
|
||||||
|
* @since 2020-05-25 17:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("el_paper_qu")
|
||||||
|
public class PaperQu extends Model<PaperQu> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 试卷ID
|
||||||
|
*/
|
||||||
|
@TableField("paper_id")
|
||||||
|
private String paperId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目ID
|
||||||
|
*/
|
||||||
|
@TableField("qu_id")
|
||||||
|
private String quId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目类型
|
||||||
|
*/
|
||||||
|
@TableField("qu_type")
|
||||||
|
private Integer quType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已答
|
||||||
|
*/
|
||||||
|
private Boolean answered;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主观答案
|
||||||
|
*/
|
||||||
|
private String answer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 问题排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单题分分值
|
||||||
|
*/
|
||||||
|
private Integer score;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际得分(主观题)
|
||||||
|
*/
|
||||||
|
@TableField("actual_score")
|
||||||
|
private Integer actualScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否答对
|
||||||
|
*/
|
||||||
|
@TableField("is_right")
|
||||||
|
private Boolean isRight;
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user