page = baseService.reviewPaging(reqDTO);
+ return super.success(page);
+ }
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java
new file mode 100644
index 0000000..723dc55
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDTO.java
@@ -0,0 +1,115 @@
+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;
+
+/**
+*
+* 考试数据传输类
+* 考试核心数据传输对象,包含考试的基本信息和状态计算逻辑
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="考试", description="考试")
+public class ExamDTO implements Serializable {
+
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java
new file mode 100644
index 0000000..d32e3c0
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamDepartDTO.java
@@ -0,0 +1,53 @@
+package com.yf.exam.modules.exam.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+*
+* 考试部门数据传输类
+* 用于表示考试与部门的关联关系,控制考试的访问权限范围
+* 实现考试按部门进行权限控制的功能
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-03 17:24
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="考试部门", description="考试部门")
+public class ExamDepartDTO implements Serializable {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ * 主键ID
+ * 考试部门关联记录的唯一标识
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 考试ID
+ * 关联的考试唯一标识
+ */
+ @ApiModelProperty(value = "考试ID", required=true)
+ private String examId;
+
+ /**
+ * 部门ID
+ * 关联的部门唯一标识,表示该部门可以访问对应考试
+ */
+ @ApiModelProperty(value = "部门ID", required=true)
+ private String departId;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java
new file mode 100644
index 0000000..51b3a26
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ExamRepoDTO.java
@@ -0,0 +1,95 @@
+package com.yf.exam.modules.exam.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+*
+* 考试题库数据传输类
+* 用于表示考试与题库的关联关系,定义考试中各题型的题目数量和分值配置
+* 实现考试题目组成和评分规则的灵活配置
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-05 11:14
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="考试题库", description="考试题库")
+public class ExamRepoDTO implements Serializable {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ * 主键ID
+ * 考试题库关联记录的唯一标识
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 考试ID
+ * 关联的考试唯一标识
+ */
+ @ApiModelProperty(value = "考试ID", required=true)
+ private String examId;
+
+ /**
+ * 题库ID
+ * 关联的题库唯一标识
+ */
+ @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;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java
new file mode 100644
index 0000000..b364145
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/ext/ExamRepoExtDTO.java
@@ -0,0 +1,51 @@
+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;
+
+/**
+*
+* 考试题库数据传输类
+* 扩展考试题库基础DTO,增加题目数量统计信息
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-05 11:14
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="考试题库扩展响应类", description="考试题库扩展响应类")
+public class ExamRepoExtDTO extends ExamRepoDTO {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ 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;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java
new file mode 100644
index 0000000..0e40cee
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/request/ExamSaveReqDTO.java
@@ -0,0 +1,47 @@
+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;
+
+/**
+*
+* 考试保存请求类
+* 用于接收考试创建或编辑时的请求数据,扩展了基础考试信息
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="考试保存请求类", description="考试保存请求类")
+public class ExamSaveReqDTO extends ExamDTO {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ * 题库列表
+ * 考试关联的题库集合,包含每个题库的题目数量统计信息
+ */
+ @ApiModelProperty(value = "题库列表", required=true)
+ private List repoList;
+
+ /**
+ * 考试部门列表
+ * 可以参加该考试的部门ID集合,用于控制考试权限范围
+ */
+ @ApiModelProperty(value = "考试部门列表", required=true)
+ private List departIds;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java
new file mode 100644
index 0000000..6aab544
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamOnlineRespDTO.java
@@ -0,0 +1,30 @@
+package com.yf.exam.modules.exam.dto.response;
+
+import com.yf.exam.modules.exam.dto.ExamDTO;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+/**
+*
+* 考试分页响应类
+* 用于在线考试场景的分页响应数据,继承基础考试信息
+* 考生视角的考试列表展示
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="在线考试分页响应类", description="在线考试分页响应类")
+public class ExamOnlineRespDTO extends ExamDTO {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java
new file mode 100644
index 0000000..77b7654
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/dto/response/ExamReviewRespDTO.java
@@ -0,0 +1,47 @@
+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;
+
+/**
+*
+* 考试分页响应类
+* 用于阅卷管理场景的分页响应数据,继承基础考试信息
+* 包含阅卷相关的统计信息,便于管理员进行试卷批阅管理
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="阅卷分页响应类", description="阅卷分页响应类")
+public class ExamReviewRespDTO extends ExamDTO {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+
+ /**
+ * 考试人数
+ * 参加该考试的总人数统计
+ */
+ @ApiModelProperty(value = "考试人数", required=true)
+ private Integer examUser;
+
+ /**
+ * 待阅试卷
+ * 需要批阅的试卷数量,用于阅卷工作量统计
+ */
+ @ApiModelProperty(value = "待阅试卷", required=true)
+ private Integer unreadPaper;
+
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/Exam.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/Exam.java
new file mode 100644
index 0000000..adc20af
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/Exam.java
@@ -0,0 +1,121 @@
+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;
+
+/**
+*
+* 考试实体类
+* 对应数据库表el_exam,表示考试的基本信息实体
+* 使用MyBatis-Plus的ActiveRecord模式,继承Model类
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// MyBatis-Plus注解,指定对应的数据库表名
+@TableName("el_exam")
+public class Exam extends Model {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ * 主键字段,使用雪花算法分配ID
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 考试名称
+ * 考试的标题名称
+ */
+ private String title;
+
+ /**
+ * 考试描述
+ * 考试的详细描述信息
+ */
+ private String content;
+
+ /**
+ * 1公开2部门3定员
+ * 考试开放类型: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;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java
new file mode 100644
index 0000000..33da592
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamDepart.java
@@ -0,0 +1,53 @@
+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;
+
+/**
+*
+* 考试部门实体类
+* 对应数据库表el_exam_depart,表示考试与部门的关联关系
+* 用于控制考试的部门访问权限,实现按部门分配考试权限
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-03 17:24
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// MyBatis-Plus注解,指定对应的数据库表名
+@TableName("el_exam_depart")
+public class ExamDepart extends Model {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ * 主键字段,使用雪花算法分配ID
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 考试ID
+ * 关联的考试唯一标识,外键关联el_exam表
+ */
+ @TableField("exam_id")
+ private String examId;
+
+ /**
+ * 部门ID
+ * 关联的部门唯一标识,外键关联部门表
+ */
+ @TableField("depart_id")
+ private String departId;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java
new file mode 100644
index 0000000..a8536a3
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/entity/ExamRepo.java
@@ -0,0 +1,95 @@
+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;
+
+/**
+*
+* 考试题库实体类
+* 对应数据库表el_exam_repo,表示考试与题库的关联关系
+* 用于配置考试中各题型的题目数量和分值,实现灵活的考试组卷规则
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-05 11:14
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// MyBatis-Plus注解,指定对应的数据库表名
+@TableName("el_exam_repo")
+public class ExamRepo extends Model {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ * 主键字段,使用雪花算法分配ID
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 考试ID
+ * 关联的考试唯一标识,外键关联el_exam表
+ */
+ @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;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java
new file mode 100644
index 0000000..80d7b8b
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamDepartMapper.java
@@ -0,0 +1,18 @@
+package com.yf.exam.modules.exam.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yf.exam.modules.exam.entity.ExamDepart;
+
+/**
+*
+* 考试部门Mapper
+* 考试部门数据访问层接口,继承MyBatis-Plus的BaseMapper
+* 提供对el_exam_depart表的CRUD操作
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-03 17:24
+*/
+public interface ExamDepartMapper extends BaseMapper {
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java
new file mode 100644
index 0000000..c3091a3
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamMapper.java
@@ -0,0 +1,50 @@
+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;
+
+/**
+*
+* 考试Mapper
+* 考试数据访问层接口,继承MyBatis-Plus的BaseMapper
+* 提供对el_exam表的CRUD操作和自定义分页查询
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+public interface ExamMapper extends BaseMapper {
+
+ /**
+ * 查找分页内容
+ * 管理员视角的考试分页查询,用于考试管理列表
+ * @param page 分页参数对象
+ * @param query 查询条件对象
+ * @return 考试分页结果
+ */
+ IPage paging(Page page, @Param("query") ExamDTO query);
+
+ /**
+ * 查找分页内容
+ * 阅卷视角的考试分页查询,用于待阅试卷列表
+ * @param page 分页参数对象
+ * @param query 查询条件对象
+ * @return 阅卷分页结果
+ */
+ IPage reviewPaging(Page page, @Param("query") ExamDTO query);
+
+ /**
+ * 在线考试分页响应类-考生视角
+ * 考生视角的在线考试分页查询,用于考生参加考试列表
+ * @param page 分页参数对象
+ * @param query 查询条件对象
+ * @return 在线考试分页结果
+ */
+ IPage online(Page page, @Param("query") ExamDTO query);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java
new file mode 100644
index 0000000..71e6062
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/mapper/ExamRepoMapper.java
@@ -0,0 +1,29 @@
+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;
+
+/**
+*
+* 考试题库Mapper
+* 考试题库数据访问层接口,继承MyBatis-Plus的BaseMapper
+* 提供对el_exam_repo表的CRUD操作和自定义查询
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-05 11:14
+*/
+public interface ExamRepoMapper extends BaseMapper {
+
+ /**
+ * 查找考试题库列表
+ * 根据考试ID查询关联的题库列表,包含题目数量统计信息
+ * @param examId 考试ID
+ * @return 考试题库扩展信息列表
+ */
+ List listByExam(@Param("examId") String examId);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java
new file mode 100644
index 0000000..239472e
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamDepartService.java
@@ -0,0 +1,35 @@
+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;
+
+/**
+*
+* 考试部门业务类
+* 考试部门关联关系服务接口,定义考试与部门关联的业务操作方法
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-03 17:24
+*/
+public interface ExamDepartService extends IService {
+
+ /**
+ * 保存全部
+ * 批量保存考试与部门的关联关系,采用先删除后新增的策略
+ * @param examId 考试ID
+ * @param departs 部门ID列表
+ */
+ void saveAll(String examId, List departs);
+
+
+ /**
+ * 根据考试查找对应的部门
+ * 查询指定考试关联的所有部门ID列表
+ * @param examId 考试ID
+ * @return 部门ID列表
+ */
+ List listByExam(String examId);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java
new file mode 100644
index 0000000..59adb91
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamRepoService.java
@@ -0,0 +1,43 @@
+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;
+
+/**
+*
+* 考试题库业务类
+* 考试题库关联关系服务接口,定义考试与题库关联的业务操作方法
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-05 11:14
+*/
+public interface ExamRepoService extends IService {
+
+ /**
+ * 保存全部
+ * 批量保存考试与题库的关联关系,包含题目数量和分值配置
+ * @param examId 考试ID
+ * @param list 考试题库扩展信息列表
+ */
+ void saveAll(String examId, List list);
+
+ /**
+ * 查找考试题库列表
+ * 查询指定考试关联的所有题库信息,包含题目数量统计
+ * @param examId 考试ID
+ * @return 考试题库扩展信息列表
+ */
+ List listByExam(String examId);
+
+ /**
+ * 清理脏数据
+ * 清理指定考试的所有题库关联关系
+ * @param examId 考试ID
+ */
+ void clear(String examId);
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamService.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamService.java
new file mode 100644
index 0000000..f9ee8db
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/ExamService.java
@@ -0,0 +1,69 @@
+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;
+
+/**
+*
+* 考试业务类
+* 考试核心业务服务接口,定义考试相关的业务操作方法
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+public interface ExamService extends IService {
+
+ /**
+ * 保存考试信息
+ * 创建或更新考试信息,包括基本信息、题库配置和部门权限
+ * @param reqDTO 考试保存请求数据传输对象
+ */
+ void save(ExamSaveReqDTO reqDTO);
+
+ /**
+ * 查找考试详情
+ * 获取考试的完整信息,包括基本信息、部门权限和题库配置
+ * @param id 考试ID
+ * @return 考试详情响应对象
+ */
+ ExamSaveReqDTO findDetail(String id);
+
+ /**
+ * 查找考试详情--简要信息
+ * 获取考试的基本信息,不包含关联的部门和题库信息
+ * @param id 考试ID
+ * @return 考试数据传输对象
+ */
+ ExamDTO findById(String id);
+
+ /**
+ * 分页查询数据
+ * 管理员视角的考试分页查询,用于考试管理列表
+ * @param reqDTO 分页请求参数
+ * @return 考试分页结果
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 在线考试分页响应类-考生视角
+ * 考生视角的在线考试分页查询,用于考生参加考试列表
+ * @param reqDTO 分页请求参数
+ * @return 在线考试分页结果
+ */
+ IPage onlinePaging(PagingReqDTO reqDTO);
+
+ /**
+ * 待阅试卷列表
+ * 阅卷视角的待阅试卷分页查询,用于试卷批阅管理
+ * @param reqDTO 分页请求参数
+ * @return 待阅试卷分页结果
+ */
+ IPage reviewPaging(PagingReqDTO reqDTO);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java
new file mode 100644
index 0000000..d322f61
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamDepartServiceImpl.java
@@ -0,0 +1,73 @@
+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;
+
+/**
+*
+* 考试部门业务实现类
+* 实现考试与部门关联关系的业务逻辑,包括批量保存和查询功能
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-03 17:24
+*/
+// Spring注解,声明为服务层组件
+@Service
+public class ExamDepartServiceImpl extends ServiceImpl implements ExamDepartService {
+
+ @Override
+ public void saveAll(String examId, List departs) {
+
+ // 先删除该考试原有的所有部门关联
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(ExamDepart::getExamId, examId);
+ this.remove(wrapper);
+
+ // 再增加新的部门关联
+ // 检查部门列表是否为空
+ if(CollectionUtils.isEmpty(departs)){
+ throw new ServiceException(1, "请至少选择选择一个部门!!");
+ }
+
+ // 构建要保存的考试部门关联列表
+ List 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 listByExam(String examId) {
+ // 查询指定考试的所有部门关联
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(ExamDepart::getExamId, examId);
+ List list = this.list(wrapper);
+
+ // 提取部门ID列表
+ List ids = new ArrayList<>();
+ if(!CollectionUtils.isEmpty(list)){
+ for(ExamDepart item: list){
+ ids.add(item.getDepartId());
+ }
+ }
+
+ return ids;
+
+ }
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java
new file mode 100644
index 0000000..1c7da31
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamRepoServiceImpl.java
@@ -0,0 +1,91 @@
+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;
+
+/**
+*
+* 考试题库业务实现类
+* 实现考试与题库关联关系的业务逻辑,包括批量保存、查询和清理功能
+*
+*
+* @author 聪明笨狗
+* @since 2020-09-05 11:14
+*/
+// Spring注解,声明为服务层组件
+@Service
+public class ExamRepoServiceImpl extends ServiceImpl implements ExamRepoService {
+
+
+ /**
+ * 批量保存考试题库关联关系
+ * 采用先删除后新增的策略更新考试题库配置
+ * @param examId 考试ID
+ * @param list 考试题库扩展信息列表
+ */
+ @Transactional(rollbackFor = Exception.class) // 事务注解,遇到任何异常都回滚
+ @Override
+ public void saveAll(String examId, List list) {
+
+ // 先删除该考试原有的所有题库关联
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(ExamRepo::getExamId, examId);
+ this.remove(wrapper);
+
+ // 再增加新的题库关联
+ // 检查题库列表是否为空
+ if(CollectionUtils.isEmpty(list)){
+ throw new ServiceException(1, "必须选择题库!");
+ }
+
+ // 使用BeanMapper将DTO列表转换为实体列表
+ List repos = BeanMapper.mapList(list, ExamRepo.class);
+
+ // 为每个实体设置考试ID和生成主键ID
+ for(ExamRepo item: repos){
+ item.setExamId(examId);
+ item.setId(IdWorker.getIdStr()); // 使用雪花算法生成唯一ID
+ }
+
+ // 批量保存新的题库关联关系
+ this.saveBatch(repos);
+ }
+
+ /**
+ * 根据考试ID查询关联的题库列表
+ * @param examId 考试ID
+ * @return 考试题库扩展信息列表
+ */
+ @Override
+ public List listByExam(String examId) {
+ // 调用Mapper层的自定义查询方法
+ return baseMapper.listByExam(examId);
+ }
+
+ /**
+ * 清理指定考试的题库关联关系
+ * @param examId 考试ID
+ */
+ @Override
+ public void clear(String examId) {
+
+ // 删除该考试的所有题库关联
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(ExamRepo::getExamId, examId);
+ this.remove(wrapper);
+ }
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java
new file mode 100644
index 0000000..1d30a90
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/exam/service/impl/ExamServiceImpl.java
@@ -0,0 +1,191 @@
+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;
+
+/**
+*
+* 考试业务实现类
+* 实现考试相关的核心业务逻辑,包括考试创建、编辑、查询和分页等功能
+*
+*
+* @author 聪明笨狗
+* @since 2020-07-25 16:18
+*/
+// Spring注解,声明为服务层组件
+@Service
+public class ExamServiceImpl extends ServiceImpl implements ExamService {
+
+
+ @Autowired
+ private ExamRepoService examRepoService;
+
+ @Autowired
+ private ExamDepartService examDepartService;
+
+ @Override
+ public void save(ExamSaveReqDTO reqDTO) {
+
+ // ID处理:如果ID为空则生成新ID,否则使用现有ID
+ String id = reqDTO.getId();
+ if(StringUtils.isBlank(id)){
+ id = IdWorker.getIdStr(); // 使用雪花算法生成唯一ID
+ }
+
+ // 复制参数到实体对象
+ Exam entity = new Exam();
+
+ // 计算考试总分
+ this.calcScore(reqDTO);
+
+ // 复制基本数据从DTO到实体
+ BeanMapper.copy(reqDTO, entity);
+ entity.setId(id);
+
+ // 修复状态:如果考试不限时且状态为2,则重置为0
+ 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 departIds = examDepartService.listByExam(id);
+ respDTO.setDepartIds(departIds);
+
+ // 获取题库关联信息
+ List 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 paging(PagingReqDTO reqDTO) {
+ // 创建分页对象
+ Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 转换结果:调用Mapper层分页查询
+ IPage pageData = baseMapper.paging(page, reqDTO.getParams());
+ return pageData;
+ }
+
+ @Override
+ public IPage onlinePaging(PagingReqDTO reqDTO) {
+ // 创建分页对象
+ Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 查找分页:考生视角的在线考试列表
+ IPage pageData = baseMapper.online(page, reqDTO.getParams());
+
+ return pageData;
+ }
+
+ @Override
+ public IPage reviewPaging(PagingReqDTO reqDTO) {
+ // 创建分页对象
+ Page page = new Page(reqDTO.getCurrent(), reqDTO.getSize());
+
+ // 查找分页:阅卷视角的待阅试卷列表
+ IPage pageData = baseMapper.reviewPaging(page, reqDTO.getParams());
+
+ return pageData;
+ }
+
+ /**
+ * 计算分值
+ * 根据题库配置计算考试总分
+ * @param reqDTO 考试保存请求对象
+ */
+ private void calcScore(ExamSaveReqDTO reqDTO){
+ // 主观题分数(这里实际计算的是客观题总分)
+ int objScore = 0;
+
+ // 遍历所有题库配置
+ List 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);
+ }
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java
new file mode 100644
index 0000000..4bf0e15
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/controller/PaperController.java
@@ -0,0 +1,160 @@
+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;
+
+/**
+*
+* 试卷控制器
+* 提供试卷相关的API接口,包括试卷创建、答题、交卷、查询等功能
+*
+*
+* @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> paging(@RequestBody PagingReqDTO reqDTO) {
+ //分页查询并转换
+ IPage page = baseService.paging(reqDTO);
+ return super.success(page);
+ }
+
+ /**
+ * 创建试卷
+ * 为用户创建新的考试试卷
+ * @param reqDTO 创建试卷请求参数,包含考试ID
+ * @return 包含新创建试卷ID的响应
+ */
+ @ApiOperation(value = "创建试卷")
+ @RequestMapping(value = "/create-paper", method = { RequestMethod.POST})
+ public ApiRest save(@RequestBody PaperCreateReqDTO reqDTO) {
+ //复制参数并创建试卷
+ String paperId = baseService.createPaper(UserUtils.getUserId(), reqDTO.getExamId());
+ return super.success(new BaseIdRespDTO(paperId));
+ }
+
+ /**
+ * 试卷详情
+ * 获取试卷的详细信息,包括考试信息和题目列表
+ * @param reqDTO 包含试卷ID的请求参数
+ * @return 试卷详情响应
+ */
+ @ApiOperation(value = "试卷详情")
+ @RequestMapping(value = "/paper-detail", method = { RequestMethod.POST})
+ public ApiRest paperDetail(@RequestBody BaseIdReqDTO reqDTO) {
+ //根据ID查询试卷详情
+ ExamDetailRespDTO respDTO = baseService.paperDetail(reqDTO.getId());
+ return super.success(respDTO);
+ }
+
+ /**
+ * 试题详情
+ * 获取试卷中指定试题的详细信息
+ * @param reqDTO 包含试卷ID和试题ID的请求参数
+ * @return 试题详情响应
+ */
+ @ApiOperation(value = "试题详情")
+ @RequestMapping(value = "/qu-detail", method = { RequestMethod.POST})
+ public ApiRest quDetail(@RequestBody PaperQuQueryDTO reqDTO) {
+ //根据试卷ID和试题ID查询试题详情
+ PaperQuDetailDTO respDTO = baseService.findQuDetail(reqDTO.getPaperId(), reqDTO.getQuId());
+ return super.success(respDTO);
+ }
+
+ /**
+ * 填充答案
+ * 保存用户对试题的答案
+ * @param reqDTO 包含试卷ID、试题ID和答案的请求参数
+ * @return 操作结果
+ */
+ @ApiOperation(value = "填充答案")
+ @RequestMapping(value = "/fill-answer", method = { RequestMethod.POST})
+ public ApiRest fillAnswer(@RequestBody PaperAnswerDTO reqDTO) {
+ //保存用户答案
+ baseService.fillAnswer(reqDTO);
+ return super.success();
+ }
+
+ /**
+ * 交卷操作
+ * 用户完成考试并提交试卷
+ * @param reqDTO 包含试卷ID的请求参数
+ * @return 操作结果
+ */
+ @ApiOperation(value = "交卷操作")
+ @RequestMapping(value = "/hand-exam", method = { RequestMethod.POST})
+ public ApiRest handleExam(@RequestBody BaseIdReqDTO reqDTO) {
+ //执行交卷操作
+ baseService.handExam(reqDTO.getId());
+ return super.success();
+ }
+
+ /**
+ * 试卷结果
+ * 获取试卷的考试结果,包括得分和答题情况
+ * @param reqDTO 包含试卷ID的请求参数
+ * @return 试卷结果响应
+ */
+ @ApiOperation(value = "试卷结果")
+ @RequestMapping(value = "/paper-result", method = { RequestMethod.POST})
+ public ApiRest paperResult(@RequestBody BaseIdReqDTO reqDTO) {
+ //根据ID查询试卷结果
+ ExamResultRespDTO respDTO = baseService.paperResult(reqDTO.getId());
+ return super.success(respDTO);
+ }
+
+ /**
+ * 检测用户有没有中断的考试
+ * 检查当前用户是否有未完成的考试试卷
+ * @return 进行中的试卷信息,如果没有则返回null
+ */
+ @ApiOperation(value = "检测进行中的考试")
+ @RequestMapping(value = "/check-process", method = { RequestMethod.POST})
+ public ApiRest checkProcess() {
+ //检查用户是否有未完成的考试
+ PaperDTO dto = baseService.checkProcess(UserUtils.getUserId());
+ return super.success(dto);
+ }
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java
new file mode 100644
index 0000000..3da43da
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperDTO.java
@@ -0,0 +1,150 @@
+package com.yf.exam.modules.paper.dto;
+
+import com.yf.exam.core.annon.Dict;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+*
+* 试卷请求类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@ApiModel(value="试卷", description="试卷")
+public class PaperDTO implements Serializable {
+
+ /**
+ * 序列化版本UID,用于保证序列化时的版本一致性
+ * 在反序列化时验证序列化对象的版本是否与当前类定义兼容
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 试卷唯一标识ID
+ * 在系统中唯一标识一份试卷
+ */
+ @ApiModelProperty(value = "试卷ID", required=true)
+ private String id;
+
+ /**
+ * 用户ID,关联系统用户表
+ * 使用字典注解关联sys_user表,显示用户真实姓名
+ */
+ @Dict(dictTable = "sys_user", dicText = "real_name", dicCode = "id")
+ @ApiModelProperty(value = "用户ID", required=true)
+ private String userId;
+
+ /**
+ * 部门ID,关联系统部门表
+ * 使用字典注解关联sys_depart表,显示部门名称
+ */
+ @Dict(dictTable = "sys_depart", dicText = "dept_name", dicCode = "id")
+ @ApiModelProperty(value = "部门ID", required=true)
+ private String departId;
+
+ /**
+ * 考试规则ID,关联考试规则表
+ * 标识该试卷所属的考试规则
+ */
+ @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;
+
+ /**
+ * 是否包含简答题
+ * true表示包含简答题,false表示不包含
+ */
+ @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;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java
new file mode 100644
index 0000000..7869f6e
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuAnswerDTO.java
@@ -0,0 +1,86 @@
+package com.yf.exam.modules.paper.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+*
+* 试卷考题备选答案请求类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案")
+public class PaperQuAnswerDTO implements Serializable {
+
+ /**
+ * 序列化版本UID,用于保证序列化时的版本一致性
+ * 在反序列化时验证序列化对象的版本是否与当前类定义兼容
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 自增主键ID
+ * 唯一标识每条试卷考题备选答案记录
+ */
+ @ApiModelProperty(value = "自增ID", required=true)
+ private String id;
+
+ /**
+ * 试卷ID
+ * 关联所属的试卷,标识该备选答案属于哪份试卷
+ */
+ @ApiModelProperty(value = "试卷ID", required=true)
+ private String paperId;
+
+ /**
+ * 回答项ID
+ * 关联具体的答案选项,标识具体的答案内容
+ */
+ @ApiModelProperty(value = "回答项ID", required=true)
+ private String answerId;
+
+ /**
+ * 题目ID
+ * 关联所属的题目,标识该备选答案属于哪道题目
+ */
+ @ApiModelProperty(value = "题目ID", required=true)
+ private String quId;
+
+ /**
+ * 是否为正确答案
+ * true表示该选项是正确答案,false表示不是正确答案
+ * 用于客观题的标准答案判定
+ */
+ @ApiModelProperty(value = "是否正确项", required=true)
+ private Boolean isRight;
+
+ /**
+ * 用户是否选中该选项
+ * true表示用户在答题时选择了该选项,false表示未选择
+ * 记录用户的实际答题选择
+ */
+ @ApiModelProperty(value = "是否选中", required=true)
+ private Boolean checked;
+
+ /**
+ * 选项排序号
+ * 控制选项在界面上的显示顺序,数值越小显示越靠前
+ */
+ @ApiModelProperty(value = "排序", required=true)
+ private Integer sort;
+
+ /**
+ * 选项标签
+ * 用于标识选项的字母标签,如A、B、C、D等
+ * 在界面上显示为选项的前缀标识
+ */
+ @ApiModelProperty(value = "选项标签", required=true)
+ private String abc;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java
new file mode 100644
index 0000000..fc1ddb1
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/PaperQuDTO.java
@@ -0,0 +1,102 @@
+package com.yf.exam.modules.paper.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+*
+* 试卷考题请求类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@ApiModel(value="试卷考题", description="试卷考题")
+public class PaperQuDTO implements Serializable {
+
+ /**
+ * 序列化版本UID,用于保证序列化时的版本一致性
+ * 在反序列化时验证序列化对象的版本是否与当前类定义兼容
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ * 唯一标识试卷中的每道题目记录
+ */
+ @ApiModelProperty(value = "ID", required=true)
+ private String id;
+
+ /**
+ * 试卷ID
+ * 关联所属的试卷,标识该题目属于哪份试卷
+ */
+ @ApiModelProperty(value = "试卷ID", required=true)
+ private String paperId;
+
+ /**
+ * 题目ID
+ * 关联题库中的原始题目,标识该题目的具体内容
+ */
+ @ApiModelProperty(value = "题目ID", required=true)
+ private String quId;
+
+ /**
+ * 题目类型
+ * 标识题目的类型,如单选题、多选题、判断题、简答题等
+ * 使用数字代码表示不同的题目类型
+ */
+ @ApiModelProperty(value = "题目类型", required=true)
+ private Integer quType;
+
+ /**
+ * 是否已答题
+ * true表示用户已经回答了该题目,false表示未回答
+ * 用于标识题目的答题状态
+ */
+ @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;
+
+ /**
+ * 是否答对(主要用于客观题)
+ * true表示该题目回答正确,false表示回答错误
+ * 对于主观题,此字段可能为空或根据得分判断
+ */
+ @ApiModelProperty(value = "是否答对", required=true)
+ private Boolean isRight;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java
new file mode 100644
index 0000000..3bb7816
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuAnswerExtDTO.java
@@ -0,0 +1,45 @@
+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;
+
+/**
+*
+* 试卷考题备选答案请求类
+* 扩展试卷考题答案基础DTO,增加试题图片和答案内容信息
+* 用于前端展示试题的完整信息和备选答案
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="试卷考题备选答案", description="试卷考题备选答案")
+public class PaperQuAnswerExtDTO extends PaperQuAnswerDTO {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 试题图片
+ * 试题相关的图片URL,用于图文并茂的题目展示
+ */
+ @ApiModelProperty(value = "试题图片", required=true)
+ private String image;
+
+ /**
+ * 答案内容
+ * 备选答案的具体文本内容
+ */
+ @ApiModelProperty(value = "答案内容", required=true)
+ private String content;
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java
new file mode 100644
index 0000000..e832e28
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/ext/PaperQuDetailDTO.java
@@ -0,0 +1,52 @@
+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;
+
+/**
+*
+* 试卷考题请求类
+* 扩展试卷考题基础DTO,增加题目内容、图片和备选答案列表
+* 用于前端展示试题的完整信息和答题选项
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="试卷题目详情类", description="试卷题目详情类")
+public class PaperQuDetailDTO extends PaperQuDTO {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 图片
+ * 试题相关的图片URL,用于图文并茂的题目展示
+ */
+ @ApiModelProperty(value = "图片", required=true)
+ private String image;
+
+ /**
+ * 题目内容
+ * 试题的正文内容,描述题目的具体问题
+ */
+ @ApiModelProperty(value = "题目内容", required=true)
+ private String content;
+
+ /**
+ * 答案内容
+ * 试题的备选答案列表,包含所有可能的选项
+ */
+ @ApiModelProperty(value = "答案内容", required=true)
+ List answerList;
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java
new file mode 100644
index 0000000..a8d70ec
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperAnswerDTO.java
@@ -0,0 +1,35 @@
+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
+ */
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类")
+public class PaperAnswerDTO extends PaperQuQueryDTO {
+
+ /**
+ * 回答列表
+ * 用于客观题(多选题等)的答案,存储用户选择的多个选项
+ */
+ @ApiModelProperty(value = "回答列表", required=true)
+ private List answers;
+
+ /**
+ * 主观答案
+ * 用于主观题(填空题、简答题等)的答案,存储用户输入的文本内容
+ */
+ @ApiModelProperty(value = "主观答案", required=true)
+ private String answer;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java
new file mode 100644
index 0000000..428003b
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperCreateReqDTO.java
@@ -0,0 +1,36 @@
+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;
+
+/**
+ * 试卷创建请求数据传输类
+ * 用于接收创建新试卷的请求参数,包含考试ID和用户ID信息
+ * 用户ID通过安全上下文自动设置,不暴露给前端
+ * @author bool
+ */
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="试卷创建请求类", description="试卷创建请求类")
+public class PaperCreateReqDTO extends BaseDTO {
+
+ /**
+ * 用户ID
+ * 使用JsonIgnore注解,在JSON序列化时忽略此字段
+ * 通常在后端通过安全上下文自动设置,避免前端传递
+ */
+ @JsonIgnore
+ private String userId;
+
+ /**
+ * 考试ID
+ * 要创建试卷对应的考试唯一标识
+ */
+ @ApiModelProperty(value = "考试ID", required=true)
+ private String examId;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java
new file mode 100644
index 0000000..20a0997
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperListReqDTO.java
@@ -0,0 +1,67 @@
+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;
+
+/**
+*
+* 试卷请求类
+* 用于试卷列表查询的请求参数封装,支持多条件筛选
+* 包含用户信息、部门信息、考试信息和试卷状态等查询条件
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="试卷", description="试卷")
+public class PaperListReqDTO implements Serializable {
+
+ /**
+ * 序列化版本UID
+ * 用于保证序列化的一致性
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户ID
+ * 查询指定用户的试卷记录
+ */
+ @ApiModelProperty(value = "用户ID", required=true)
+ private String userId;
+
+ /**
+ * 部门ID
+ * 按部门筛选试卷,用于权限控制
+ */
+ @ApiModelProperty(value = "部门ID", required=true)
+ private String departId;
+
+ /**
+ * 规则ID
+ * 查询指定考试的试卷记录
+ */
+ @ApiModelProperty(value = "规则ID", required=true)
+ private String examId;
+
+ /**
+ * 用户昵称
+ * 按用户真实姓名模糊查询试卷
+ */
+ @ApiModelProperty(value = "用户昵称", required=true)
+ private String realName;
+
+ /**
+ * 试卷状态
+ * 按试卷状态筛选,如进行中、已交卷、已批阅等
+ */
+ @ApiModelProperty(value = "试卷状态", required=true)
+ private Integer state;
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java
new file mode 100644
index 0000000..6bdf038
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/request/PaperQuQueryDTO.java
@@ -0,0 +1,34 @@
+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;
+
+/**
+ * 试卷题目详情查询请求数据传输类
+ * 用于查询指定试卷中特定题目的详细信息
+ * 包含试卷ID和题目ID两个必要参数,精确定位要查询的题目
+ * @author bool
+ */
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="查找试卷题目详情请求类", description="查找试卷题目详情请求类")
+public class PaperQuQueryDTO extends BaseDTO {
+
+ /**
+ * 试卷ID
+ * 要查询题目所属的试卷唯一标识
+ */
+ @ApiModelProperty(value = "试卷ID", required=true)
+ private String paperId;
+
+ /**
+ * 题目ID
+ * 要查询的具体题目唯一标识
+ */
+ @ApiModelProperty(value = "题目ID", required=true)
+ private String quId;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java
new file mode 100644
index 0000000..923e312
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamDetailRespDTO.java
@@ -0,0 +1,61 @@
+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;
+
+/**
+ * 考试详情响应数据传输类
+ * 扩展试卷基础信息,包含按题型分类的题目列表和考试剩余时间计算
+ * 用于前端展示考试详情页面,支持按题型分组显示题目
+ */
+// 使用Lombok注解自动生成getter、setter等方法
+@Data
+// Swagger注解,用于API文档生成,定义模型名称和描述
+@ApiModel(value="考试详情", description="考试详情")
+public class ExamDetailRespDTO extends PaperDTO {
+
+ /**
+ * 单选题列表
+ * 试卷中所有单选题的集合
+ */
+ @ApiModelProperty(value = "单选题列表", required=true)
+ private List radioList;
+
+ /**
+ * 多选题列表
+ * 试卷中所有多选题的集合
+ */
+ @ApiModelProperty(value = "多选题列表", required=true)
+ private List multiList;
+
+ /**
+ * 判断题列表
+ * 试卷中所有判断题的集合
+ */
+ @ApiModelProperty(value = "判断题", required=true)
+ private List judgeList;
+
+ /**
+ * 获取剩余结束秒数
+ * 动态计算考试剩余时间,基于试卷创建时间和总时长
+ * @return 距离考试结束的剩余秒数
+ */
+ @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;
+ }
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java
new file mode 100644
index 0000000..8ed5d37
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/ExamResultRespDTO.java
@@ -0,0 +1,28 @@
+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;
+
+/**
+ * 考试结果展示响应数据传输对象
+ * 用于封装考试结束后向客户端返回的详细考试结果信息
+ * 继承自PaperDTO,包含试卷基本信息,并扩展了题目详情列表
+ */
+@Data
+@ApiModel(value="考试结果展示响应类", description="考试结果展示响应类")
+public class ExamResultRespDTO extends PaperDTO {
+
+ /**
+ * 试卷中所有题目的详细信息列表
+ * 包含每道题目的内容、选项、用户答案、标准答案、得分等详细信息
+ * 该字段在考试结果展示时为必需数据
+ */
+ @ApiModelProperty(value = "问题列表", required=true)
+ private List quList;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java
new file mode 100644
index 0000000..a433a0c
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/dto/response/PaperListRespDTO.java
@@ -0,0 +1,35 @@
+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;
+
+/**
+*
+* 试卷请求类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@ApiModel(value="试卷列表响应类", description="试卷列表响应类")
+public class PaperListRespDTO extends PaperDTO {
+
+ /**
+ * 序列化版本UID,用于保证序列化时的版本一致性
+ * 在反序列化时验证序列化对象的版本是否与当前类定义兼容
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 考试人员的真实姓名
+ * 用于在试卷列表中显示考生的真实身份信息,而非用户名或昵称
+ * 该字段在响应中为必需字段,不能为空
+ */
+ @ApiModelProperty(value = "人员", required=true)
+ private String realName;
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/Paper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/Paper.java
new file mode 100644
index 0000000..979100e
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/Paper.java
@@ -0,0 +1,164 @@
+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;
+
+/**
+*
+* 试卷实体类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@TableName("el_paper")
+public class Paper extends Model {
+
+ /**
+ * 序列化版本UID,用于保证序列化时的版本一致性
+ * 在反序列化时验证序列化对象的版本是否与当前类定义兼容
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 试卷ID
+ * 使用MyBatis-Plus的雪花算法分配ID,保证全局唯一性
+ * 对应数据库表el_paper的主键字段
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 用户ID
+ * 参加考试的用户唯一标识,关联用户表
+ * 对应数据库表中的user_id字段
+ */
+ @TableField("user_id")
+ private String userId;
+
+ /**
+ * 部门ID
+ * 用户所属的部门标识,关联部门表
+ * 对应数据库表中的depart_id字段
+ */
+ @TableField("depart_id")
+ private String departId;
+
+ /**
+ * 规则ID
+ * 关联考试规则表,标识该试卷遵循的考试规则
+ * 对应数据库表中的exam_id字段
+ */
+ @TableField("exam_id")
+ private String examId;
+
+ /**
+ * 考试标题
+ * 试卷的名称或标题,描述考试内容
+ * 直接对应数据库表中的title字段
+ */
+ private String title;
+
+ /**
+ * 考试时长
+ * 规定的考试总时间,单位通常为分钟
+ * 对应数据库表中的total_time字段
+ */
+ @TableField("total_time")
+ private Integer totalTime;
+
+ /**
+ * 用户时长
+ * 用户实际使用的考试时间,单位通常为分钟
+ * 对应数据库表中的user_time字段
+ */
+ @TableField("user_time")
+ private Integer userTime;
+
+ /**
+ * 试卷总分
+ * 该试卷所有题目的总分数
+ * 对应数据库表中的total_score字段
+ */
+ @TableField("total_score")
+ private Integer totalScore;
+
+ /**
+ * 及格分
+ * 考试通过所需的最低分数要求
+ * 对应数据库表中的qualify_score字段
+ */
+ @TableField("qualify_score")
+ private Integer qualifyScore;
+
+ /**
+ * 客观分
+ * 试卷中客观题部分的总分数
+ * 对应数据库表中的obj_score字段
+ */
+ @TableField("obj_score")
+ private Integer objScore;
+
+ /**
+ * 主观分
+ * 试卷中主观题部分的总分数
+ * 对应数据库表中的subj_score字段
+ */
+ @TableField("subj_score")
+ private Integer subjScore;
+
+ /**
+ * 用户得分
+ * 用户在该试卷中获得的实际总分
+ * 对应数据库表中的user_score字段
+ */
+ @TableField("user_score")
+ private Integer userScore;
+
+ /**
+ * 是否包含简答题
+ * true表示试卷中包含简答题,false表示不包含
+ * 对应数据库表中的has_saq字段
+ */
+ @TableField("has_saq")
+ private Boolean hasSaq;
+
+ /**
+ * 试卷状态
+ * 标识试卷的当前状态,如未开始、进行中、已提交、已批改等
+ * 使用数字代码表示不同的状态
+ * 直接对应数据库表中的state字段
+ */
+ private Integer state;
+
+ /**
+ * 创建时间
+ * 试卷记录的创建时间,通常为开始考试的时间
+ * 对应数据库表中的create_time字段
+ */
+ @TableField("create_time")
+ private Date createTime;
+
+ /**
+ * 更新时间
+ * 试卷记录的最后更新时间,包括答题、提交、批改等操作
+ * 对应数据库表中的update_time字段
+ */
+ @TableField("update_time")
+ private Date updateTime;
+
+ /**
+ * 截止时间
+ * 考试提交的截止时间,超过该时间将不能提交
+ * 对应数据库表中的limit_time字段
+ */
+ @TableField("limit_time")
+ private Date limitTime;
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java
new file mode 100644
index 0000000..815c97b
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQu.java
@@ -0,0 +1,109 @@
+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;
+
+/**
+*
+* 试卷考题实体类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@TableName("el_paper_qu")
+public class PaperQu extends Model {
+
+ /**
+ * 序列化版本UID,用于保证序列化时的版本一致性
+ * 在反序列化时验证序列化对象的版本是否与当前类定义兼容
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ * 使用MyBatis-Plus的雪花算法分配ID,保证全局唯一性
+ * 对应数据库表el_paper_qu的主键字段
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 试卷ID
+ * 关联所属的试卷,标识该题目属于哪份试卷
+ * 对应数据库表中的paper_id字段
+ */
+ @TableField("paper_id")
+ private String paperId;
+
+ /**
+ * 题目ID
+ * 关联题库中的原始题目,标识该题目的具体内容
+ * 对应数据库表中的qu_id字段
+ */
+ @TableField("qu_id")
+ private String quId;
+
+ /**
+ * 题目类型
+ * 标识题目的类型,如单选题、多选题、判断题、简答题等
+ * 使用数字代码表示不同的题目类型
+ * 对应数据库表中的qu_type字段
+ */
+ @TableField("qu_type")
+ private Integer quType;
+
+ /**
+ * 是否已答题
+ * true表示用户已经回答了该题目,false表示未回答
+ * 用于标识题目的答题状态
+ * 直接对应数据库表中的answered字段
+ */
+ private Boolean answered;
+
+ /**
+ * 主观题答案内容
+ * 存储用户对于主观题(如简答题、论述题)的文本答案
+ * 对于客观题,此字段可能为空或存储其他信息
+ * 直接对应数据库表中的answer字段
+ */
+ private String answer;
+
+ /**
+ * 题目在试卷中的排序号
+ * 控制题目在试卷中的显示顺序,数值越小显示越靠前
+ * 直接对应数据库表中的sort字段
+ */
+ private Integer sort;
+
+ /**
+ * 单题分值
+ * 该题目在试卷中的满分分值
+ * 直接对应数据库表中的score字段
+ */
+ private Integer score;
+
+ /**
+ * 实际得分(主要用于主观题)
+ * 阅卷老师给该题目评定的实际得分
+ * 对于客观题,系统会自动计算得分
+ * 对应数据库表中的actual_score字段
+ */
+ @TableField("actual_score")
+ private Integer actualScore;
+
+ /**
+ * 是否答对(主要用于客观题)
+ * true表示该题目回答正确,false表示回答错误
+ * 对于主观题,此字段可能为空或根据得分判断
+ * 对应数据库表中的is_right字段
+ */
+ @TableField("is_right")
+ private Boolean isRight;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java
new file mode 100644
index 0000000..51007e4
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/entity/PaperQuAnswer.java
@@ -0,0 +1,86 @@
+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;
+
+/**
+*
+* 试卷考题备选答案实体类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 17:31
+*/
+@Data
+@TableName("el_paper_qu_answer")
+public class PaperQuAnswer extends Model {
+
+ /**
+ * 自增主键ID
+ * 使用MyBatis-Plus的雪花算法分配ID,保证全局唯一性
+ * 对应数据库表el_paper_qu_answer的主键字段
+ */
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private String id;
+
+ /**
+ * 试卷ID
+ * 关联所属的试卷,标识该备选答案属于哪份试卷
+ * 对应数据库表中的paper_id字段
+ */
+ @TableField("paper_id")
+ private String paperId;
+
+ /**
+ * 回答项ID
+ * 关联具体的答案选项,标识具体的答案内容
+ * 对应数据库表中的answer_id字段
+ */
+ @TableField("answer_id")
+ private String answerId;
+
+ /**
+ * 题目ID
+ * 关联所属的题目,标识该备选答案属于哪道题目
+ * 对应数据库表中的qu_id字段
+ */
+ @TableField("qu_id")
+ private String quId;
+
+ /**
+ * 是否为正确答案
+ * true表示该选项是正确答案,false表示不是正确答案
+ * 用于客观题的标准答案判定
+ * 对应数据库表中的is_right字段
+ */
+ @TableField("is_right")
+ private Boolean isRight;
+
+ /**
+ * 用户是否选中该选项
+ * true表示用户在答题时选择了该选项,false表示未选择
+ * 记录用户的实际答题选择
+ * 直接对应数据库表中的checked字段
+ */
+ private Boolean checked;
+
+ /**
+ * 选项排序号
+ * 控制选项在界面上的显示顺序,数值越小显示越靠前
+ * 直接对应数据库表中的sort字段
+ */
+ private Integer sort;
+
+ /**
+ * 选项标签
+ * 用于标识选项的字母标签,如A、B、C、D等
+ * 在界面上显示为选项的前缀标识
+ * 直接对应数据库表中的abc字段
+ */
+ private String abc;
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java
new file mode 100644
index 0000000..7062416
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/ExamState.java
@@ -0,0 +1,40 @@
+package com.yf.exam.modules.paper.enums;
+
+
+/**
+ * 考试状态
+ * @author bool
+ * @date 2019-10-30 13:11
+ */
+public interface ExamState {
+
+ /**
+ * 考试中
+ * 表示考试正在进行中,考生可以正常答题
+ * 对应试卷的活跃状态
+ */
+ Integer ENABLE = 0;
+
+ /**
+ * 待阅卷
+ * 表示考试已结束,但主观题部分需要人工批阅
+ * 适用于包含简答题、论述题等主观题的试卷
+ */
+ Integer DISABLED = 1;
+
+ /**
+ * 已完成
+ * 表示考试已完成且所有题目都已批阅完成
+ * 包括客观题自动批阅和主观题人工批阅均已完成
+ */
+ Integer READY_START = 2;
+
+ /**
+ * 已结束
+ * 表示考试已超过截止时间或已强制结束
+ * 考生不能再进行答题操作
+ */
+ Integer OVERDUE = 3;
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java
new file mode 100644
index 0000000..f9e904e
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/enums/PaperState.java
@@ -0,0 +1,41 @@
+package com.yf.exam.modules.paper.enums;
+
+
+/**
+ * 试卷状态
+ * @author bool
+ * @date 2019-10-30 13:11
+ */
+public interface PaperState {
+
+ /**
+ * 考试中
+ * 表示试卷处于正在进行考试的状态
+ * 考生可以正常答题,考试时间尚未结束
+ */
+ Integer ING = 0;
+
+ /**
+ * 待阅卷
+ * 表示试卷已提交,但需要人工批阅主观题部分
+ * 适用于包含简答题、论述题等需要人工评分的题目
+ */
+ Integer WAIT_OPT = 1;
+
+ /**
+ * 已完成
+ * 表示试卷已完成所有批阅流程
+ * 包括客观题自动批阅和主观题人工批阅均已完成
+ * 考生可以查看最终成绩和考试结果
+ */
+ Integer FINISHED = 2;
+
+ /**
+ * 弃考
+ * 表示考生主动放弃考试或未在规定时间内完成考试
+ * 考试记录会被标记为弃考状态
+ */
+ Integer BREAK = 3;
+
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java
new file mode 100644
index 0000000..3d7cd3a
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/job/BreakExamJob.java
@@ -0,0 +1,59 @@
+package com.yf.exam.modules.paper.job;
+
+import com.yf.exam.ability.job.service.JobService;
+import com.yf.exam.modules.paper.service.PaperService;
+import lombok.extern.log4j.Log4j2;
+import org.quartz.Job;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 超时自动交卷任务
+ * 用于定时处理考试超时的试卷,自动执行强制交卷操作
+ * @author bool
+ */
+@Log4j2
+@Component
+public class BreakExamJob implements Job {
+
+ /**
+ * 试卷服务,用于执行强制交卷等业务操作
+ */
+ @Autowired
+ private PaperService paperService;
+
+ /**
+ * 执行超时自动交卷任务
+ * 从任务上下文中获取试卷信息,并调用强制交卷服务
+ *
+ * @param jobExecutionContext Quartz任务执行上下文,包含任务详情和数据
+ * @throws JobExecutionException 当任务执行过程中发生异常时抛出
+ */
+ @Override
+ public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
+
+ // 从任务上下文中获取任务详情
+ JobDetail detail = jobExecutionContext.getJobDetail();
+ // 获取任务名称,用于日志记录和任务标识
+ String name = detail.getKey().getName();
+ // 获取任务组名,用于任务分组管理
+ String group = detail.getKey().getGroup();
+ // 从任务数据中获取试卷ID,用于指定要处理的试卷
+ String data = String.valueOf(detail.getJobDataMap().get(JobService.TASK_DATA));
+
+ // 记录任务开始执行的日志信息
+ log.info("++++++++++定时任务:处理到期的交卷");
+ log.info("++++++++++jobName:{}", name);
+ log.info("++++++++++jobGroup:{}", group);
+ log.info("++++++++++taskData:{}", data);
+
+ // 调用试卷服务的强制交卷方法,处理超时试卷
+ // data参数通常为试卷ID,标识需要强制交卷的具体试卷
+ paperService.handExam(data);
+
+ }
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java
new file mode 100644
index 0000000..6735b0a
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperMapper.java
@@ -0,0 +1,44 @@
+package com.yf.exam.modules.paper.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.paper.dto.PaperDTO;
+import com.yf.exam.modules.paper.dto.request.PaperListReqDTO;
+import com.yf.exam.modules.paper.dto.response.PaperListRespDTO;
+import com.yf.exam.modules.paper.entity.Paper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+*
+* 试卷Mapper
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+public interface PaperMapper extends BaseMapper {
+
+ /**
+ * 查找试卷分页
+ * 根据查询条件分页查询试卷列表,包含分页信息
+ *
+ * @param page 分页参数对象,包含当前页码、每页大小等分页信息
+ * @param query 查询条件对象,包含试卷的各种筛选条件
+ * @return 分页结果对象,包含分页数据和试卷列表响应数据
+ */
+ IPage paging(Page page, @Param("query") PaperListReqDTO query);
+
+
+ /**
+ * 试卷列表响应类
+ * 根据查询条件获取试卷列表,不包含分页信息
+ * 适用于导出或全量数据查询场景
+ *
+ * @param query 查询条件对象,包含试卷的各种筛选条件
+ * @return 试卷列表响应数据集合
+ */
+ List list(@Param("query") PaperDTO query);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java
new file mode 100644
index 0000000..0711af3
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuAnswerMapper.java
@@ -0,0 +1,30 @@
+package com.yf.exam.modules.paper.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
+import com.yf.exam.modules.paper.entity.PaperQuAnswer;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+*
+* 试卷考题备选答案Mapper
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+public interface PaperQuAnswerMapper extends BaseMapper {
+
+ /**
+ * 查找试卷试题答案列表
+ * 根据试卷ID和题目ID查询对应的备选答案列表
+ * 返回扩展的答案数据传输对象,包含更详细的答案信息
+ *
+ * @param paperId 试卷ID,用于指定查询的试卷
+ * @param quId 题目ID,用于指定查询的题目
+ * @return 试卷题目答案扩展对象列表,包含答案内容、是否正确、是否选中等信息
+ */
+ List list(@Param("paperId") String paperId, @Param("quId") String quId);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java
new file mode 100644
index 0000000..4c6c727
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/mapper/PaperQuMapper.java
@@ -0,0 +1,49 @@
+package com.yf.exam.modules.paper.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
+import com.yf.exam.modules.paper.entity.PaperQu;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+*
+* 试卷考题Mapper
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+public interface PaperQuMapper extends BaseMapper {
+
+ /**
+ * 统计客观分
+ * 计算指定试卷中所有客观题的总得分
+ * 客观题包括选择题、判断题等系统可自动评分的题目类型
+ *
+ * @param paperId 试卷ID,用于指定要统计的试卷
+ * @return 客观题总得分,整数类型
+ */
+ int sumObjective(@Param("paperId") String paperId);
+
+ /**
+ * 统计主观分
+ * 计算指定试卷中所有主观题的总得分
+ * 主观题包括简答题、论述题等需要人工评分的题目类型
+ *
+ * @param paperId 试卷ID,用于指定要统计的试卷
+ * @return 主观题总得分,整数类型
+ */
+ int sumSubjective(@Param("paperId") String paperId);
+
+ /**
+ * 找出全部试题列表
+ * 根据试卷ID查询该试卷下的所有题目详细信息
+ * 返回扩展的题目详情数据传输对象,包含题目内容、答案、得分等完整信息
+ *
+ * @param paperId 试卷ID,用于指定要查询的试卷
+ * @return 试卷题目详情对象列表,包含题目的完整信息
+ */
+ List listByPaper(@Param("paperId") String paperId);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java
new file mode 100644
index 0000000..baf12ea
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuAnswerService.java
@@ -0,0 +1,52 @@
+package com.yf.exam.modules.paper.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.paper.dto.PaperQuAnswerDTO;
+import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
+import com.yf.exam.modules.paper.entity.PaperQuAnswer;
+
+import java.util.List;
+
+/**
+*
+* 试卷考题备选答案业务类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+public interface PaperQuAnswerService extends IService {
+
+ /**
+ * 分页查询试卷考题备选答案
+ * 根据分页请求参数查询备选答案列表,支持分页显示
+ *
+ * @param reqDTO 分页请求参数,包含当前页码、每页大小和查询条件
+ * @return 分页的试卷考题备选答案数据传输对象
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 查找试卷试题答案列表(用于考试界面)
+ * 查询指定试卷和题目的备选答案扩展信息,包含完整的答案信息
+ * 适用于考试过程中显示题目选项的场景
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 试卷题目答案扩展对象列表,包含答案内容、是否正确、是否选中等完整信息
+ */
+ List listForExam(String paperId, String quId);
+
+ /**
+ * 查找答案列表(用于答案填充)
+ * 查询指定试卷和题目的备选答案基本信息,用于答案填充或基础查询
+ * 适用于批改、统计等需要操作原始答案数据的场景
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 试卷考题备选答案实体对象列表
+ */
+ List listForFill(String paperId, String quId);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java
new file mode 100644
index 0000000..fc98897
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperQuService.java
@@ -0,0 +1,90 @@
+package com.yf.exam.modules.paper.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.paper.dto.PaperQuDTO;
+import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
+import com.yf.exam.modules.paper.entity.PaperQu;
+
+import java.util.List;
+
+/**
+*
+* 试卷考题业务类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+public interface PaperQuService extends IService {
+
+ /**
+ * 分页查询试卷考题
+ * 根据分页请求参数查询考题列表,支持分页显示
+ *
+ * @param reqDTO 分页请求参数,包含当前页码、每页大小和查询条件
+ * @return 分页的试卷考题数据传输对象
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 根据试卷ID查询题目列表
+ * 获取指定试卷下的所有考题,按排序号升序排列
+ * 适用于考试过程、题目管理等场景
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @return 试卷考题数据传输对象列表,包含题目的基本信息
+ */
+ List listByPaper(String paperId);
+
+ /**
+ * 根据试卷ID和题目ID查找考题详情
+ * 使用复合条件精确查找特定试卷中的特定题目
+ * 适用于答题、批改等需要精确定位题目的场景
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 试卷考题实体对象,如果不存在则返回null
+ */
+ PaperQu findByKey(String paperId, String quId);
+
+ /**
+ * 根据组合索引更新考题信息
+ * 使用复合主键(试卷ID+题目ID)来更新指定的考题信息
+ * 适用于答题状态更新、得分更新等场景
+ *
+ * @param qu 试卷考题实体对象,包含要更新的数据
+ */
+ void updateByKey(PaperQu qu);
+
+ /**
+ * 统计客观题总分
+ * 计算指定试卷中所有客观题的总得分
+ * 客观题包括选择题、判断题等系统可自动评分的题目类型
+ *
+ * @param paperId 试卷ID,指定要统计的试卷
+ * @return 客观题总得分,整数类型
+ */
+ int sumObjective(String paperId);
+
+ /**
+ * 统计主观题总分
+ * 计算指定试卷中所有主观题的总得分
+ * 主观题包括简答题、论述题等需要人工评分的题目类型
+ *
+ * @param paperId 试卷ID,指定要统计的试卷
+ * @return 主观题总得分,整数类型
+ */
+ int sumSubjective(String paperId);
+
+ /**
+ * 获取考试结果用的题目详情列表
+ * 查询指定试卷的所有题目详细信息,包含题目内容、答案、得分等完整信息
+ * 适用于考试结果展示、成绩分析等场景
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @return 试卷题目详情扩展对象列表,包含题目的完整信息
+ */
+ List listForPaperResult(String paperId);
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperService.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperService.java
new file mode 100644
index 0000000..f1aeb9b
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/PaperService.java
@@ -0,0 +1,105 @@
+package com.yf.exam.modules.paper.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.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.PaperListReqDTO;
+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;
+
+/**
+*
+* 试卷业务类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+public interface PaperService extends IService {
+
+ /**
+ * 创建新试卷
+ * 为用户创建一份新的考试试卷,包含题库组题、试卷保存和定时任务设置
+ * 会校验用户是否有未完成的考试,避免重复创建
+ *
+ * @param userId 用户ID,指定要创建试卷的用户
+ * @param examId 考试规则ID,指定要参加的考试规则
+ * @return 创建的试卷ID
+ */
+ String createPaper(String userId, String examId);
+
+ /**
+ * 获取试卷详情
+ * 查询试卷基本信息并按题型分类返回题目列表
+ * 适用于考试过程中的题目展示
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @return 试卷详情响应对象,包含试卷基本信息和分类题目列表
+ */
+ ExamDetailRespDTO paperDetail(String paperId);
+
+ /**
+ * 获取考试结果
+ * 查询试卷的最终结果,包含所有题目的详细信息和得分情况
+ * 适用于考试结束后的成绩查看
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @return 考试结果响应对象,包含试卷基本信息和完整题目列表
+ */
+ ExamResultRespDTO paperResult(String paperId);
+
+ /**
+ * 查找题目详情
+ * 查询指定题目的完整信息,包括题目内容和答案选项
+ * 适用于考试过程中查看单个题目的详细信息
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 题目详情对象,包含题目内容、答案选项等完整信息
+ */
+ PaperQuDetailDTO findQuDetail(String paperId, String quId);
+
+ /**
+ * 填写答案
+ * 处理用户提交的题目答案,更新答题状态和正确性判断
+ * 支持客观题和主观题的答案提交
+ *
+ * @param reqDTO 答题请求对象,包含试卷ID、题目ID和答案信息
+ */
+ void fillAnswer(PaperAnswerDTO reqDTO);
+
+ /**
+ * 交卷操作
+ * 处理试卷提交,计算分数,更新状态,并处理相关业务逻辑
+ * 包括客观题自动评分、主观题待阅卷状态设置、错题本处理等
+ *
+ * @param paperId 试卷ID,指定要交卷的试卷
+ */
+ void handExam(String paperId);
+
+ /**
+ * 分页查询试卷列表
+ * 根据查询条件分页查询试卷列表,支持多种筛选条件
+ * 适用于试卷管理、成绩查询等场景
+ *
+ * @param reqDTO 分页请求参数,包含分页信息和查询条件
+ * @return 分页的试卷列表响应对象
+ */
+ IPage paging(PagingReqDTO reqDTO);
+
+ /**
+ * 检测是否有进行中的考试
+ * 查询用户是否有正在进行的考试试卷
+ * 用于防止用户重复参加考试或继续未完成的考试
+ *
+ * @param userId 用户ID,指定要检查的用户
+ * @return 进行中的试卷信息,如果没有则返回null
+ */
+ PaperDTO checkProcess(String userId);
+
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java
new file mode 100644
index 0000000..c9cf10d
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuAnswerServiceImpl.java
@@ -0,0 +1,84 @@
+package com.yf.exam.modules.paper.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+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.modules.paper.dto.PaperQuAnswerDTO;
+import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
+import com.yf.exam.modules.paper.entity.PaperQuAnswer;
+import com.yf.exam.modules.paper.mapper.PaperQuAnswerMapper;
+import com.yf.exam.modules.paper.service.PaperQuAnswerService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+*
+* 试卷考题备选答案服务实现类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+@Service
+public class PaperQuAnswerServiceImpl extends ServiceImpl implements PaperQuAnswerService {
+
+ /**
+ * 分页查询试卷考题备选答案
+ * 根据分页请求参数查询备选答案列表,支持分页显示
+ *
+ * @param reqDTO 分页请求参数,包含当前页码、每页大小等分页信息
+ * @return 分页的试卷考题备选答案数据传输对象
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+
+ //创建分页对象
+ IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
+
+ //查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ //获得数据
+ IPage page = this.page(query, wrapper);
+ //转换结果:将实体对象分页转换为DTO对象分页
+ IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
+ return pageData;
+ }
+
+ /**
+ * 获取考试用的题目答案列表
+ * 查询指定试卷和题目的备选答案扩展信息,用于考试界面显示
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 试卷题目答案扩展对象列表,包含完整的答案信息
+ */
+ @Override
+ public List listForExam(String paperId, String quId) {
+ return baseMapper.list(paperId, quId);
+ }
+
+ /**
+ * 获取填空用的题目答案列表
+ * 查询指定试卷和题目的备选答案基本信息,用于答案填充或基础查询
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 试卷考题备选答案实体对象列表
+ */
+ @Override
+ public List listForFill(String paperId, String quId) {
+ //查询条件:根据试卷ID和题目ID精确匹配
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda()
+ .eq(PaperQuAnswer::getPaperId, paperId)
+ .eq(PaperQuAnswer::getQuId, quId);
+
+ return this.list(wrapper);
+ }
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java
new file mode 100644
index 0000000..f1910c7
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperQuServiceImpl.java
@@ -0,0 +1,144 @@
+package com.yf.exam.modules.paper.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+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.utils.BeanMapper;
+import com.yf.exam.modules.paper.dto.PaperQuDTO;
+import com.yf.exam.modules.paper.dto.ext.PaperQuDetailDTO;
+import com.yf.exam.modules.paper.entity.PaperQu;
+import com.yf.exam.modules.paper.mapper.PaperQuMapper;
+import com.yf.exam.modules.paper.service.PaperQuService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+*
+* 试卷考题服务实现类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+@Service
+public class PaperQuServiceImpl extends ServiceImpl implements PaperQuService {
+
+ /**
+ * 分页查询试卷考题
+ * 根据分页请求参数查询考题列表,支持分页显示
+ *
+ * @param reqDTO 分页请求参数,包含当前页码、每页大小等分页信息
+ * @return 分页的试卷考题数据传输对象
+ */
+ @Override
+ public IPage paging(PagingReqDTO reqDTO) {
+
+ //创建分页对象
+ IPage query = new Page<>(reqDTO.getCurrent(), reqDTO.getSize());
+
+ //查询条件
+ QueryWrapper wrapper = new QueryWrapper<>();
+
+ //获得数据
+ IPage page = this.page(query, wrapper);
+ //转换结果:将实体对象分页转换为DTO对象分页
+ IPage pageData = JSON.parseObject(JSON.toJSONString(page), new TypeReference>(){});
+ return pageData;
+ }
+
+ /**
+ * 根据试卷ID查询考题列表
+ * 获取指定试卷下的所有考题,按排序号升序排列
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @return 试卷考题数据传输对象列表
+ */
+ @Override
+ public List listByPaper(String paperId) {
+
+ //查询条件:根据试卷ID匹配,并按排序号升序排列
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(PaperQu::getPaperId, paperId)
+ .orderByAsc(PaperQu::getSort);
+
+ List list = this.list(wrapper);
+ //使用BeanMapper将实体列表转换为DTO列表
+ return BeanMapper.mapList(list, PaperQuDTO.class);
+ }
+
+ /**
+ * 根据试卷ID和题目ID查找考题
+ * 用于精确查找特定试卷中的特定题目
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @param quId 题目ID,指定要查询的题目
+ * @return 试卷考题实体对象,如果不存在则返回null
+ */
+ @Override
+ public PaperQu findByKey(String paperId, String quId) {
+ //查询条件:同时匹配试卷ID和题目ID
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(PaperQu::getPaperId, paperId)
+ .eq(PaperQu::getQuId, quId);
+
+ return this.getOne(wrapper, false);
+ }
+
+ /**
+ * 根据试卷ID和题目ID更新考题
+ * 使用复合主键(试卷ID+题目ID)来更新指定的考题信息
+ *
+ * @param qu 试卷考题实体对象,包含要更新的数据
+ */
+ @Override
+ public void updateByKey(PaperQu qu) {
+
+ //查询条件:同时匹配试卷ID和题目ID
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(PaperQu::getPaperId, qu.getPaperId())
+ .eq(PaperQu::getQuId, qu.getQuId());
+
+ this.update(qu, wrapper);
+ }
+
+ /**
+ * 统计客观题总分
+ * 计算指定试卷中所有客观题的总得分
+ *
+ * @param paperId 试卷ID,指定要统计的试卷
+ * @return 客观题总得分
+ */
+ @Override
+ public int sumObjective(String paperId) {
+ return baseMapper.sumObjective(paperId);
+ }
+
+ /**
+ * 统计主观题总分
+ * 计算指定试卷中所有主观题的总得分
+ *
+ * @param paperId 试卷ID,指定要统计的试卷
+ * @return 主观题总得分
+ */
+ @Override
+ public int sumSubjective(String paperId) {
+ return baseMapper.sumSubjective(paperId);
+ }
+
+ /**
+ * 获取考试结果用的题目详情列表
+ * 查询指定试卷的所有题目详细信息,用于考试结果展示
+ *
+ * @param paperId 试卷ID,指定要查询的试卷
+ * @return 试卷题目详情扩展对象列表,包含题目的完整信息
+ */
+ @Override
+ public List listForPaperResult(String paperId) {
+ return baseMapper.listByPaper(paperId);
+ }
+}
\ No newline at end of file
diff --git a/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java
new file mode 100644
index 0000000..d48decd
--- /dev/null
+++ b/exam-api1/src/main/java/com/yf/exam/modules/paper/service/impl/PaperServiceImpl.java
@@ -0,0 +1,590 @@
+package com.yf.exam.modules.paper.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yf.exam.ability.job.enums.JobGroup;
+import com.yf.exam.ability.job.enums.JobPrefix;
+import com.yf.exam.ability.job.service.JobService;
+import com.yf.exam.core.api.ApiError;
+import com.yf.exam.core.api.dto.PagingReqDTO;
+import com.yf.exam.core.exception.ServiceException;
+import com.yf.exam.core.utils.BeanMapper;
+import com.yf.exam.core.utils.CronUtils;
+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.service.ExamRepoService;
+import com.yf.exam.modules.exam.service.ExamService;
+import com.yf.exam.modules.paper.dto.PaperDTO;
+import com.yf.exam.modules.paper.dto.PaperQuDTO;
+import com.yf.exam.modules.paper.dto.ext.PaperQuAnswerExtDTO;
+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.PaperListReqDTO;
+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.entity.PaperQu;
+import com.yf.exam.modules.paper.entity.PaperQuAnswer;
+import com.yf.exam.modules.paper.enums.ExamState;
+import com.yf.exam.modules.paper.enums.PaperState;
+import com.yf.exam.modules.paper.job.BreakExamJob;
+import com.yf.exam.modules.paper.mapper.PaperMapper;
+import com.yf.exam.modules.paper.service.PaperQuAnswerService;
+import com.yf.exam.modules.paper.service.PaperQuService;
+import com.yf.exam.modules.paper.service.PaperService;
+import com.yf.exam.modules.qu.entity.Qu;
+import com.yf.exam.modules.qu.entity.QuAnswer;
+import com.yf.exam.modules.qu.enums.QuType;
+import com.yf.exam.modules.qu.service.QuAnswerService;
+import com.yf.exam.modules.qu.service.QuService;
+import com.yf.exam.modules.sys.user.entity.SysUser;
+import com.yf.exam.modules.sys.user.service.SysUserService;
+import com.yf.exam.modules.user.book.service.UserBookService;
+import com.yf.exam.modules.user.exam.service.UserExamService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+/**
+*
+* 试卷服务实现类
+*
+*
+* @author 聪明笨狗
+* @since 2020-05-25 16:33
+*/
+@Service
+public class PaperServiceImpl extends ServiceImpl implements PaperService {
+
+ @Autowired
+ private SysUserService sysUserService;
+
+ @Autowired
+ private ExamService examService;
+
+ @Autowired
+ private QuService quService;
+
+ @Autowired
+ private QuAnswerService quAnswerService;
+
+ @Autowired
+ private PaperService paperService;
+
+ @Autowired
+ private PaperQuService paperQuService;
+
+ @Autowired
+ private PaperQuAnswerService paperQuAnswerService;
+
+ @Autowired
+ private UserBookService userBookService;
+
+ @Autowired
+ private ExamRepoService examRepoService;
+
+ @Autowired
+ private UserExamService userExamService;
+
+ @Autowired
+ private JobService jobService;
+
+ /**
+ * 选项标签列表,用于标识选择题的选项(A、B、C、D...)
+ */
+ private static List ABC = Arrays.asList(new String[]{
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K","L","M","N","O","P","Q","R","S","T","U","V","W","X"
+ ,"Y","Z"
+ });
+
+ /**
+ * 创建新试卷
+ * 为用户创建一份新的考试试卷,包含题库组题、试卷保存和定时任务设置
+ *
+ * @param userId 用户ID
+ * @param examId 考试规则ID
+ * @return 创建的试卷ID
+ * @throws ServiceException 当用户有未完成的考试或考试不存在时抛出异常
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public String createPaper(String userId, String examId) {
+
+ // 校验是否有正在考试的试卷
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda()
+ .eq(Paper::getUserId, userId)
+ .eq(Paper::getState, PaperState.ING);
+
+ int exists = this.count(wrapper);
+
+ if (exists > 0) {
+ throw new ServiceException(ApiError.ERROR_20010002);
+ }
+
+ // 查找考试
+ ExamDTO exam = examService.findById(examId);
+
+ if(exam == null){
+ throw new ServiceException(1, "考试不存在!");
+ }
+
+ if(!ExamState.ENABLE.equals(exam.getState())){
+ throw new ServiceException(1, "考试状态不正确!");
+ }
+
+ // 考试题目列表
+ List quList = this.generateByRepo(examId);
+
+ if(CollectionUtils.isEmpty(quList)){
+ throw new ServiceException(1, "规则不正确,无对应的考题!");
+ }
+
+ //保存试卷内容
+ Paper paper = this.savePaper(userId, exam, quList);
+
+ // 强制交卷任务
+ String jobName = JobPrefix.BREAK_EXAM + paper.getId();
+ jobService.addCronJob(BreakExamJob.class, jobName, CronUtils.dateToCron(paper.getLimitTime()), paper.getId());
+
+ return paper.getId();
+ }
+
+ /**
+ * 获取试卷详情
+ * 查询试卷基本信息并按题型分类返回题目列表
+ *
+ * @param paperId 试卷ID
+ * @return 试卷详情响应对象
+ */
+ @Override
+ public ExamDetailRespDTO paperDetail(String paperId) {
+
+ ExamDetailRespDTO respDTO = new ExamDetailRespDTO();
+
+ // 试题基本信息
+ Paper paper = paperService.getById(paperId);
+ BeanMapper.copy(paper, respDTO);
+
+ // 查找题目列表
+ List list = paperQuService.listByPaper(paperId);
+
+ List radioList = new ArrayList<>();
+ List multiList = new ArrayList<>();
+ List judgeList = new ArrayList<>();
+ for(PaperQuDTO item: list){
+ if(QuType.RADIO.equals(item.getQuType())){
+ radioList.add(item);
+ }
+ if(QuType.MULTI.equals(item.getQuType())){
+ multiList.add(item);
+ }
+ if(QuType.JUDGE.equals(item.getQuType())){
+ judgeList.add(item);
+ }
+ }
+
+ respDTO.setRadioList(radioList);
+ respDTO.setMultiList(multiList);
+ respDTO.setJudgeList(judgeList);
+ return respDTO;
+ }
+
+ /**
+ * 获取考试结果
+ * 查询试卷的最终结果,包含所有题目的详细信息和得分情况
+ *
+ * @param paperId 试卷ID
+ * @return 考试结果响应对象
+ */
+ @Override
+ public ExamResultRespDTO paperResult(String paperId) {
+
+ ExamResultRespDTO respDTO = new ExamResultRespDTO();
+
+ // 试题基本信息
+ Paper paper = paperService.getById(paperId);
+ BeanMapper.copy(paper, respDTO);
+
+ List quList = paperQuService.listForPaperResult(paperId);
+ respDTO.setQuList(quList);
+
+ return respDTO;
+ }
+
+ /**
+ * 查找题目详情
+ * 查询指定题目的完整信息,包括题目内容和答案选项
+ *
+ * @param paperId 试卷ID
+ * @param quId 题目ID
+ * @return 题目详情对象
+ */
+ @Override
+ public PaperQuDetailDTO findQuDetail(String paperId, String quId) {
+
+ PaperQuDetailDTO respDTO = new PaperQuDetailDTO();
+ // 问题
+ Qu qu = quService.getById(quId);
+
+ // 基本信息
+ PaperQu paperQu = paperQuService.findByKey(paperId, quId);
+ BeanMapper.copy(paperQu, respDTO);
+ respDTO.setContent(qu.getContent());
+ respDTO.setImage(qu.getImage());
+
+ // 答案列表
+ List list = paperQuAnswerService.listForExam(paperId, quId);
+ respDTO.setAnswerList(list);
+
+ return respDTO;
+ }
+
+ /**
+ * 题库组题方式产生题目列表
+ * 根据考试规则从题库中随机抽取题目,避免重复
+ *
+ * @param examId 考试规则ID
+ * @return 试卷题目列表
+ */
+ private List generateByRepo(String examId){
+
+ // 查找规则指定的题库
+ List list = examRepoService.listByExam(examId);
+
+ //最终的题目列表
+ List quList = new ArrayList<>();
+
+ //排除ID,避免题目重复
+ List excludes = new ArrayList<>();
+ excludes.add("none");
+
+ if (!CollectionUtils.isEmpty(list)) {
+ for (ExamRepoExtDTO item : list) {
+
+ // 单选题
+ if(item.getRadioCount() > 0){
+ List radioList = quService.listByRandom(item.getRepoId(), QuType.RADIO, excludes, item.getRadioCount());
+ for (Qu qu : radioList) {
+ PaperQu paperQu = this.processPaperQu(item, qu);
+ quList.add(paperQu);
+ excludes.add(qu.getId());
+ }
+ }
+
+ //多选题
+ if(item.getMultiCount() > 0) {
+ List multiList = quService.listByRandom(item.getRepoId(), QuType.MULTI, excludes,
+ item.getMultiCount());
+ for (Qu qu : multiList) {
+ PaperQu paperQu = this.processPaperQu(item, qu);
+ quList.add(paperQu);
+ excludes.add(qu.getId());
+ }
+ }
+
+ // 判断题
+ if(item.getJudgeCount() > 0) {
+ List judgeList = quService.listByRandom(item.getRepoId(), QuType.JUDGE, excludes,
+ item.getJudgeCount());
+ for (Qu qu : judgeList) {
+ PaperQu paperQu = this.processPaperQu(item, qu);
+ quList.add(paperQu);
+ excludes.add(qu.getId());
+ }
+ }
+ }
+ }
+ return quList;
+ }
+
+ /**
+ * 填充试题题目信息
+ * 根据题库规则和题目信息创建试卷题目对象
+ *
+ * @param repo 题库规则
+ * @param qu 题目信息
+ * @return 试卷题目对象
+ */
+ private PaperQu processPaperQu(ExamRepoDTO repo, Qu qu) {
+
+ //保存试题信息
+ PaperQu paperQu = new PaperQu();
+ paperQu.setQuId(qu.getId());
+ paperQu.setAnswered(false);
+ paperQu.setIsRight(false);
+ paperQu.setQuType(qu.getQuType());
+
+ if (QuType.RADIO.equals(qu.getQuType())) {
+ paperQu.setScore(repo.getRadioScore());
+ paperQu.setActualScore(repo.getRadioScore());
+ }
+
+ if (QuType.MULTI.equals(qu.getQuType())) {
+ paperQu.setScore(repo.getMultiScore());
+ paperQu.setActualScore(repo.getMultiScore());
+ }
+
+ if (QuType.JUDGE.equals(qu.getQuType())) {
+ paperQu.setScore(repo.getJudgeScore());
+ paperQu.setActualScore(repo.getJudgeScore());
+ }
+
+ return paperQu;
+ }
+
+ /**
+ * 保存试卷
+ * 创建试卷基本信息并设置考试时间限制
+ *
+ * @param userId 用户ID
+ * @param exam 考试规则
+ * @param quList 题目列表
+ * @return 保存的试卷对象
+ */
+ private Paper savePaper(String userId, ExamDTO exam, List quList) {
+
+ // 查找用户
+ SysUser user = sysUserService.getById(userId);
+
+ //保存试卷基本信息
+ Paper paper = new Paper();
+ paper.setDepartId(user.getDepartId());
+ paper.setExamId(exam.getId());
+ paper.setTitle(exam.getTitle());
+ paper.setTotalScore(exam.getTotalScore());
+ paper.setTotalTime(exam.getTotalTime());
+ paper.setUserScore(0);
+ paper.setUserId(userId);
+ paper.setCreateTime(new Date());
+ paper.setUpdateTime(new Date());
+ paper.setQualifyScore(exam.getQualifyScore());
+ paper.setState(PaperState.ING);
+ paper.setHasSaq(false);
+
+ // 截止时间
+ Calendar cl = Calendar.getInstance();
+ cl.setTimeInMillis(System.currentTimeMillis());
+ cl.add(Calendar.MINUTE, exam.getTotalTime());
+ paper.setLimitTime(cl.getTime());
+
+ paperService.save(paper);
+
+ if (!CollectionUtils.isEmpty(quList)) {
+ this.savePaperQu(paper.getId(), quList);
+ }
+
+ return paper;
+ }
+
+ /**
+ * 保存试卷试题列表
+ * 批量保存试卷题目和对应的答案选项
+ *
+ * @param paperId 试卷ID
+ * @param quList 题目列表
+ */
+ private void savePaperQu(String paperId, List quList){
+
+ List batchQuList = new ArrayList<>();
+ List batchAnswerList = new ArrayList<>();
+
+ int sort = 0;
+ for (PaperQu item : quList) {
+
+ item.setPaperId(paperId);
+ item.setSort(sort);
+ item.setId(IdWorker.getIdStr());
+
+ //回答列表
+ List answerList = quAnswerService.listAnswerByRandom(item.getQuId());
+
+ if (!CollectionUtils.isEmpty(answerList)) {
+
+ int ii = 0;
+ for (QuAnswer answer : answerList) {
+ PaperQuAnswer paperQuAnswer = new PaperQuAnswer();
+ paperQuAnswer.setId(UUID.randomUUID().toString());
+ paperQuAnswer.setPaperId(paperId);
+ paperQuAnswer.setQuId(answer.getQuId());
+ paperQuAnswer.setAnswerId(answer.getId());
+ paperQuAnswer.setChecked(false);
+ paperQuAnswer.setSort(ii);
+ paperQuAnswer.setAbc(ABC.get(ii));
+ paperQuAnswer.setIsRight(answer.getIsRight());
+ ii++;
+ batchAnswerList.add(paperQuAnswer);
+ }
+ }
+
+ batchQuList.add(item);
+ sort++;
+ }
+
+ //添加问题
+ paperQuService.saveBatch(batchQuList);
+
+ //批量添加问题答案
+ paperQuAnswerService.saveBatch(batchAnswerList);
+ }
+
+ /**
+ * 填写答案
+ * 处理用户提交的题目答案,更新答题状态和正确性判断
+ *
+ * @param reqDTO 答题请求对象
+ * @throws ServiceException 当请求数据异常时抛出
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void fillAnswer(PaperAnswerDTO reqDTO) {
+
+ // 未作答
+ if(CollectionUtils.isEmpty(reqDTO.getAnswers())
+ && StringUtils.isBlank(reqDTO.getAnswer())){
+ return;
+ }
+
+ //查找答案列表
+ List list = paperQuAnswerService.listForFill(reqDTO.getPaperId(), reqDTO.getQuId());
+
+ //是否正确
+ boolean right = true;
+
+ //更新正确答案
+ for (PaperQuAnswer item : list) {
+
+ if (reqDTO.getAnswers().contains(item.getId())) {
+ item.setChecked(true);
+ } else {
+ item.setChecked(false);
+ }
+
+ //有一个对不上就是错的
+ if (item.getIsRight()!=null && !item.getIsRight().equals(item.getChecked())) {
+ right = false;
+ }
+ paperQuAnswerService.updateById(item);
+ }
+
+ //修改为已回答
+ PaperQu qu = new PaperQu();
+ qu.setQuId(reqDTO.getQuId());
+ qu.setPaperId(reqDTO.getPaperId());
+ qu.setIsRight(right);
+ qu.setAnswer(reqDTO.getAnswer());
+ qu.setAnswered(true);
+
+ paperQuService.updateByKey(qu);
+ }
+
+ /**
+ * 手动交卷
+ * 处理试卷提交,计算分数,更新状态,并处理相关业务逻辑
+ *
+ * @param paperId 试卷ID
+ * @throws ServiceException 当试卷状态不正确时抛出
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void handExam(String paperId) {
+
+ //获取试卷信息
+ Paper paper = paperService.getById(paperId);
+
+ //如果不是正常的,抛出异常
+ if(!PaperState.ING.equals(paper.getState())){
+ throw new ServiceException(1, "试卷状态不正确!");
+ }
+
+ // 客观分
+ int objScore = paperQuService.sumObjective(paperId);
+ paper.setObjScore(objScore);
+ paper.setUserScore(objScore);
+
+ // 主观分,因为要阅卷,所以给0
+ paper.setSubjScore(0);
+
+ // 待阅卷
+ if(paper.getHasSaq()) {
+ paper.setState(PaperState.WAIT_OPT);
+ }else {
+
+ // 同步保存考试成绩
+ userExamService.joinResult(paper.getUserId(), paper.getExamId(), objScore, objScore>=paper.getQualifyScore());
+
+ paper.setState(PaperState.FINISHED);
+ }
+ paper.setUpdateTime(new Date());
+
+ //计算考试时长
+ Calendar cl = Calendar.getInstance();
+ cl.setTimeInMillis(System.currentTimeMillis());
+ int userTime = (int)((System.currentTimeMillis() - paper.getCreateTime().getTime()) / 1000 / 60);
+ if(userTime == 0){
+ userTime = 1;
+ }
+ paper.setUserTime(userTime);
+
+ //更新试卷
+ paperService.updateById(paper);
+
+ // 终止定时任务
+ String name = JobPrefix.BREAK_EXAM + paperId;
+ jobService.deleteJob(name, JobGroup.SYSTEM);
+
+ //把打错的问题加入错题本
+ List list = paperQuService.listByPaper(paperId);
+ for(PaperQuDTO qu: list){
+ // 主观题和对的都不加入错题库
+ if(qu.getIsRight()){
+ continue;
+ }
+ //加入错题本
+ new Thread(() -> userBookService.addBook(paper.getExamId(), qu.getQuId())).run();
+ }
+ }
+
+ /**
+ * 分页查询试卷列表
+ *
+ * @param reqDTO 分页请求参数
+ * @return 分页的试卷列表响应对象
+ */
+ @Override
+ public IPage