apiRest = message(ex.getCode(), ex.getMsg(), null);
+ return apiRest;
+ }
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseDTO.java
new file mode 100644
index 0000000..063e59f
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseDTO.java
@@ -0,0 +1,15 @@
+package com.yx.exam.core.api.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 请求和响应的基础类,用于处理序列化
+ * @author chenhaodong
+ * @date 2019/3/16 15:56
+ */
+@Data
+public class BaseDTO implements Serializable {
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdReqDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdReqDTO.java
new file mode 100644
index 0000000..7292837
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdReqDTO.java
@@ -0,0 +1,27 @@
+package com.yx.exam.core.api.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ *
+ * 主键通用请求类,用于根据ID查询
+ *
+ *
+ * @author chenhaodong
+ * @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;
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdRespDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdRespDTO.java
new file mode 100644
index 0000000..21ffec6
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdRespDTO.java
@@ -0,0 +1,25 @@
+package com.yx.exam.core.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ *
+ * 主键通用响应类,用于添加后返回内容
+ *
+ *
+ * @author chenhaodong
+ * @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;
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdsReqDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdsReqDTO.java
new file mode 100644
index 0000000..0556b7a
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseIdsReqDTO.java
@@ -0,0 +1,25 @@
+package com.yx.exam.core.api.dto;
+
+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 chenhaodong
+ * @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 ids;
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseStateReqDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseStateReqDTO.java
new file mode 100644
index 0000000..6dc7d67
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/BaseStateReqDTO.java
@@ -0,0 +1,31 @@
+package com.yx.exam.core.api.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ *
+ * 通用状态请求类,用于修改状态什么的
+ *
+ *
+ * @author chenhaodong
+ * @since 2019-04-20 12:15
+ */
+@Data
+@ApiModel(value="通用状态请求类", description="通用状态请求类")
+@AllArgsConstructor
+@NoArgsConstructor
+public class BaseStateReqDTO extends BaseDTO {
+
+
+ @ApiModelProperty(value = "要修改对象的ID列表", required=true)
+ private List ids;
+
+ @ApiModelProperty(value = "通用状态,0为正常,1为禁用", required=true)
+ private Integer state;
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/PagingReqDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/PagingReqDTO.java
new file mode 100644
index 0000000..8d042b4
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/PagingReqDTO.java
@@ -0,0 +1,47 @@
+package com.yx.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
+ * @author chenhaodong
+ */
+@ApiModel(value="分页参数", description="分页参数")
+@Data
+public class PagingReqDTO {
+
+
+ @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;
+ }
+
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/dto/PagingRespDTO.java b/exam-api/src/main/java/com/yx/exam/core/api/dto/PagingRespDTO.java
new file mode 100644
index 0000000..8f76179
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/dto/PagingRespDTO.java
@@ -0,0 +1,30 @@
+package com.yx.exam.core.api.dto;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+/**
+ * 分页响应类
+ * @author chenhaodong
+ * @date 2019-07-20 15:17
+ * @param
+ */
+public class PagingRespDTO extends Page {
+
+ /**
+ * 获取页面总数量
+ * @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;
+ }
+ }
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/api/utils/JsonConverter.java b/exam-api/src/main/java/com/yx/exam/core/api/utils/JsonConverter.java
new file mode 100644
index 0000000..c7f2f9b
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/api/utils/JsonConverter.java
@@ -0,0 +1,48 @@
+package com.yx.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 chenhaodong
+ * @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 fastMediaTypes = new ArrayList<>();
+ fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
+ fastConverter.setSupportedMediaTypes(fastMediaTypes);
+ // 在convert中添加配置信息
+ fastConverter.setFastJsonConfig(fastJsonConfig);
+
+ return fastConverter;
+ }
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/enums/CommonState.java b/exam-api/src/main/java/com/yx/exam/core/enums/CommonState.java
new file mode 100644
index 0000000..0e14dbe
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/enums/CommonState.java
@@ -0,0 +1,19 @@
+package com.yx.exam.core.enums;
+
+/**
+ * 通用的状态枚举信息
+ *
+ * @author chenhaodong
+ * @date 2019-09-17 17:57
+ */
+public interface CommonState {
+
+ /**
+ * 普通状态,正常的
+ */
+ Integer NORMAL = 0;
+ /**
+ * 非正常状态,禁用,下架等
+ */
+ Integer ABNORMAL = 1;
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/enums/JoinType.java b/exam-api/src/main/java/com/yx/exam/core/enums/JoinType.java
new file mode 100644
index 0000000..c6fa1e4
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/enums/JoinType.java
@@ -0,0 +1,14 @@
+package com.yx.exam.core.enums;
+
+/**
+ * 组卷方式
+ * @author chenhaodong
+ */
+public interface JoinType {
+
+
+ /**
+ * 题库组题
+ */
+ Integer REPO_JOIN = 1;
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/enums/OpenType.java b/exam-api/src/main/java/com/yx/exam/core/enums/OpenType.java
new file mode 100644
index 0000000..d2ee7f0
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/enums/OpenType.java
@@ -0,0 +1,18 @@
+package com.yx.exam.core.enums;
+
+/**
+ * 开放方式
+ * @author chenhaodong
+ */
+public interface OpenType {
+
+ /**
+ * 完全开放
+ */
+ Integer OPEN = 1;
+
+ /**
+ * 部门开放
+ */
+ Integer DEPT_OPEN = 2;
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/exception/ServiceException.java b/exam-api/src/main/java/com/yx/exam/core/exception/ServiceException.java
new file mode 100644
index 0000000..92f8b9f
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/exception/ServiceException.java
@@ -0,0 +1,51 @@
+package com.yx.exam.core.exception;
+
+import com.yx.exam.core.api.ApiError;
+import com.yx.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;
+ }
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/exception/ServiceExceptionHandler.java b/exam-api/src/main/java/com/yx/exam/core/exception/ServiceExceptionHandler.java
new file mode 100644
index 0000000..64f412e
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/exception/ServiceExceptionHandler.java
@@ -0,0 +1,46 @@
+package com.yx.exam.core.exception;
+
+import com.yx.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 chenhaodong
+ * @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.yx.exam.core.exception.ServiceException.class})
+ @ResponseStatus(HttpStatus.OK)
+ public ApiRest serviceExceptionHandler(ServiceException e) {
+ return new ApiRest(e);
+ }
+
+}
\ No newline at end of file
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/BeanMapper.java b/exam-api/src/main/java/com/yx/exam/core/utils/BeanMapper.java
new file mode 100644
index 0000000..922e930
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/BeanMapper.java
@@ -0,0 +1,59 @@
+package com.yx.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 map(Object source, Class destinationClass) {
+ return dozerBeanMapper.map(source, destinationClass);
+ }
+
+ /**
+ * 基于Dozer转换Collection中对象的类型.
+ */
+ public static List mapList(Iterable> sourceList, Class destinationClass) {
+ List 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 List mapList(Collection source, Function super S, ? extends T> mapper) {
+ return source.stream().map(mapper).collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/CodeGenerator.java b/exam-api/src/main/java/com/yx/exam/core/utils/CodeGenerator.java
new file mode 100644
index 0000000..2c30d6f
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/CodeGenerator.java
@@ -0,0 +1,75 @@
+package com.yx.exam.core.utils;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.generator.AutoGenerator;
+import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
+import com.baomidou.mybatisplus.generator.config.GlobalConfig;
+import com.baomidou.mybatisplus.generator.config.PackageConfig;
+import com.baomidou.mybatisplus.generator.config.StrategyConfig;
+import com.baomidou.mybatisplus.generator.config.rules.DateType;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+
+/**
+ * @author chenhaodong
+ * @since 2022-11-23 21:11
+ */
+public class CodeGenerator {
+
+ public static void main(String[] args) {
+
+ // 1、创建代码生成器
+ AutoGenerator mpg = new AutoGenerator();
+
+ // 2、全局配置
+ GlobalConfig gc = new GlobalConfig();
+ String projectPath = System.getProperty("user.dir");
+ gc.setOutputDir(projectPath + "/exam-api/src/main/java");
+ gc.setAuthor("chenhaodong");
+ gc.setOpen(false); //生成后是否打开资源管理器
+ gc.setFileOverride(false); //重新生成时文件是否覆盖
+ gc.setServiceName("%sService"); //去掉Service接口的首字母I
+ gc.setIdType(IdType.ASSIGN_ID); //主键策略
+ gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
+ gc.setSwagger2(false);//开启Swagger2模式
+
+ mpg.setGlobalConfig(gc);
+
+ // 3、数据源配置
+ DataSourceConfig dsc = new DataSourceConfig();
+ dsc.setUrl("jdbc:mysql://47.102.144.36:3306/chdbs?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true");
+ dsc.setDriverName("com.mysql.cj.jdbc.Driver");
+ dsc.setUsername("chdbs");
+ dsc.setPassword("chdbs");
+ dsc.setDbType(DbType.MYSQL);
+ mpg.setDataSource(dsc);
+
+ // 4、包配置
+ PackageConfig pc = new PackageConfig();
+ // 模块名 会在Parent目录下建立目录
+ pc.setModuleName("tlog");
+ pc.setParent("com.yx.exam.modules");
+ pc.setController("controller");
+ pc.setEntity("entity");
+ pc.setService("service");
+ pc.setMapper("mapper");
+ mpg.setPackageInfo(pc);
+
+ // 5、策略配置
+ StrategyConfig strategy = new StrategyConfig();
+ strategy.setInclude("t_log");//对那一张表生成代码
+ strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
+ strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
+
+ strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
+ strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
+
+ strategy.setRestControllerStyle(true); //restful api风格控制器
+ strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
+
+ mpg.setStrategy(strategy);
+
+ // 6、执行
+ mpg.execute();
+ }
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/DateUtils.java b/exam-api/src/main/java/com/yx/exam/core/utils/DateUtils.java
new file mode 100644
index 0000000..863e3a8
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/DateUtils.java
@@ -0,0 +1,101 @@
+package com.yx.exam.core.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ * 日期处理工具类
+ * ClassName: DateUtils
+ * date: 2018年12月13日 下午6:34:02
+ *
+ * @author chenhaodong
+ * @version
+ */
+public class DateUtils {
+
+ /**
+ *
+ * calcExpDays:计算某个日期与当前日期相差的天数,如果计算的日期大于现在时间,将返回负数;否则返回正数
+ * @author chenhaodong
+ * @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:获取当前时间的字符串格式,根据传入的格式化来展示.
+ * @author chenhaodong
+ * @param format 日期格式化
+ * @return
+ */
+ public static String dateNow(String format) {
+ SimpleDateFormat fmt = new SimpleDateFormat(format);
+ Calendar c = new GregorianCalendar();
+ return fmt.format(c.getTime());
+ }
+
+ /**
+ * formatDate:格式化日期,返回指定的格式
+ * @author chenhaodong
+ * @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 chenhaodong
+ * @param date
+ * @return
+ */
+ public static Date parseDate(String date) {
+ return parseDate(date, "yyyy-MM-dd HH:mm:ss");
+ }
+
+
+ /**
+ *
+ * parseDate:将字符串转换成日期,使用指定格式化来格式化
+ * @author chenhaodong
+ * @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 ignored) {
+
+ }
+ return null;
+ }
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/IpUtils.java b/exam-api/src/main/java/com/yx/exam/core/utils/IpUtils.java
new file mode 100644
index 0000000..21fd4f2
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/IpUtils.java
@@ -0,0 +1,65 @@
+package com.yx.exam.core.utils;
+
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * IP获取工具类,用户获取网络请求过来的真实IP
+ * ClassName: IpUtils
+ * date: 2018年2月13日 下午7:27:52
+ *
+ * @author chenhaodong
+ * @version
+ */
+public class IpUtils {
+
+
+ /**
+ *
+ * getClientIp:通过请求获取客户端的真实IP地址
+ * @author chenhaodong
+ * @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;
+ }
+
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/Reflections.java b/exam-api/src/main/java/com/yx/exam/core/utils/Reflections.java
new file mode 100644
index 0000000..29d42a5
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/Reflections.java
@@ -0,0 +1,324 @@
+/**
+ * Copyright (c) 2005-2012 springside.org.cn
+ */
+package com.yx.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 chenhaodong
+ * @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 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[] 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
+ *
+ * @param clazz The class to introspect
+ * @return the first generic declaration, or Object.class if cannot be determined
+ */
+ @SuppressWarnings("unchecked")
+ public static Class getClassGenricType(final Class clazz) {
+ return getClassGenricType(clazz, 0);
+ }
+
+ /**
+ * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
+ * 如无法找到, 返回Object.class.
+ *
+ * 如public UserDao extends HibernateDao
+ *
+ * @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);
+ }
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/SpringUtils.java b/exam-api/src/main/java/com/yx/exam/core/utils/SpringUtils.java
new file mode 100644
index 0000000..d6159ad
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/SpringUtils.java
@@ -0,0 +1,32 @@
+package com.yx.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 chenhaodong
+ * @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 getBean(Class tClass) {
+ return applicationContext.getBean(tClass);
+ }
+
+ public static T getBean(String name, Class type) {
+ return applicationContext.getBean(name, type);
+ }
+
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/StringUtils.java b/exam-api/src/main/java/com/yx/exam/core/utils/StringUtils.java
new file mode 100644
index 0000000..ca74ed3
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/StringUtils.java
@@ -0,0 +1,39 @@
+package com.yx.exam.core.utils;
+
+import java.util.Map;
+
+/**
+ * 字符串常用工具类
+ * @author chenhaodong
+ * @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格式,格式为value...
+ * @param params
+ * @return
+ */
+ public static String mapToXml(Map params){
+ StringBuffer sb = new StringBuffer("");
+ for(String key:params.keySet()){
+ sb.append("<")
+ .append(key).append(">")
+ .append(params.get(key))
+ .append("").append(key).append(">");
+ }
+
+ sb.append("");
+ return sb.toString();
+ }
+}
diff --git a/exam-api/src/main/java/com/yx/exam/core/utils/excel/ExportExcel.java b/exam-api/src/main/java/com/yx/exam/core/utils/excel/ExportExcel.java
new file mode 100644
index 0000000..ad7f6f3
--- /dev/null
+++ b/exam-api/src/main/java/com/yx/exam/core/utils/excel/ExportExcel.java
@@ -0,0 +1,402 @@
+/**
+ * Copyright © 2015-2020 JeePlus All rights reserved.
+ */
+package com.yx.exam.core.utils.excel;
+
+import com.google.common.collect.Lists;
+import com.yx.exam.core.utils.Reflections;
+import com.yx.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 chenhaodong
+ * @version 2016-04-21
+ */
+public class ExportExcel {
+
+ private static Logger log = LoggerFactory.getLogger(ExportExcel.class);
+
+ /**
+ * 工作薄对象
+ */
+ private SXSSFWorkbook wb;
+
+ /**
+ * 工作表对象
+ */
+ private Sheet sheet;
+
+ /**
+ * 样式列表
+ */
+ private Map styles;
+
+ /**
+ * 当前行号
+ */
+ private int rownum;
+
+ /**
+ * 注解列表(Object[]{ ExcelField, Field/Method })
+ */
+ List